Files
renovate/lib/modules/manager/xcodegen/extract.ts
T
Stefan Ceriu 0ff55d2d34 feat: add XcodeGen manager for Swift package dependencies in project.yml files (#41889)
* feat: add XcodeGen manager for Swift package dependencies in project.yml files

* chore(xcodegen): inline project.yml fixture

* fix(xcodegen): use semver instead of swift versioning

* fix(xcodegen): use glob syntax for manager file pattern

* tests(xcodegen): expect logger is called on invalid packages

* tests(xcodegen): better assert extracted package contents

* chore(xcodegen): simplify regex usage

* chore(xcodegen): log projects without dependencies

* chore(xcodegen): simplify dependency resolving and remove regular expressions in favor of the `detectPlatform` helper function

* chore(xcodegen): use `coerceString` on package versions to simplify testing and improve coverage

* chore(xcodegen): address PR review comments.

---------

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
2026-04-15 15:13:04 +00:00

177 lines
4.3 KiB
TypeScript

import { logger } from '../../../logger/index.ts';
import { detectPlatform } from '../../../util/common.ts';
import { parseGitUrl } from '../../../util/git/url.ts';
import { coerceString } from '../../../util/string.ts';
import { GitTagsDatasource } from '../../datasource/git-tags/index.ts';
import { GithubTagsDatasource } from '../../datasource/github-tags/index.ts';
import { GitlabTagsDatasource } from '../../datasource/gitlab-tags/index.ts';
import type { PackageDependency, PackageFileContent } from '../types.ts';
import type { XcodeGenSwiftPackage } from './schema.ts';
import { XcodeGenProjectFile } from './schema.ts';
function resolvePackageUrl(pkg: XcodeGenSwiftPackage): string | null {
if (pkg.url) {
return pkg.url;
}
if (pkg.github) {
return `https://github.com/${pkg.github}`;
}
return null;
}
function resolveGitDep(
url: string,
): Pick<PackageDependency, 'datasource' | 'packageName' | 'registryUrls'> {
const platform = detectPlatform(url);
switch (platform) {
case 'github': {
const { full_name, host, protocol } = parseGitUrl(url);
return {
datasource: GithubTagsDatasource.id,
packageName: full_name,
...(host !== 'github.com' && {
registryUrls: [`${protocol}://${host}`],
}),
};
}
case 'gitlab': {
const { full_name, host, protocol } = parseGitUrl(url);
return {
datasource: GitlabTagsDatasource.id,
packageName: full_name,
...(host !== 'gitlab.com' && {
registryUrls: [`${protocol}://${host}`],
}),
};
}
default:
return { datasource: GitTagsDatasource.id, packageName: url };
}
}
function resolveCurrentValue(
pkg: XcodeGenSwiftPackage,
): { currentValue: string; depType: string } | null {
if (pkg.from) {
return { currentValue: pkg.from, depType: 'from' };
}
if (pkg.majorVersion) {
return { currentValue: pkg.majorVersion, depType: 'majorVersion' };
}
if (pkg.minorVersion) {
return { currentValue: pkg.minorVersion, depType: 'minorVersion' };
}
if (pkg.exactVersion) {
return { currentValue: pkg.exactVersion, depType: 'exactVersion' };
}
if (pkg.version) {
return { currentValue: pkg.version, depType: 'version' };
}
return null;
}
export function extractPackageFile(
content: string,
packageFile?: string,
): PackageFileContent | null {
let parsed: XcodeGenProjectFile;
try {
parsed = XcodeGenProjectFile.parse(content);
} catch (err) {
logger.debug({ err, packageFile }, 'Parsing XcodeGen project YAML failed');
return null;
}
if (!parsed.packages) {
logger.trace({ packageFile }, 'XcodeGen project file has no packages');
return null;
}
const deps: PackageDependency[] = [];
for (const [depName, pkg] of Object.entries(parsed.packages)) {
// Skip local packages
if (pkg.path) {
deps.push({
depName,
skipReason: 'path-dependency',
});
continue;
}
const resolvedUrl = resolvePackageUrl(pkg);
if (!resolvedUrl) {
deps.push({
depName,
skipReason: 'invalid-url',
});
continue;
}
// Skip branch/revision references
if (pkg.branch) {
deps.push({
depName,
skipReason: 'unversioned-reference',
currentValue: pkg.branch,
});
continue;
}
if (pkg.revision) {
deps.push({
depName,
skipReason: 'unversioned-reference',
currentValue: pkg.revision,
});
continue;
}
// Skip minVersion/maxVersion range constraints
if (pkg.minVersion !== undefined || pkg.maxVersion !== undefined) {
deps.push({
depName,
skipReason: 'unsupported-version',
currentValue:
`${coerceString(pkg.minVersion)} - ${coerceString(pkg.maxVersion)}`.trim(),
});
continue;
}
const versionInfo = resolveCurrentValue(pkg);
if (!versionInfo) {
deps.push({
depName,
skipReason: 'unspecified-version',
});
continue;
}
const { datasource, packageName, registryUrls } =
resolveGitDep(resolvedUrl);
deps.push({
depName,
packageName,
datasource,
...(registryUrls && { registryUrls }),
currentValue: versionInfo.currentValue,
depType: versionInfo.depType,
});
}
if (deps.length === 0) {
return null;
}
return { deps };
}