Files
renovate/tools/docs/manager.ts
T
Jamie Tanna 2d239d2cc1 docs: explicitly document depTypes each manager supports (#42142)
* docs: explicitly document `depTypes` each manager supports

A long-standing question we receive is "what `depTypes` can I match
against in my project".

Although the best recommendation we usually have is "check the
`packageFiles with updates" log line (as a very key log line for users),
we can go one step further and try and create a stronger contract.

To do this, I've used Claude Sonnet 4.6 to work through the codebase,
determine areas that a `depType` is set in a manager (statically or
dynamically) and generate a structured, typed, means to document how
they're used.

Through a few iterations of the process, and attempts at human-written
documentation, we've settled on:

- prioritising autogenerated documentation (for humans to read) to note
  what `depTypes` are known, and what they are
- `knownDepTypes`, to describe any known, static, `depType`s and their
  human-readable meaning
  - with the `prettyDepType`, if used
- `supportsDynamicDepTypesNote`, to add a Markdown note to the generated
  documentation

While here, we also:

- make sure to note this in the "adding a new package manager"
  documentation
- remove any hand-written references to `depTypes` in each manager's
  documentation page
- correct the Gleam `depTypes`
- make sure to use the `knownDepTypes`'s `prettyDepType` definition if
  we know it, for a given `depType`

The descriptions have been generated by Claude Sonnet, with some
human-written tweaks where appropriate.

[0]: https://github.com/renovatebot/renovate/discussions/15176

Co-authored-by: Claude Sonnet 4.6 <jamie.tanna+claude-code@mend.io>

* fixup! docs: explicitly document `depTypes` each manager supports

* docs(manager): display `depTypes` documentation if none present

For consistency, and to make it more explicit.

---------

Co-authored-by: Claude Sonnet 4.6 <jamie.tanna+claude-code@mend.io>
2026-03-31 12:08:39 +00:00

241 lines
8.2 KiB
TypeScript

import { codeBlock } from 'common-tags';
import type { RenovateConfig } from '../../lib/config/types.ts';
import type { Category } from '../../lib/constants/index.ts';
import {
getCustomManagers,
isCustomManager,
} from '../../lib/modules/manager/custom/index.ts';
import { getManagers } from '../../lib/modules/manager/index.ts';
import { readFile, updateFile } from '../utils/index.ts';
import type { OpenItems } from './github-query-items.ts';
import { generateFeatureAndBugMarkdown } from './github-query-items.ts';
import {
formatUrls,
getDisplayName,
getModuleLink,
getNameWithUrl,
replaceContent,
} from './utils.ts';
const noCategoryID = 'no-category';
const noCategoryDisplayName = 'No Category';
function getTitle(
manager: string,
displayName: string,
isCustomMgr: boolean,
): string {
if (isCustomMgr) {
return `Custom Manager Support using ${displayName}`;
}
return `Automated Dependency Updates for ${displayName}`;
}
function getManagerLink(manager: string): string {
return getModuleLink(manager, `\`${manager}\``);
}
export const CategoryNames: Record<Category, string> = {
ansible: 'Ansible',
batect: 'Batect',
bazel: 'Bazel',
c: 'C and C++',
cd: 'Continuous Delivery',
ci: 'Continuous Integration',
custom: 'Custom Managers',
dart: 'Dart',
docker: 'Docker',
dotnet: '.NET',
elixir: 'Elixir',
golang: 'Go',
haskell: 'Haskell',
helm: 'Helm',
iac: 'Infrastructure as Code',
java: 'Java',
js: 'JavaScript',
kubernetes: 'Kubernetes',
node: 'Node.js',
perl: 'Perl',
php: 'PHP',
python: 'Python',
ruby: 'Ruby',
rust: 'Rust',
swift: 'Swift',
terraform: 'Terraform',
};
export async function generateManagers(
dist: string,
managerIssuesMap: OpenItems,
): Promise<void> {
const allManagers = [...getManagers(), ...getCustomManagers()];
const allCategories: Record<string, string[]> = {};
for (const [manager, definition] of allManagers) {
const { defaultConfig, supportedDatasources, urls } = definition;
const { managerFilePatterns } = defaultConfig as RenovateConfig;
const displayName = getDisplayName(manager, definition);
const isCustomMgr = isCustomManager(manager);
const categories = definition.categories ?? [noCategoryID];
for (const category of categories) {
allCategories[category] ??= [];
allCategories[category].push(manager);
}
let md = codeBlock`
---
title: ${getTitle(manager, displayName, isCustomMgr)}
sidebar_label: ${displayName}
edit_url: https://github.com/renovatebot/renovate/edit/main/lib/modules/manager/${
isCustomMgr ? `custom/${manager}` : manager
}/readme.md
---
`;
md += '\n\n';
md += '**Categories**: ';
if (categories.length) {
for (let i = 0; i < categories.length; i++) {
const category = categories[i];
if (i < categories.length - 1) {
md += `\`${category}\`, `;
} else {
md += `\`${category}\``;
}
}
}
md += '\n\n';
if (!isCustomMgr) {
const nameWithUrl = getNameWithUrl(manager, definition);
md += `Renovate supports updating ${nameWithUrl} dependencies.\n\n`;
if (defaultConfig.enabled === false) {
md += '## Enabling\n\n';
md += `${displayName} functionality is currently in beta testing, so you must opt-in to test it. To enable it, add a configuration like this to either your bot config or your \`renovate.json\`:\n\n`;
md += '```\n';
md += `{\n "${manager}": {\n "enabled": true\n }\n}`;
md += '\n```\n\n';
md +=
'If you find any bugs, please [create a new discussion first](https://github.com/renovatebot/renovate/discussions/new). If you find that it works well, then let us know too.\n\n';
}
md += '## File Matching\n\n';
if (
!Array.isArray(managerFilePatterns) ||
managerFilePatterns.length === 0
) {
md += `Because file names for \`${manager}\` cannot be easily determined automatically, Renovate will not attempt to match any \`${manager}\` files by default. `;
} else {
md += `By default, Renovate will check any files matching `;
if (managerFilePatterns.length === 1) {
md += `the following regular expression: \`${managerFilePatterns[0]}\`.\n\n`;
} else {
md += `any of the following regular expressions:\n\n`;
md += '```\n';
md += managerFilePatterns.join('\n');
md += '\n```\n\n';
}
}
md += `For details on how to extend a manager's \`managerFilePatterns\` value, please follow [this link](../index.md#file-matching).\n\n`;
md += '## Supported datasources\n\n';
const escapedDatasources = (supportedDatasources || [])
.map(
(datasource) =>
`[\`${datasource}\`](../../datasource/${datasource}/index.md)`,
)
.join(', ');
md += `This manager supports extracting the following datasources: ${escapedDatasources}.\n\n`;
md += formatUrls(urls);
md += '## Dependency types\n\n';
if (definition.knownDepTypes?.length) {
const hasPrettyDepType = definition.knownDepTypes.some(
(m) => m.prettyDepType,
);
md += 'This manager extracts the following `depType` values:\n\n';
if (hasPrettyDepType) {
md += '| `depType` | `prettyDepType` | Description |\n';
md += '|-----------|-----------------|-------------|\n';
for (const {
depType,
prettyDepType,
description,
} of definition.knownDepTypes) {
md += `| \`${depType}\` | ${prettyDepType ? `\`${prettyDepType}\`` : ''} | ${description} |\n`;
}
} else {
md += '| `depType` | Description |\n';
md += '|-----------|-------------|\n';
for (const { depType, description } of definition.knownDepTypes) {
md += `| \`${depType}\` | ${description} |\n`;
}
}
md += '\n';
}
if (definition.supportsDynamicDepTypesNote) {
md += definition.supportsDynamicDepTypesNote + '\n\n';
}
if (
(!definition.knownDepTypes || definition.knownDepTypes.length === 0) &&
definition.supportsDynamicDepTypesNote === undefined
) {
md += 'This manager has no documented `depType` values.\n';
}
md += '## Default config\n\n';
md += '```json\n';
md += JSON.stringify(definition.defaultConfig, null, 2) + '\n';
md += '```\n\n';
}
const managerReadmeContent = await readFile(
`lib/modules/manager/${
isCustomMgr ? `custom/${manager}` : manager
}/readme.md`,
);
if (!isCustomMgr && definition.supportsLockFileMaintenance) {
md += '\n## Lock File Maintenance\n\n';
md +=
'This manager supports [`lockFileMaintenance`](../../../configuration-options.md#lockfilemaintenance) for the following file(s):\n';
md += '\n';
for (const lockFile of definition.lockFileNames) {
md += `- \`${lockFile}\`\n`;
}
md += '\n';
}
if (!isCustomMgr) {
md += '\n## Additional Information\n\n';
}
md += managerReadmeContent;
md += generateFeatureAndBugMarkdown(managerIssuesMap, manager);
await updateFile(`${dist}/modules/manager/${manager}/index.md`, md);
}
// add noCategoryDisplayName as last option
const categories = Object.keys(allCategories).filter(
(category) => category !== noCategoryID,
);
categories.sort();
categories.push(noCategoryID);
let categoryText = '\n';
categoryText += '| Group | Category ID | Managers |\n';
categoryText += '| :-- | :-- | :-- |\n';
for (const category of categories) {
const managerLinkList = allCategories[category]
.map(getManagerLink)
.join(', ');
const displayName =
CategoryNames[category as Category] ?? noCategoryDisplayName;
const massagedCategory =
category === noCategoryID ? 'n/a' : `\`${category}\``;
categoryText += `| ${displayName} | ${massagedCategory} | ${managerLinkList} | \n`;
}
let indexContent = await readFile(`docs/usage/modules/manager/index.md`);
indexContent = replaceContent(indexContent, categoryText);
await updateFile(`${dist}/modules/manager/index.md`, indexContent);
}