import { isPlainObject, isString } from '@sindresorhus/is'; import { regEx } from '../../../util/regex.ts'; import { isHttpUrl } from '../../../util/url.ts'; import { BitbucketTagsDatasource } from '../../datasource/bitbucket-tags/index.ts'; import { CLOJARS_REPO } from '../../datasource/clojure/common.ts'; import { ClojureDatasource } from '../../datasource/clojure/index.ts'; import { GitRefsDatasource } from '../../datasource/git-refs/index.ts'; import { GithubTagsDatasource } from '../../datasource/github-tags/index.ts'; import { GitlabTagsDatasource } from '../../datasource/gitlab-tags/index.ts'; import { MAVEN_REPO } from '../../datasource/maven/common.ts'; import type { PackageDependency, PackageFileContent } from '../types.ts'; import { parseDepsEdnFile } from './parser.ts'; import type { ParsedEdnData, ParsedEdnMetadata, ParsedEdnRecord, } from './types.ts'; const dependencyRegex = regEx( /^(?[a-zA-Z][-_a-zA-Z0-9]*(?:\.[a-zA-Z0-9][-_a-zA-Z0-9]*)*)(?:\/(?[a-zA-Z][-_a-zA-Z0-9]*(?:\.[a-zA-Z0-9][-_a-zA-Z0-9]*)*))?$/, ); function getPackageName(depName: string): string | null { const matchGroups = dependencyRegex.exec(depName)?.groups; if (matchGroups) { const groupId = matchGroups.groupId; const artifactId = matchGroups.artifactId ? matchGroups.artifactId : groupId; return `${groupId}:${artifactId}`; } return null; } const githubDependencyRegex = regEx( /^(?:com|io)\.github\.(?[^/]+\/[^/]+)$/, ); const gitlabDependencyRegex = regEx( /^(?:com|io)\.gitlab\.(?[^/]+\/[^/]+)$/, ); const bitbucketDependencyRegex = regEx( /^(?:org|io)\.bitbucket\.(?[^/]+\/[^/]+)$/, ); function resolveGitPackageFromEdnKey( dep: PackageDependency, key: string, ): void { if (dep.datasource) { return; } const githubDependencyGroups = githubDependencyRegex.exec(key)?.groups; if (githubDependencyGroups?.packageName) { dep.datasource = GithubTagsDatasource.id; dep.packageName = githubDependencyGroups.packageName; return; } const gitlabDependencyGroups = gitlabDependencyRegex.exec(key)?.groups; if (gitlabDependencyGroups?.packageName) { dep.datasource = GitlabTagsDatasource.id; dep.packageName = gitlabDependencyGroups.packageName; return; } const bitbucketDependencyGroups = bitbucketDependencyRegex.exec(key)?.groups; if (bitbucketDependencyGroups?.packageName) { dep.datasource = BitbucketTagsDatasource.id; dep.packageName = bitbucketDependencyGroups.packageName; return; } } const githubUrlRegex = regEx( /^(?:https:\/\/|git@)github\.com[/:](?[^/]+\/[^/]+?)(?:\.git)?$/, ); const gitlabUrlRegex = regEx( /^(?:https:\/\/|git@)gitlab\.com[/:](?[^/]+\/[^/]+?)(?:\.git)?$/, ); const bitbucketUrlRegex = regEx( /^(?:https:\/\/|git@)bitbucket\.org[/:](?[^/]+\/[^/]+?)(?:\.git)?$/, ); function resolveGitPackageFromEdnVal( dep: PackageDependency, val: ParsedEdnRecord, ): void { const gitUrl = val['git/url']; if (!isString(gitUrl)) { return; } const githubMatchGroups = githubUrlRegex.exec(gitUrl)?.groups; if (githubMatchGroups) { dep.datasource = GithubTagsDatasource.id; dep.packageName = githubMatchGroups.packageName; dep.sourceUrl = `https://github.com/${dep.packageName}`; return; } const gitlabMatchGroups = gitlabUrlRegex.exec(gitUrl)?.groups; const bitbucketMatchGroups = bitbucketUrlRegex.exec(gitUrl)?.groups; if (gitlabMatchGroups) { dep.datasource = GitlabTagsDatasource.id; dep.packageName = gitlabMatchGroups.packageName; dep.sourceUrl = `https://gitlab.com/${dep.packageName}`; return; } if (bitbucketMatchGroups) { dep.datasource = GitlabTagsDatasource.id; dep.packageName = bitbucketMatchGroups.packageName; dep.sourceUrl = `https://bitbucket.org/${dep.packageName}`; return; } dep.datasource = GitRefsDatasource.id; dep.packageName = gitUrl; if (isHttpUrl(gitUrl)) { dep.sourceUrl = gitUrl.replace(regEx(/\.git$/), ''); } } function extractDependency( key: string, val: ParsedEdnData, metadata: ParsedEdnMetadata, mavenRegistries: string[], depType?: string, ): PackageDependency | null { if (!isPlainObject(val)) { return null; } const packageName = getPackageName(key); if (!packageName) { return null; } const depName = key; const dep: PackageDependency = { depName, packageName, currentValue: null, ...metadata.get(val), }; if (depType) { dep.depType = depType; } const mvnVersion = val['mvn/version']; if (isString(mvnVersion)) { dep.datasource = ClojureDatasource.id; dep.currentValue = mvnVersion; dep.packageName = packageName.replace('/', ':'); dep.registryUrls = [...mavenRegistries]; return dep; } resolveGitPackageFromEdnVal(dep, val); resolveGitPackageFromEdnKey(dep, key); if (dep.datasource) { const gitTag = val['git/tag']; if (isString(gitTag)) { dep.currentValue = gitTag; } const gitSha = val['git/sha'] ?? val.sha; if (isString(gitSha)) { dep.currentDigest = gitSha; dep.currentDigestShort = gitSha.slice(0, 7); } return dep; } return null; } function extractSection( section: ParsedEdnData, metadata: ParsedEdnMetadata, mavenRegistries: string[], depType?: string, ): PackageDependency[] { const deps: PackageDependency[] = []; if (isPlainObject(section)) { for (const [key, val] of Object.entries(section)) { const dep = extractDependency( key, val, metadata, mavenRegistries, depType, ); if (dep) { deps.push(dep); } } } return deps; } export function extractPackageFile(content: string): PackageFileContent | null { const parsed = parseDepsEdnFile(content); if (!parsed) { return null; } const { data, metadata } = parsed; const deps: PackageDependency[] = []; // See: https://clojure.org/reference/deps_and_cli#_modifying_the_default_repositories const registryMap: Record = { clojars: CLOJARS_REPO, central: MAVEN_REPO, }; const mavenRepos = data['mvn/repos']; if (isPlainObject(mavenRepos)) { for (const [repoName, repoSpec] of Object.entries(mavenRepos)) { if (isString(repoName)) { if (isPlainObject(repoSpec) && isString(repoSpec.url)) { registryMap[repoName] = repoSpec.url; } else if (isString(repoSpec) && repoSpec === 'nil') { delete registryMap[repoName]; } } } } const mavenRegistries: string[] = Object.values(registryMap); deps.push(...extractSection(data.deps, metadata, mavenRegistries)); const aliases = data.aliases; if (isPlainObject(aliases)) { for (const [depType, aliasSection] of Object.entries(aliases)) { if (isPlainObject(aliasSection)) { deps.push( ...extractSection( aliasSection['extra-deps'], metadata, mavenRegistries, depType, ), ); deps.push( ...extractSection( aliasSection['override-deps'], metadata, mavenRegistries, depType, ), ); } } } return { deps }; }