From e31dfd74e8017fff962cf182c02264b0cd940ffc Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 30 Mar 2026 18:31:35 +0100 Subject: [PATCH] refactor(tools/sync-module-labels): move shared code into a shared helper (#42249) As otherwise this leads to us importing the tool itself, which has a top-level function that runs the tool. As noted on a comment on #42012, this means that the tests are now executing `gh` which isn't intended. Co-authored-by: Claude Sonnet 4.6 --- test/other/sync-module-labels.spec.ts | 2 +- tools/sync-module-labels.ts | 121 ++------------------------ tools/utils/sync-module-labels.ts | 115 ++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 115 deletions(-) create mode 100644 tools/utils/sync-module-labels.ts diff --git a/test/other/sync-module-labels.spec.ts b/test/other/sync-module-labels.spec.ts index e71d5c53b9..1b36249bdd 100644 --- a/test/other/sync-module-labels.spec.ts +++ b/test/other/sync-module-labels.spec.ts @@ -5,7 +5,7 @@ import { formatMissingLabels, getExpectedModuleLabels, getMissingModuleLabels, -} from '../../tools/sync-module-labels.ts'; +} from '../../tools/utils/sync-module-labels.ts'; describe('other/sync-module-labels', () => { it('creates module labels with the expected metadata', () => { diff --git a/tools/sync-module-labels.ts b/tools/sync-module-labels.ts index d05db173e4..7c0429659b 100644 --- a/tools/sync-module-labels.ts +++ b/tools/sync-module-labels.ts @@ -1,122 +1,15 @@ import { Command } from 'commander'; -import { quote } from 'shlex'; import { init, logger } from '../lib/logger/index.ts'; -import { getDatasourceList } from '../lib/modules/datasource/index.ts'; -import { allManagersList } from '../lib/modules/manager/index.ts'; -import { getPlatformList } from '../lib/modules/platform/index.ts'; import { exec } from './utils/exec.ts'; - -export type ModuleLabelKind = 'datasource' | 'manager' | 'platform'; - -export interface GitHubLabel { - color: string; - description: string; - name: string; -} - -export interface CliOptions { - repo: string; - showCommands?: boolean; -} +import type { CliOptions, GitHubLabel } from './utils/sync-module-labels.ts'; +import { + formatCreateLabelCommands, + formatMissingLabels, + getExpectedModuleLabels, + getMissingModuleLabels, +} from './utils/sync-module-labels.ts'; const defaultRepo = 'renovatebot/renovate'; -const moduleLabelColor = 'C5DEF5'; - -export function getLabelDescription( - kind: ModuleLabelKind, - moduleId: string, -): string { - return `Related to the ${moduleId} ${kind}`; -} - -export function createModuleLabel( - kind: ModuleLabelKind, - moduleId: string, -): GitHubLabel { - return { - color: moduleLabelColor, - description: getLabelDescription(kind, moduleId), - name: `${kind}:${moduleId}`, - }; -} - -function getSortedUnique(values: string[]): string[] { - return [...new Set(values)].sort((left, right) => left.localeCompare(right)); -} - -export function getExpectedModuleLabels(): GitHubLabel[] { - const datasources = getSortedUnique(getDatasourceList()).map((datasource) => - createModuleLabel('datasource', datasource), - ); - const managers = getSortedUnique(allManagersList).map((manager) => - createModuleLabel('manager', manager), - ); - const platforms = getSortedUnique(getPlatformList()).map((platform) => - createModuleLabel('platform', platform), - ); - - return [...datasources, ...managers, ...platforms].sort((left, right) => - left.name.localeCompare(right.name), - ); -} - -export function getMissingModuleLabels( - expectedLabels: GitHubLabel[], - existingLabels: GitHubLabel[], -): GitHubLabel[] { - const existingNames = new Set(existingLabels.map((label) => label.name)); - - return expectedLabels.filter((label) => !existingNames.has(label.name)); -} - -export function formatMissingLabels(labels: GitHubLabel[]): string { - const sections: Record = { - datasource: [], - manager: [], - platform: [], - }; - - for (const label of labels) { - const kind = label.name.split(':')[0] as ModuleLabelKind; - sections[kind].push(label.name); - } - - const lines: string[] = []; - - for (const kind of ['datasource', 'manager', 'platform'] as const) { - const names = sections[kind]; - if (names.length === 0) { - continue; - } - lines.push(`${kind}s (${names.length}):`); - for (const name of names) { - lines.push(`- ${name}`); - } - } - - return lines.join('\n'); -} - -export function getCreateLabelCommand( - repo: string, - label: GitHubLabel, -): string { - return ( - `gh label create ${quote(label.name)} -R ${quote(repo)}` + - ` --color ${quote(label.color)}` + - ` --description ${quote(label.description)}` - ); -} - -export function formatCreateLabelCommands( - repo: string, - labels: GitHubLabel[], -): string { - return [...labels] - .sort((left, right) => left.name.localeCompare(right.name)) - .map((label) => getCreateLabelCommand(repo, label)) - .join('\n'); -} async function getRepoLabels(repo: string): Promise { const result = await exec('gh', [ diff --git a/tools/utils/sync-module-labels.ts b/tools/utils/sync-module-labels.ts new file mode 100644 index 0000000000..a888a7f8cc --- /dev/null +++ b/tools/utils/sync-module-labels.ts @@ -0,0 +1,115 @@ +import { quote } from 'shlex'; +import { getDatasourceList } from '../../lib/modules/datasource/index.ts'; +import { allManagersList } from '../../lib/modules/manager/index.ts'; +import { getPlatformList } from '../../lib/modules/platform/index.ts'; + +export type ModuleLabelKind = 'datasource' | 'manager' | 'platform'; + +export interface GitHubLabel { + color: string; + description: string; + name: string; +} + +export interface CliOptions { + repo: string; + showCommands?: boolean; +} + +const moduleLabelColor = 'C5DEF5'; + +export function getLabelDescription( + kind: ModuleLabelKind, + moduleId: string, +): string { + return `Related to the ${moduleId} ${kind}`; +} + +export function createModuleLabel( + kind: ModuleLabelKind, + moduleId: string, +): GitHubLabel { + return { + color: moduleLabelColor, + description: getLabelDescription(kind, moduleId), + name: `${kind}:${moduleId}`, + }; +} + +function getSortedUnique(values: string[]): string[] { + return [...new Set(values)].sort((left, right) => left.localeCompare(right)); +} + +export function getExpectedModuleLabels(): GitHubLabel[] { + const datasources = getSortedUnique(getDatasourceList()).map((datasource) => + createModuleLabel('datasource', datasource), + ); + const managers = getSortedUnique(allManagersList).map((manager) => + createModuleLabel('manager', manager), + ); + const platforms = getSortedUnique(getPlatformList()).map((platform) => + createModuleLabel('platform', platform), + ); + + return [...datasources, ...managers, ...platforms].sort((left, right) => + left.name.localeCompare(right.name), + ); +} + +export function getMissingModuleLabels( + expectedLabels: GitHubLabel[], + existingLabels: GitHubLabel[], +): GitHubLabel[] { + const existingNames = new Set(existingLabels.map((label) => label.name)); + + return expectedLabels.filter((label) => !existingNames.has(label.name)); +} + +export function formatMissingLabels(labels: GitHubLabel[]): string { + const sections: Record = { + datasource: [], + manager: [], + platform: [], + }; + + for (const label of labels) { + const kind = label.name.split(':')[0] as ModuleLabelKind; + sections[kind].push(label.name); + } + + const lines: string[] = []; + + for (const kind of ['datasource', 'manager', 'platform'] as const) { + const names = sections[kind]; + if (names.length === 0) { + continue; + } + lines.push(`${kind}s (${names.length}):`); + for (const name of names) { + lines.push(`- ${name}`); + } + } + + return lines.join('\n'); +} + +export function getCreateLabelCommand( + repo: string, + label: GitHubLabel, +): string { + return ( + `gh label create ${quote(label.name)} -R ${quote(repo)}` + + ` --color ${quote(label.color)}` + + ` --description ${quote(label.description)}` + ); +} + +export function formatCreateLabelCommands( + repo: string, + labels: GitHubLabel[], +): string { + return [...labels] + .sort((left, right) => left.name.localeCompare(right.name)) + .map((label) => getCreateLabelCommand(repo, label)) + .join('\n'); +}