Files
renovate/lib/modules/manager/docker-compose/extract.ts
Michael Kriese 312c380926 test(manager): cover the pre-commit warning switch and drop unreachable fallbacks (#46221)
* fix(util/http): make the legacy `headers` getter configurable

Every other property defined on `HttpError.prototype` in this file is
`configurable`, but `headers` was not. That makes the module fail with
`TypeError: Cannot redefine property: headers` whenever a test resets the
module registry and re-imports anything that pulls in `got.ts`.

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager/pre-commit): cover the suppress-warning env var

`defaultConfig.prBodyNotes` drops the unsupported-manager warning when
`RENOVATE_X_SUPPRESS_PRE_COMMIT_WARNING` is set. Since the value is
computed at module load, the new spec resets the module registry and
re-imports the manager with the variable stubbed.

The `istanbul ignore next` on that arm is dropped because the branch is
now exercised.

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* refactor(manager): remove unreachable fallbacks

Drop three defensive fallbacks that can never be taken:

- `docker-compose`: `config.services ?? config` always yields an object,
  so the `|| {}` guard behind it is dead
- `git-submodules`: `git.raw()` resolves to a string, so the `?? ''`
  never applies
- `maven`: the only site that builds a `MavenProp` always passes a
  `packageFile` string, so narrow the type and drop the `?? undefined`

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>

---------

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
2026-09-16 06:43:02 +00:00

85 lines
2.9 KiB
TypeScript

import { isString, isTruthy } from '@sindresorhus/is';
import { logger } from '../../../logger/index.ts';
import { coerceObject } from '../../../util/object.ts';
import { newlineRegex, regEx } from '../../../util/regex.ts';
import { parseSingleYaml } from '../../../util/yaml.ts';
import { getDep } from '../dockerfile/extract.ts';
import type { ExtractConfig, PackageFileContent } from '../types.ts';
import { DockerComposeFile } from './schema.ts';
class LineMapper {
private imageLines: { line: string; lineNumber: number; used: boolean }[];
constructor(content: string, filter: RegExp) {
this.imageLines = [...content.split(newlineRegex).entries()]
.filter((entry) => filter.test(entry[1]))
.map(([lineNumber, line]) => ({ lineNumber, line, used: false }));
}
pluckLineNumber(imageName: string | undefined): number | null {
const lineMeta = this.imageLines.find(
({ line, used }) => !used && imageName && line.includes(imageName),
);
// istanbul ignore if
if (!lineMeta) {
return null;
}
lineMeta.used = true; // unset plucked lines so duplicates are skipped
return lineMeta.lineNumber;
}
}
export function extractPackageFile(
content: string,
packageFile: string,
extractConfig: ExtractConfig,
): PackageFileContent | null {
logger.debug(`docker-compose.extractPackageFile(${packageFile})`);
let config: DockerComposeFile;
try {
config = parseSingleYaml(content, {
customSchema: DockerComposeFile,
removeTemplates: true,
});
} catch (err) {
logger.debug(
{ err, packageFile },
`Parsing Docker Compose config YAML failed`,
);
return null;
}
try {
const lineMapper = new LineMapper(content, regEx(/^\s*image:/));
// docker-compose v1 places the services at the top level,
// docker-compose v2+ places the services within a 'services' key
// since docker-compose spec version 1.27, the 'version' key has
// become optional and can no longer be used to differentiate
// between v1 and v2.
const services = config.services ?? config;
const extensions = coerceObject(config.extensions);
// Image name/tags for services are only eligible for update if they don't
// use variables and if the image is not built locally
const deps = Object.values(services)
.concat(Object.values(extensions))
.filter((service) => isString(service?.image) && !service?.build)
.map((service) => {
const dep = getDep(service.image, true, extractConfig.registryAliases);
const lineNumber = lineMapper.pluckLineNumber(service.image);
// istanbul ignore if
if (!lineNumber) {
return null;
}
return dep;
})
.filter(isTruthy);
logger.trace({ deps }, 'Docker Compose image');
return { deps };
} catch (err) /* istanbul ignore next */ {
logger.debug({ packageFile, err }, 'Error extracting Docker Compose file');
return null;
}
}