renovate 44.86.0 → 44.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/data/replacements.js +11 -0
  2. package/dist/modules/datasource/conan/index.js +2 -0
  3. package/dist/modules/datasource/conan/index.js.map +1 -1
  4. package/dist/modules/datasource/cpan/index.js +1 -0
  5. package/dist/modules/datasource/cpan/index.js.map +1 -1
  6. package/dist/modules/datasource/dart/index.js +1 -0
  7. package/dist/modules/datasource/dart/index.js.map +1 -1
  8. package/dist/modules/datasource/deb/index.js +3 -0
  9. package/dist/modules/datasource/deb/index.js.map +1 -1
  10. package/dist/modules/datasource/deno/index.js +1 -0
  11. package/dist/modules/datasource/deno/index.js.map +1 -1
  12. package/dist/modules/datasource/docker/dockerhub-cache.js +1 -0
  13. package/dist/modules/datasource/docker/dockerhub-cache.js.map +1 -1
  14. package/dist/modules/datasource/docker/index.js +5 -0
  15. package/dist/modules/datasource/docker/index.js.map +1 -1
  16. package/dist/modules/datasource/go/releases-goproxy.js +4 -2
  17. package/dist/modules/datasource/go/releases-goproxy.js.map +1 -1
  18. package/dist/modules/datasource/index.js +3 -0
  19. package/dist/modules/datasource/index.js.map +1 -1
  20. package/dist/modules/datasource/npm/npmrc.js.map +1 -1
  21. package/dist/modules/datasource/nuget/v3.js +1 -0
  22. package/dist/modules/datasource/nuget/v3.js.map +1 -1
  23. package/dist/modules/datasource/pod/index.js +1 -0
  24. package/dist/modules/datasource/pod/index.js.map +1 -1
  25. package/dist/modules/datasource/repology/index.js +1 -0
  26. package/dist/modules/datasource/repology/index.js.map +1 -1
  27. package/dist/modules/datasource/rubygems/metadata-cache.js +2 -0
  28. package/dist/modules/datasource/rubygems/metadata-cache.js.map +1 -1
  29. package/dist/modules/datasource/rubygems/versions-endpoint-cache.js +6 -1
  30. package/dist/modules/datasource/rubygems/versions-endpoint-cache.js.map +1 -1
  31. package/dist/modules/datasource/sbt-package/index.js +3 -0
  32. package/dist/modules/datasource/sbt-package/index.js.map +1 -1
  33. package/dist/modules/datasource/sbt-plugin/index.js +3 -0
  34. package/dist/modules/datasource/sbt-plugin/index.js.map +1 -1
  35. package/dist/workers/repository/process/lookup/filter.js +1 -1
  36. package/dist/workers/repository/process/lookup/filter.js.map +1 -1
  37. package/dist/workers/repository/process/lookup/generate.js +1 -7
  38. package/dist/workers/repository/process/lookup/generate.js.map +1 -1
  39. package/dist/workers/repository/process/lookup/index.js +2 -2
  40. package/dist/workers/repository/process/lookup/index.js.map +1 -1
  41. package/dist/workers/repository/process/lookup/rollback.js.map +1 -1
  42. package/package.json +1 -1
  43. package/renovate-schema.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"npmrc.js","names":["hostRules.add"],"sources":["../../../../lib/modules/datasource/npm/npmrc.ts"],"sourcesContent":["import { isNonEmptyString, isString } from '@sindresorhus/is';\nimport ini from 'ini';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport type { PackageRule } from '../../../config/types.ts';\nimport { logger } from '../../../logger/index.ts';\nimport type { HostRule } from '../../../types/index.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport * as hostRules from '../../../util/host-rules.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { fromBase64 } from '../../../util/string.ts';\nimport { ensureTrailingSlash, isHttpUrl } from '../../../util/url.ts';\nimport { defaultRegistryUrl } from './common.ts';\nimport type { NpmrcRules } from './types.ts';\n\nlet npmrc: Record<string, any> = {};\nlet npmrcRaw = '';\nlet packageRules: PackageRule[] = [];\n\nfunction envReplace(value: any, env = getEnv()): any {\n /* v8 ignore next -- TODO: add test */\n if (!isString(value)) {\n return value;\n }\n\n const ENV_EXPR = regEx(/(?<esc>\\\\*)\\$\\{(?<envVarName>[^}]+)\\}/g);\n\n return value.replace(ENV_EXPR, (match, _esc, envVarName) => {\n if (env[envVarName] === undefined) {\n logger.warn({ match }, 'Failed to replace env in config');\n throw new Error('env-replace');\n }\n return env[envVarName];\n });\n}\n\nexport function getMatchHostFromNpmrcHost(input: string): string {\n if (input.startsWith('//')) {\n const matchHost = input.replace('//', '');\n if (matchHost.includes('/')) {\n return `https://${matchHost}`;\n }\n return matchHost;\n }\n return input;\n}\n\nexport function convertNpmrcToRules(npmrc: Record<string, any>): NpmrcRules {\n const rules: NpmrcRules = {\n hostRules: [],\n packageRules: [],\n };\n // Generate hostRules\n const hostType = 'npm';\n const hosts: Record<string, HostRule> = {};\n for (const [key, value] of Object.entries(npmrc)) {\n if (!isNonEmptyString(value)) {\n continue;\n }\n const keyParts = key.split(':');\n const keyType = keyParts.pop();\n let matchHost = '';\n if (keyParts.length) {\n matchHost = getMatchHostFromNpmrcHost(keyParts.join(':'));\n }\n const rule: HostRule = hosts[matchHost] || {};\n if (keyType === '_authToken' || keyType === '_auth') {\n rule.token = value;\n if (keyType === '_auth') {\n rule.authType = 'Basic';\n }\n } else if (keyType === 'username') {\n rule.username = value;\n } else if (keyType === '_password') {\n rule.password = fromBase64(value);\n } else {\n continue; // don't add the rule\n }\n hosts[matchHost] = rule;\n }\n for (const [matchHost, rule] of Object.entries(hosts)) {\n const hostRule = { ...rule, hostType };\n if (matchHost) {\n hostRule.matchHost = matchHost;\n }\n rules.hostRules?.push(hostRule);\n }\n // Generate packageRules\n const matchDatasources = ['npm'];\n const { registry } = npmrc;\n // packageRules order matters, so look for a default registry first\n if (isNonEmptyString(registry)) {\n if (isHttpUrl(registry)) {\n // Default registry\n rules.packageRules?.push({\n matchDatasources,\n registryUrls: [registry],\n });\n } else {\n logger.warn({ registry }, 'Invalid npmrc registry= URL');\n }\n }\n // Now look for scoped registries\n for (const [key, value] of Object.entries(npmrc)) {\n if (!isNonEmptyString(value)) {\n continue;\n }\n const keyParts = key.split(':');\n const keyType = keyParts.pop();\n if (keyType === 'registry' && keyParts.length && isNonEmptyString(value)) {\n const scope = keyParts.join(':');\n if (isHttpUrl(value)) {\n rules.packageRules?.push({\n matchDatasources,\n matchPackageNames: [`${scope}/**`],\n registryUrls: [value],\n });\n } else {\n logger.warn({ scope, registry: value }, 'Invalid npmrc registry= URL');\n }\n }\n }\n return rules;\n}\n\nexport function setNpmrc(input?: string): void {\n if (input) {\n if (input === npmrcRaw) {\n return;\n }\n const existingNpmrc = npmrc;\n npmrcRaw = input;\n logger.debug('Setting npmrc');\n npmrc = ini.parse(input.replace(regEx(/\\\\n/g), '\\n'));\n const exposeAllEnv = GlobalConfig.get('exposeAllEnv');\n for (const [key, val] of Object.entries(npmrc)) {\n if (\n !exposeAllEnv &&\n key.endsWith('registry') &&\n isString(val) &&\n val.includes('localhost')\n ) {\n logger.debug(\n { key, val },\n 'Detected localhost registry - rejecting npmrc file',\n );\n npmrc = existingNpmrc;\n return;\n }\n }\n if (exposeAllEnv) {\n for (const key of Object.keys(npmrc)) {\n npmrc[key] = envReplace(npmrc[key]);\n }\n }\n const npmrcRules = convertNpmrcToRules(npmrc);\n if (npmrcRules.hostRules?.length) {\n npmrcRules.hostRules.forEach((hostRule) => hostRules.add(hostRule));\n }\n packageRules = npmrcRules.packageRules;\n } else if (npmrc) {\n logger.debug('Resetting npmrc');\n npmrc = {};\n npmrcRaw = '';\n packageRules = [];\n }\n}\n\nexport function resolveRegistryUrl(packageName: string): string {\n let registryUrl = defaultRegistryUrl;\n for (const rule of packageRules) {\n const { matchPackageNames, registryUrls } = rule;\n if (\n !matchPackageNames ||\n packageName.startsWith(matchPackageNames[0].replace(regEx(/\\*\\*$/), ''))\n ) {\n // TODO: fix types #22198\n registryUrl = registryUrls![0];\n }\n }\n return registryUrl;\n}\n\nexport function resolvePackageUrl(\n registryUrl: string,\n packageName: string,\n): string {\n return new URL(\n encodeURIComponent(packageName).replace(regEx(/^%40/), '@'),\n ensureTrailingSlash(registryUrl),\n ).href;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"npmrc.js","names":["hostRules.add"],"sources":["../../../../lib/modules/datasource/npm/npmrc.ts"],"sourcesContent":["import { isNonEmptyString, isString } from '@sindresorhus/is';\nimport ini from 'ini';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport type { PackageRule } from '../../../config/types.ts';\nimport { logger } from '../../../logger/index.ts';\nimport type { HostRule } from '../../../types/index.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport * as hostRules from '../../../util/host-rules.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { fromBase64 } from '../../../util/string.ts';\nimport { ensureTrailingSlash, isHttpUrl } from '../../../util/url.ts';\nimport { defaultRegistryUrl } from './common.ts';\nimport type { NpmrcRules } from './types.ts';\n\nlet npmrc: Record<string, any> = {};\nlet npmrcRaw = '';\nlet packageRules: PackageRule[] = [];\n\nfunction envReplace(value: any, env = getEnv()): any {\n /* v8 ignore next -- TODO: add test */\n if (!isString(value)) {\n return value;\n }\n\n const ENV_EXPR = regEx(/(?<esc>\\\\*)\\$\\{(?<envVarName>[^}]+)\\}/g);\n\n return value.replace(ENV_EXPR, (match, _esc, envVarName) => {\n if (env[envVarName] === undefined) {\n logger.warn({ match }, 'Failed to replace env in config');\n throw new Error('env-replace');\n }\n return env[envVarName];\n });\n}\n\nexport function getMatchHostFromNpmrcHost(input: string): string {\n if (input.startsWith('//')) {\n const matchHost = input.replace('//', '');\n if (matchHost.includes('/')) {\n return `https://${matchHost}`;\n }\n return matchHost;\n }\n return input;\n}\n\nexport function convertNpmrcToRules(npmrc: Record<string, any>): NpmrcRules {\n const rules: NpmrcRules = {\n hostRules: [],\n packageRules: [],\n };\n // Generate hostRules\n const hostType = 'npm';\n const hosts: Record<string, HostRule> = {};\n for (const [key, value] of Object.entries(npmrc)) {\n if (!isNonEmptyString(value)) {\n continue;\n }\n const keyParts = key.split(':');\n const keyType = keyParts.pop();\n let matchHost = '';\n if (keyParts.length) {\n matchHost = getMatchHostFromNpmrcHost(keyParts.join(':'));\n }\n const rule: HostRule = hosts[matchHost] || {};\n if (keyType === '_authToken' || keyType === '_auth') {\n rule.token = value;\n if (keyType === '_auth') {\n rule.authType = 'Basic';\n }\n } else if (keyType === 'username') {\n rule.username = value;\n } else if (keyType === '_password') {\n rule.password = fromBase64(value);\n } else {\n continue; // don't add the rule\n }\n hosts[matchHost] = rule;\n }\n for (const [matchHost, rule] of Object.entries(hosts)) {\n const hostRule = { ...rule, hostType };\n if (matchHost) {\n hostRule.matchHost = matchHost;\n }\n rules.hostRules?.push(hostRule);\n }\n // Generate packageRules\n const matchDatasources = ['npm'];\n const { registry } = npmrc;\n // packageRules order matters, so look for a default registry first\n if (isNonEmptyString(registry)) {\n if (isHttpUrl(registry)) {\n // Default registry\n rules.packageRules?.push({\n matchDatasources,\n registryUrls: [registry],\n });\n } else {\n logger.warn({ registry }, 'Invalid npmrc registry= URL');\n }\n }\n // Now look for scoped registries\n for (const [key, value] of Object.entries(npmrc)) {\n if (!isNonEmptyString(value)) {\n continue;\n }\n const keyParts = key.split(':');\n const keyType = keyParts.pop();\n if (keyType === 'registry' && keyParts.length && isNonEmptyString(value)) {\n const scope = keyParts.join(':');\n if (isHttpUrl(value)) {\n rules.packageRules?.push({\n matchDatasources,\n matchPackageNames: [`${scope}/**`],\n registryUrls: [value],\n });\n } else {\n logger.warn({ scope, registry: value }, 'Invalid npmrc registry= URL');\n }\n }\n }\n return rules;\n}\n\nexport function setNpmrc(input?: string): void {\n if (input) {\n if (input === npmrcRaw) {\n return;\n }\n const existingNpmrc = npmrc;\n npmrcRaw = input;\n logger.debug('Setting npmrc');\n npmrc = ini.parse(input.replace(regEx(/\\\\n/g), '\\n'));\n const exposeAllEnv = GlobalConfig.get('exposeAllEnv');\n for (const [key, val] of Object.entries(npmrc)) {\n if (\n !exposeAllEnv &&\n key.endsWith('registry') &&\n isString(val) &&\n val.includes('localhost')\n ) {\n logger.debug(\n { key, val },\n 'Detected localhost registry - rejecting npmrc file',\n );\n npmrc = existingNpmrc;\n return;\n }\n }\n if (exposeAllEnv) {\n for (const key of Object.keys(npmrc)) {\n npmrc[key] = envReplace(npmrc[key]);\n }\n }\n const npmrcRules = convertNpmrcToRules(npmrc);\n if (npmrcRules.hostRules?.length) {\n npmrcRules.hostRules.forEach((hostRule) => hostRules.add(hostRule));\n }\n packageRules = npmrcRules.packageRules;\n // NOTE: `npmrc` is an object, so it is never falsy. A coverage-ignore hint\n // cannot suppress the implicit else on an `else if`.\n } else if (npmrc) {\n logger.debug('Resetting npmrc');\n npmrc = {};\n npmrcRaw = '';\n packageRules = [];\n }\n}\n\nexport function resolveRegistryUrl(packageName: string): string {\n let registryUrl = defaultRegistryUrl;\n for (const rule of packageRules) {\n const { matchPackageNames, registryUrls } = rule;\n if (\n !matchPackageNames ||\n packageName.startsWith(matchPackageNames[0].replace(regEx(/\\*\\*$/), ''))\n ) {\n // TODO: fix types #22198\n registryUrl = registryUrls![0];\n }\n }\n return registryUrl;\n}\n\nexport function resolvePackageUrl(\n registryUrl: string,\n packageName: string,\n): string {\n return new URL(\n encodeURIComponent(packageName).replace(regEx(/^%40/), '@'),\n ensureTrailingSlash(registryUrl),\n ).href;\n}\n"],"mappings":"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"}
@@ -32,6 +32,7 @@ var NugetV3Api = class NugetV3Api {
32
32
  try {
33
33
  const responseCacheKey = url;
34
34
  servicesIndexRaw = await get(NugetV3Api.cacheNamespace, responseCacheKey);
35
+ // v8 ignore else -- needs a primed package cache
35
36
  if (!servicesIndexRaw) {
36
37
  servicesIndexRaw = (await http.getJson(url, { cacheProvider: memCacheProvider }, ServicesIndexRaw)).body;
37
38
  await set(NugetV3Api.cacheNamespace, responseCacheKey, servicesIndexRaw, 4320);
@@ -1 +1 @@
1
- {"version":3,"file":"v3.js","names":["packageCache.get","packageCache.set","p.all","versioning","HttpError","fs.createCacheWriteStream","fs.pipeline","fs.readCacheFile","fs.rmCache"],"sources":["../../../../lib/modules/datasource/nuget/v3.ts"],"sourcesContent":["import { isNonEmptyString } from '@sindresorhus/is';\nimport AdmZip from 'adm-zip';\nimport semver from 'semver';\nimport upath from 'upath';\nimport { XmlDocument } from 'xmldoc';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { coerceArray } from '../../../util/array.ts';\nimport * as packageCache from '../../../util/cache/package/index.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport * as fs from '../../../util/fs/index.ts';\nimport { ensureCacheDir } from '../../../util/fs/index.ts';\nimport { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport * as p from '../../../util/promises.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport { ensureTrailingSlash } from '../../../util/url.ts';\nimport { api as versioning } from '../../versioning/nuget/index.ts';\nimport type { Release, ReleaseResult } from '../types.ts';\nimport { massageUrl, removeBuildMeta, sortNugetVersions } from './common.ts';\nimport type { CatalogEntry } from './schema.ts';\nimport {\n CatalogPage,\n PackageRegistration,\n ServicesIndexRaw,\n} from './schema.ts';\n\nexport class NugetV3Api {\n static readonly cacheNamespace = 'datasource-nuget-v3';\n\n async getResourceUrl(\n http: Http,\n url: string,\n resourceType = 'RegistrationsBaseUrl',\n ): Promise<string | null> {\n // https://learn.microsoft.com/nuget/api/service-index\n const resultCacheKey = `${url}:${resourceType}`;\n const cachedResult = await packageCache.get<string>(\n NugetV3Api.cacheNamespace,\n resultCacheKey,\n );\n\n /* v8 ignore next -- TODO: add test */\n if (cachedResult) {\n return cachedResult;\n }\n let servicesIndexRaw: ServicesIndexRaw | undefined;\n try {\n const responseCacheKey = url;\n servicesIndexRaw = await packageCache.get<ServicesIndexRaw>(\n NugetV3Api.cacheNamespace,\n responseCacheKey,\n );\n if (!servicesIndexRaw) {\n servicesIndexRaw = (\n await http.getJson(\n url,\n { cacheProvider: memCacheProvider },\n ServicesIndexRaw,\n )\n ).body;\n await packageCache.set(\n NugetV3Api.cacheNamespace,\n responseCacheKey,\n servicesIndexRaw,\n 3 * 24 * 60,\n );\n }\n\n const services = servicesIndexRaw.resources\n .map(({ '@id': serviceId, '@type': t }) => ({\n serviceId,\n type: t?.split('/')?.shift(),\n version: t?.split('/')?.pop(),\n }))\n .filter(\n ({ type, version }) => type === resourceType && semver.valid(version),\n )\n .sort((x, y) =>\n x.version && y.version\n ? semver.compare(x.version, y.version)\n : /* istanbul ignore next: hard to test */ 0,\n );\n\n if (services.length === 0) {\n await packageCache.set(\n NugetV3Api.cacheNamespace,\n resultCacheKey,\n null,\n 60,\n );\n logger.debug(\n { url, servicesIndexRaw },\n `no ${resourceType} services found`,\n );\n return null;\n }\n\n const { serviceId, version } = services.pop()!;\n\n // istanbul ignore if\n if (\n resourceType === 'RegistrationsBaseUrl' &&\n version &&\n !version.startsWith('3.0.0-') &&\n !semver.satisfies(version, '^3.0.0')\n ) {\n logger.warn(\n { url, version },\n `Nuget: Unknown version returned. Only v3 is supported`,\n );\n }\n\n await packageCache.set(\n NugetV3Api.cacheNamespace,\n resultCacheKey,\n serviceId,\n 60,\n );\n return serviceId;\n } catch (err) {\n // istanbul ignore if: not easy testable with nock\n if (err instanceof ExternalHostError) {\n throw err;\n }\n logger.debug(\n { err, url, servicesIndexRaw },\n `nuget registry failure: can't get ${resourceType}`,\n );\n return null;\n }\n }\n\n async getCatalogEntry(\n http: Http,\n catalogPage: CatalogPage,\n ): Promise<CatalogEntry[]> {\n let items = catalogPage.items;\n if (!items) {\n const url = catalogPage['@id'];\n if (!url) {\n return [];\n }\n const catalogPageFull = await http.getJson(url, CatalogPage);\n items = catalogPageFull.body.items;\n }\n return coerceArray(items).map(({ catalogEntry }) => catalogEntry);\n }\n\n async getReleases(\n http: Http,\n registryUrl: string,\n feedUrl: string,\n pkgName: string,\n ): Promise<ReleaseResult | null> {\n const baseUrl = feedUrl.replace(regEx(/\\/*$/), '');\n const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`;\n const packageRegistration = await http.getJson(url, PackageRegistration);\n const catalogPages = packageRegistration.body.items;\n const catalogPagesQueue = catalogPages.map(\n (page) => (): Promise<CatalogEntry[]> => this.getCatalogEntry(http, page),\n );\n const catalogEntries = (await p.all(catalogPagesQueue))\n .flat()\n .sort((a, b) => sortNugetVersions(a.version, b.version));\n\n let homepage: string | null = null;\n let latestStable: string | null = null;\n let nupkgUrl: string | null = null;\n const releases = catalogEntries.map(\n ({\n version,\n published,\n projectUrl,\n listed,\n packageContent,\n deprecation,\n }) => {\n const release: Release = { version: removeBuildMeta(version) };\n const releaseTimestamp = asTimestamp(published);\n if (releaseTimestamp) {\n release.releaseTimestamp = releaseTimestamp;\n }\n if (\n versioning.isValid(version) &&\n versioning.isStable(version) &&\n listed\n ) {\n latestStable = removeBuildMeta(version);\n homepage = projectUrl ? massageUrl(projectUrl) : homepage;\n nupkgUrl = massageUrl(packageContent);\n }\n\n if (listed === false || deprecation) {\n release.isDeprecated = true;\n }\n return release;\n },\n );\n\n if (!releases.length) {\n return null;\n }\n\n // istanbul ignore next: only happens when no stable version exists\n if (latestStable === null && catalogPages.length) {\n const last = catalogEntries.pop()!;\n latestStable = removeBuildMeta(last.version);\n homepage ??= last.projectUrl ?? null;\n nupkgUrl ??= massageUrl(last.packageContent);\n }\n\n const dep: ReleaseResult = {\n releases,\n };\n\n if (releases.every((release) => release.isDeprecated === true)) {\n dep.deprecationMessage = this.getDeprecationMessage(pkgName);\n }\n\n try {\n const packageBaseAddress = await this.getResourceUrl(\n http,\n registryUrl,\n 'PackageBaseAddress',\n );\n let shouldTryNupkg = false;\n if (isNonEmptyString(packageBaseAddress)) {\n const nuspecUrl = `${ensureTrailingSlash(\n packageBaseAddress,\n )}${pkgName.toLowerCase()}/${\n latestStable\n }/${pkgName.toLowerCase()}.nuspec`;\n try {\n const metaresult = await http.getText(nuspecUrl, {\n cacheProvider: memCacheProvider,\n });\n const nuspec = new XmlDocument(metaresult.body);\n const releaseNotes = nuspec.valueWithPath('metadata.releaseNotes');\n if (releaseNotes) {\n dep.changelogContent = releaseNotes;\n }\n const sourceUrl = nuspec.valueWithPath('metadata.repository@url');\n if (sourceUrl) {\n dep.sourceUrl = massageUrl(sourceUrl);\n }\n } catch (err) {\n /* v8 ignore else -- not easy testable with nock */\n if (err instanceof HttpError && err.response?.statusCode === 404) {\n shouldTryNupkg = true;\n logger.debug(\n { registryUrl, pkgName, pkgVersion: latestStable },\n `package manifest (.nuspec) not found`,\n );\n } else {\n throw err;\n }\n }\n } else {\n shouldTryNupkg = true;\n }\n\n if (!dep.sourceUrl && shouldTryNupkg && nupkgUrl) {\n const sourceUrl = await this.getSourceUrlFromNupkg(\n http,\n registryUrl,\n pkgName,\n latestStable,\n nupkgUrl,\n );\n if (sourceUrl) {\n dep.sourceUrl = massageUrl(sourceUrl);\n logger.debug(`Determined sourceUrl ${sourceUrl} from ${nupkgUrl}`);\n }\n }\n } catch (err) {\n // istanbul ignore if: not easy testable with nock\n if (err instanceof ExternalHostError) {\n throw err;\n }\n // ignore / silence 404. Seen on proget, if remote connector is used and package is not yet cached\n if (err instanceof HttpError && err.response?.statusCode === 404) {\n logger.debug(\n { registryUrl, pkgName, pkgVersion: latestStable },\n `package manifest (.nuspec) not found`,\n );\n } else {\n logger.debug(\n { err, registryUrl, pkgName, pkgVersion: latestStable },\n `Cannot obtain sourceUrl`,\n );\n }\n }\n\n if (homepage) {\n // only assign if not assigned\n dep.sourceUrl ??= homepage;\n dep.homepage ??= homepage;\n }\n\n return dep;\n }\n\n private async _getSourceUrlFromNupkg(\n http: Http,\n _registryUrl: string,\n packageName: string,\n packageVersion: string | null,\n nupkgUrl: string,\n ): Promise<string | null> {\n /* v8 ignore if -- specs enable RENOVATE_X_NUGET_DOWNLOAD_NUPKGS, so the disabled path is unexercised */\n if (!getEnv().RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) {\n logger.once.debug('RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set');\n return null;\n }\n const cacheDir = await ensureCacheDir('nuget');\n const nupkgFile = upath.join(\n cacheDir,\n `${packageName}.${packageVersion}.nupkg`,\n );\n const nupkgContentsDir = upath.join(\n cacheDir,\n `${packageName}.${packageVersion}`,\n );\n const readStream = http.stream(nupkgUrl);\n try {\n const writeStream = fs.createCacheWriteStream(nupkgFile);\n await fs.pipeline(readStream, writeStream);\n const zip = new AdmZip(nupkgFile);\n zip.extractAllTo(nupkgContentsDir);\n const nuspecFile = upath.join(nupkgContentsDir, `${packageName}.nuspec`);\n const nuspec = new XmlDocument(\n await fs.readCacheFile(nuspecFile, 'utf8'),\n );\n return nuspec.valueWithPath('metadata.repository@url') ?? null;\n } finally {\n await fs.rmCache(nupkgFile);\n await fs.rmCache(nupkgContentsDir);\n }\n }\n\n getSourceUrlFromNupkg(\n http: Http,\n registryUrl: string,\n packageName: string,\n packageVersion: string | null,\n nupkgUrl: string,\n ): Promise<string | null> {\n return withCache(\n {\n namespace: NugetV3Api.cacheNamespace,\n key: `source-url:${registryUrl}:${packageName}`,\n ttlMinutes: 10080, // 1 week\n },\n () =>\n this._getSourceUrlFromNupkg(\n http,\n registryUrl,\n packageName,\n packageVersion,\n nupkgUrl,\n ),\n );\n }\n\n getDeprecationMessage(packageName: string): string {\n return `The package \\`${packageName}\\` is deprecated.`;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"v3.js","names":["packageCache.get","packageCache.set","p.all","versioning","HttpError","fs.createCacheWriteStream","fs.pipeline","fs.readCacheFile","fs.rmCache"],"sources":["../../../../lib/modules/datasource/nuget/v3.ts"],"sourcesContent":["import { isNonEmptyString } from '@sindresorhus/is';\nimport AdmZip from 'adm-zip';\nimport semver from 'semver';\nimport upath from 'upath';\nimport { XmlDocument } from 'xmldoc';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { coerceArray } from '../../../util/array.ts';\nimport * as packageCache from '../../../util/cache/package/index.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport * as fs from '../../../util/fs/index.ts';\nimport { ensureCacheDir } from '../../../util/fs/index.ts';\nimport { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport * as p from '../../../util/promises.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport { ensureTrailingSlash } from '../../../util/url.ts';\nimport { api as versioning } from '../../versioning/nuget/index.ts';\nimport type { Release, ReleaseResult } from '../types.ts';\nimport { massageUrl, removeBuildMeta, sortNugetVersions } from './common.ts';\nimport type { CatalogEntry } from './schema.ts';\nimport {\n CatalogPage,\n PackageRegistration,\n ServicesIndexRaw,\n} from './schema.ts';\n\nexport class NugetV3Api {\n static readonly cacheNamespace = 'datasource-nuget-v3';\n\n async getResourceUrl(\n http: Http,\n url: string,\n resourceType = 'RegistrationsBaseUrl',\n ): Promise<string | null> {\n // https://learn.microsoft.com/nuget/api/service-index\n const resultCacheKey = `${url}:${resourceType}`;\n const cachedResult = await packageCache.get<string>(\n NugetV3Api.cacheNamespace,\n resultCacheKey,\n );\n\n /* v8 ignore next -- TODO: add test */\n if (cachedResult) {\n return cachedResult;\n }\n let servicesIndexRaw: ServicesIndexRaw | undefined;\n try {\n const responseCacheKey = url;\n servicesIndexRaw = await packageCache.get<ServicesIndexRaw>(\n NugetV3Api.cacheNamespace,\n responseCacheKey,\n );\n // v8 ignore else -- needs a primed package cache\n if (!servicesIndexRaw) {\n servicesIndexRaw = (\n await http.getJson(\n url,\n { cacheProvider: memCacheProvider },\n ServicesIndexRaw,\n )\n ).body;\n await packageCache.set(\n NugetV3Api.cacheNamespace,\n responseCacheKey,\n servicesIndexRaw,\n 3 * 24 * 60,\n );\n }\n\n const services = servicesIndexRaw.resources\n .map(({ '@id': serviceId, '@type': t }) => ({\n serviceId,\n type: t?.split('/')?.shift(),\n version: t?.split('/')?.pop(),\n }))\n .filter(\n ({ type, version }) => type === resourceType && semver.valid(version),\n )\n .sort((x, y) =>\n x.version && y.version\n ? semver.compare(x.version, y.version)\n : /* istanbul ignore next: hard to test */ 0,\n );\n\n if (services.length === 0) {\n await packageCache.set(\n NugetV3Api.cacheNamespace,\n resultCacheKey,\n null,\n 60,\n );\n logger.debug(\n { url, servicesIndexRaw },\n `no ${resourceType} services found`,\n );\n return null;\n }\n\n const { serviceId, version } = services.pop()!;\n\n // istanbul ignore if\n if (\n resourceType === 'RegistrationsBaseUrl' &&\n version &&\n !version.startsWith('3.0.0-') &&\n !semver.satisfies(version, '^3.0.0')\n ) {\n logger.warn(\n { url, version },\n `Nuget: Unknown version returned. Only v3 is supported`,\n );\n }\n\n await packageCache.set(\n NugetV3Api.cacheNamespace,\n resultCacheKey,\n serviceId,\n 60,\n );\n return serviceId;\n } catch (err) {\n // istanbul ignore if: not easy testable with nock\n if (err instanceof ExternalHostError) {\n throw err;\n }\n logger.debug(\n { err, url, servicesIndexRaw },\n `nuget registry failure: can't get ${resourceType}`,\n );\n return null;\n }\n }\n\n async getCatalogEntry(\n http: Http,\n catalogPage: CatalogPage,\n ): Promise<CatalogEntry[]> {\n let items = catalogPage.items;\n if (!items) {\n const url = catalogPage['@id'];\n if (!url) {\n return [];\n }\n const catalogPageFull = await http.getJson(url, CatalogPage);\n items = catalogPageFull.body.items;\n }\n return coerceArray(items).map(({ catalogEntry }) => catalogEntry);\n }\n\n async getReleases(\n http: Http,\n registryUrl: string,\n feedUrl: string,\n pkgName: string,\n ): Promise<ReleaseResult | null> {\n const baseUrl = feedUrl.replace(regEx(/\\/*$/), '');\n const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`;\n const packageRegistration = await http.getJson(url, PackageRegistration);\n const catalogPages = packageRegistration.body.items;\n const catalogPagesQueue = catalogPages.map(\n (page) => (): Promise<CatalogEntry[]> => this.getCatalogEntry(http, page),\n );\n const catalogEntries = (await p.all(catalogPagesQueue))\n .flat()\n .sort((a, b) => sortNugetVersions(a.version, b.version));\n\n let homepage: string | null = null;\n let latestStable: string | null = null;\n let nupkgUrl: string | null = null;\n const releases = catalogEntries.map(\n ({\n version,\n published,\n projectUrl,\n listed,\n packageContent,\n deprecation,\n }) => {\n const release: Release = { version: removeBuildMeta(version) };\n const releaseTimestamp = asTimestamp(published);\n if (releaseTimestamp) {\n release.releaseTimestamp = releaseTimestamp;\n }\n if (\n versioning.isValid(version) &&\n versioning.isStable(version) &&\n listed\n ) {\n latestStable = removeBuildMeta(version);\n homepage = projectUrl ? massageUrl(projectUrl) : homepage;\n nupkgUrl = massageUrl(packageContent);\n }\n\n if (listed === false || deprecation) {\n release.isDeprecated = true;\n }\n return release;\n },\n );\n\n if (!releases.length) {\n return null;\n }\n\n // istanbul ignore next: only happens when no stable version exists\n if (latestStable === null && catalogPages.length) {\n const last = catalogEntries.pop()!;\n latestStable = removeBuildMeta(last.version);\n homepage ??= last.projectUrl ?? null;\n nupkgUrl ??= massageUrl(last.packageContent);\n }\n\n const dep: ReleaseResult = {\n releases,\n };\n\n if (releases.every((release) => release.isDeprecated === true)) {\n dep.deprecationMessage = this.getDeprecationMessage(pkgName);\n }\n\n try {\n const packageBaseAddress = await this.getResourceUrl(\n http,\n registryUrl,\n 'PackageBaseAddress',\n );\n let shouldTryNupkg = false;\n if (isNonEmptyString(packageBaseAddress)) {\n const nuspecUrl = `${ensureTrailingSlash(\n packageBaseAddress,\n )}${pkgName.toLowerCase()}/${\n latestStable\n }/${pkgName.toLowerCase()}.nuspec`;\n try {\n const metaresult = await http.getText(nuspecUrl, {\n cacheProvider: memCacheProvider,\n });\n const nuspec = new XmlDocument(metaresult.body);\n const releaseNotes = nuspec.valueWithPath('metadata.releaseNotes');\n if (releaseNotes) {\n dep.changelogContent = releaseNotes;\n }\n const sourceUrl = nuspec.valueWithPath('metadata.repository@url');\n if (sourceUrl) {\n dep.sourceUrl = massageUrl(sourceUrl);\n }\n } catch (err) {\n /* v8 ignore else -- not easy testable with nock */\n if (err instanceof HttpError && err.response?.statusCode === 404) {\n shouldTryNupkg = true;\n logger.debug(\n { registryUrl, pkgName, pkgVersion: latestStable },\n `package manifest (.nuspec) not found`,\n );\n } else {\n throw err;\n }\n }\n } else {\n shouldTryNupkg = true;\n }\n\n if (!dep.sourceUrl && shouldTryNupkg && nupkgUrl) {\n const sourceUrl = await this.getSourceUrlFromNupkg(\n http,\n registryUrl,\n pkgName,\n latestStable,\n nupkgUrl,\n );\n if (sourceUrl) {\n dep.sourceUrl = massageUrl(sourceUrl);\n logger.debug(`Determined sourceUrl ${sourceUrl} from ${nupkgUrl}`);\n }\n }\n } catch (err) {\n // istanbul ignore if: not easy testable with nock\n if (err instanceof ExternalHostError) {\n throw err;\n }\n // ignore / silence 404. Seen on proget, if remote connector is used and package is not yet cached\n if (err instanceof HttpError && err.response?.statusCode === 404) {\n logger.debug(\n { registryUrl, pkgName, pkgVersion: latestStable },\n `package manifest (.nuspec) not found`,\n );\n } else {\n logger.debug(\n { err, registryUrl, pkgName, pkgVersion: latestStable },\n `Cannot obtain sourceUrl`,\n );\n }\n }\n\n if (homepage) {\n // only assign if not assigned\n dep.sourceUrl ??= homepage;\n dep.homepage ??= homepage;\n }\n\n return dep;\n }\n\n private async _getSourceUrlFromNupkg(\n http: Http,\n _registryUrl: string,\n packageName: string,\n packageVersion: string | null,\n nupkgUrl: string,\n ): Promise<string | null> {\n /* v8 ignore if -- specs enable RENOVATE_X_NUGET_DOWNLOAD_NUPKGS, so the disabled path is unexercised */\n if (!getEnv().RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) {\n logger.once.debug('RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set');\n return null;\n }\n const cacheDir = await ensureCacheDir('nuget');\n const nupkgFile = upath.join(\n cacheDir,\n `${packageName}.${packageVersion}.nupkg`,\n );\n const nupkgContentsDir = upath.join(\n cacheDir,\n `${packageName}.${packageVersion}`,\n );\n const readStream = http.stream(nupkgUrl);\n try {\n const writeStream = fs.createCacheWriteStream(nupkgFile);\n await fs.pipeline(readStream, writeStream);\n const zip = new AdmZip(nupkgFile);\n zip.extractAllTo(nupkgContentsDir);\n const nuspecFile = upath.join(nupkgContentsDir, `${packageName}.nuspec`);\n const nuspec = new XmlDocument(\n await fs.readCacheFile(nuspecFile, 'utf8'),\n );\n return nuspec.valueWithPath('metadata.repository@url') ?? null;\n } finally {\n await fs.rmCache(nupkgFile);\n await fs.rmCache(nupkgContentsDir);\n }\n }\n\n getSourceUrlFromNupkg(\n http: Http,\n registryUrl: string,\n packageName: string,\n packageVersion: string | null,\n nupkgUrl: string,\n ): Promise<string | null> {\n return withCache(\n {\n namespace: NugetV3Api.cacheNamespace,\n key: `source-url:${registryUrl}:${packageName}`,\n ttlMinutes: 10080, // 1 week\n },\n () =>\n this._getSourceUrlFromNupkg(\n http,\n registryUrl,\n packageName,\n packageVersion,\n nupkgUrl,\n ),\n );\n }\n\n getDeprecationMessage(packageName: string): string {\n return `The package \\`${packageName}\\` is deprecated.`;\n }\n}\n"],"mappings":"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"}
@@ -71,6 +71,7 @@ var PodDatasource = class PodDatasource extends Datasource {
71
71
  async requestGithub(url, packageName) {
72
72
  try {
73
73
  const resp = await this.githubHttp.getJsonUnchecked(url);
74
+ // v8 ignore else -- an empty json body throws before it gets here
74
75
  if (resp?.body) return resp.body;
75
76
  } catch (err) {
76
77
  handleError(packageName, err);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../lib/modules/datasource/pod/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport {\n HOST_BLOCKED,\n HOST_DISABLED,\n} from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { GithubHttp } from '../../../util/http/github.ts';\nimport type { HttpError } from '../../../util/http/index.ts';\nimport { refusedHostMessage } from '../../../util/http/util.ts';\nimport { newlineRegex, regEx } from '../../../util/regex.ts';\nimport { Datasource } from '../datasource.ts';\nimport { massageGithubUrl } from '../metadata.ts';\nimport type { GetReleasesConfig, ReleaseResult } from '../types.ts';\n\ntype URLFormatOptions =\n | 'withShardWithSpec'\n | 'withShardWithoutSpec'\n | 'withSpecsWithoutShard'\n | 'withoutSpecsWithoutShard';\n\nfunction shardParts(packageName: string): string[] {\n return crypto\n .createHash('md5')\n .update(packageName)\n .digest('hex')\n .slice(0, 3)\n .split('');\n}\n\nconst githubRegex = regEx(\n /(?<hostURL>^https:\\/\\/[a-zA-Z0-9-.]+)\\/(?<account>[^/]+)\\/(?<repo>[^/]+?)(?:\\.git|\\/.*)?$/,\n);\n\nfunction releasesGithubUrl(\n packageName: string,\n opts: {\n hostURL: string;\n account: string;\n repo: string;\n useShard: boolean;\n useSpecs: boolean;\n },\n): string {\n const { hostURL, account, repo, useShard, useSpecs } = opts;\n const prefix =\n hostURL && hostURL !== 'https://github.com'\n ? `${hostURL}/api/v3/repos`\n : 'https://api.github.com/repos';\n const shard = shardParts(packageName).join('/');\n // `Specs` in the pods repo URL is a new requirement for legacy support also allow pod repo URL without `Specs`\n const packageNamePath = useSpecs ? `Specs/${packageName}` : packageName;\n const shardPath = useSpecs\n ? `Specs/${shard}/${packageName}`\n : `${shard}/${packageName}`;\n const suffix = useShard ? shardPath : packageNamePath;\n return `${prefix}/${account}/${repo}/contents/${suffix}`;\n}\n\nfunction handleError(packageName: string, err: HttpError): void {\n const errorData = { packageName, err };\n\n const statusCode = err.response?.statusCode ?? 0;\n if (statusCode === 429 || (statusCode >= 500 && statusCode < 600)) {\n logger.warn({ packageName, err }, `CocoaPods registry failure`);\n throw new ExternalHostError(err);\n }\n\n if (statusCode === 401) {\n logger.debug(errorData, 'Authorization error');\n } else if (statusCode === 404) {\n logger.debug(errorData, 'Package lookup error');\n } else if ([HOST_BLOCKED, HOST_DISABLED].includes(err.message)) {\n logger.trace(errorData, refusedHostMessage(err));\n } else {\n logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');\n }\n}\n\nfunction isDefaultRepo(url: string): boolean {\n const match = githubRegex.exec(url);\n if (match?.groups) {\n const { account, repo } = match.groups;\n return (\n account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs'\n ); // https://github.com/CocoaPods/Specs.git\n }\n return false;\n}\n\nfunction releasesCDNUrl(packageName: string, registryUrl: string): string {\n const shard = shardParts(packageName).join('_');\n return `${registryUrl}/all_pods_versions_${shard}.txt`;\n}\n\nexport class PodDatasource extends Datasource {\n static readonly id = 'pod';\n\n override readonly defaultRegistryUrls = ['https://cdn.cocoapods.org'];\n\n override readonly registryStrategy = 'hunt';\n\n githubHttp: GithubHttp;\n\n constructor() {\n super(PodDatasource.id);\n this.githubHttp = new GithubHttp(PodDatasource.id);\n }\n\n private async requestCDN(\n url: string,\n packageName: string,\n ): Promise<string | null> {\n try {\n const resp = await this.http.getText(url);\n if (resp?.body) {\n return resp.body;\n }\n } catch (err) {\n handleError(packageName, err);\n }\n\n return null;\n }\n\n private async requestGithub<T = unknown>(\n url: string,\n packageName: string,\n ): Promise<T | null> {\n try {\n const resp = await this.githubHttp.getJsonUnchecked<T>(url);\n if (resp?.body) {\n return resp.body;\n }\n } catch (err) {\n handleError(packageName, err);\n }\n\n return null;\n }\n\n private async getReleasesFromGithub(\n packageName: string,\n opts: { hostURL: string; account: string; repo: string },\n useShard = true,\n useSpecs = true,\n urlFormatOptions: URLFormatOptions = 'withShardWithSpec',\n ): Promise<ReleaseResult | null> {\n const url = releasesGithubUrl(packageName, { ...opts, useShard, useSpecs });\n const resp = await this.requestGithub<{ name: string }[]>(url, packageName);\n if (resp) {\n const releases = resp.map(({ name }) => ({ version: name }));\n return { releases };\n }\n\n // support different url formats\n switch (urlFormatOptions) {\n case 'withShardWithSpec':\n return this.getReleasesFromGithub(\n packageName,\n opts,\n true,\n false,\n 'withShardWithoutSpec',\n );\n case 'withShardWithoutSpec':\n return this.getReleasesFromGithub(\n packageName,\n opts,\n false,\n true,\n 'withSpecsWithoutShard',\n );\n case 'withSpecsWithoutShard':\n return this.getReleasesFromGithub(\n packageName,\n opts,\n false,\n false,\n 'withoutSpecsWithoutShard',\n );\n case 'withoutSpecsWithoutShard':\n default:\n return null;\n }\n }\n\n private async getReleasesFromCDN(\n packageName: string,\n registryUrl: string,\n ): Promise<ReleaseResult | null> {\n const url = releasesCDNUrl(packageName, registryUrl);\n const resp = await this.requestCDN(url, packageName);\n if (resp) {\n const lines = resp.split(newlineRegex);\n for (const line of lines) {\n const [name, ...versions] = line.split('/');\n if (name === packageName.replace(regEx(/\\/.*$/), '')) {\n const releases = versions.map((version) => ({ version }));\n return { releases };\n }\n }\n }\n return null;\n }\n\n private async _getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n /* v8 ignore next -- should never happen */\n if (!registryUrl) {\n return null;\n }\n\n const podName = packageName.replace(regEx(/\\/.*$/), '');\n let baseUrl = registryUrl.replace(regEx(/\\/+$/), '');\n // In order to not abuse github API limits, query CDN instead\n if (isDefaultRepo(baseUrl)) {\n [baseUrl] = this.defaultRegistryUrls;\n }\n\n let result: ReleaseResult | null = null;\n const match = githubRegex.exec(baseUrl);\n // We would ideally have a reliable way to differentiate between\n // a CDN URL and a Github URL, but we'll start with detecting Artifactory\n if (match?.groups && !baseUrl.includes('/api/pods/')) {\n baseUrl = massageGithubUrl(baseUrl);\n const { hostURL, account, repo } = match.groups;\n const opts = { hostURL, account, repo };\n result = await this.getReleasesFromGithub(podName, opts);\n } else {\n result = await this.getReleasesFromCDN(podName, baseUrl);\n }\n\n return result;\n }\n\n getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {\n return withCache(\n {\n ttlMinutes: 30,\n namespace: `datasource-${PodDatasource.id}`,\n // TODO: types (#22198)\n key: `${config.registryUrl}:${config.packageName}`,\n fallback: true,\n },\n () => this._getReleases(config),\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../lib/modules/datasource/pod/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport {\n HOST_BLOCKED,\n HOST_DISABLED,\n} from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { GithubHttp } from '../../../util/http/github.ts';\nimport type { HttpError } from '../../../util/http/index.ts';\nimport { refusedHostMessage } from '../../../util/http/util.ts';\nimport { newlineRegex, regEx } from '../../../util/regex.ts';\nimport { Datasource } from '../datasource.ts';\nimport { massageGithubUrl } from '../metadata.ts';\nimport type { GetReleasesConfig, ReleaseResult } from '../types.ts';\n\ntype URLFormatOptions =\n | 'withShardWithSpec'\n | 'withShardWithoutSpec'\n | 'withSpecsWithoutShard'\n | 'withoutSpecsWithoutShard';\n\nfunction shardParts(packageName: string): string[] {\n return crypto\n .createHash('md5')\n .update(packageName)\n .digest('hex')\n .slice(0, 3)\n .split('');\n}\n\nconst githubRegex = regEx(\n /(?<hostURL>^https:\\/\\/[a-zA-Z0-9-.]+)\\/(?<account>[^/]+)\\/(?<repo>[^/]+?)(?:\\.git|\\/.*)?$/,\n);\n\nfunction releasesGithubUrl(\n packageName: string,\n opts: {\n hostURL: string;\n account: string;\n repo: string;\n useShard: boolean;\n useSpecs: boolean;\n },\n): string {\n const { hostURL, account, repo, useShard, useSpecs } = opts;\n const prefix =\n hostURL && hostURL !== 'https://github.com'\n ? `${hostURL}/api/v3/repos`\n : 'https://api.github.com/repos';\n const shard = shardParts(packageName).join('/');\n // `Specs` in the pods repo URL is a new requirement for legacy support also allow pod repo URL without `Specs`\n const packageNamePath = useSpecs ? `Specs/${packageName}` : packageName;\n const shardPath = useSpecs\n ? `Specs/${shard}/${packageName}`\n : `${shard}/${packageName}`;\n const suffix = useShard ? shardPath : packageNamePath;\n return `${prefix}/${account}/${repo}/contents/${suffix}`;\n}\n\nfunction handleError(packageName: string, err: HttpError): void {\n const errorData = { packageName, err };\n\n const statusCode = err.response?.statusCode ?? 0;\n if (statusCode === 429 || (statusCode >= 500 && statusCode < 600)) {\n logger.warn({ packageName, err }, `CocoaPods registry failure`);\n throw new ExternalHostError(err);\n }\n\n if (statusCode === 401) {\n logger.debug(errorData, 'Authorization error');\n } else if (statusCode === 404) {\n logger.debug(errorData, 'Package lookup error');\n } else if ([HOST_BLOCKED, HOST_DISABLED].includes(err.message)) {\n logger.trace(errorData, refusedHostMessage(err));\n } else {\n logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');\n }\n}\n\nfunction isDefaultRepo(url: string): boolean {\n const match = githubRegex.exec(url);\n if (match?.groups) {\n const { account, repo } = match.groups;\n return (\n account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs'\n ); // https://github.com/CocoaPods/Specs.git\n }\n return false;\n}\n\nfunction releasesCDNUrl(packageName: string, registryUrl: string): string {\n const shard = shardParts(packageName).join('_');\n return `${registryUrl}/all_pods_versions_${shard}.txt`;\n}\n\nexport class PodDatasource extends Datasource {\n static readonly id = 'pod';\n\n override readonly defaultRegistryUrls = ['https://cdn.cocoapods.org'];\n\n override readonly registryStrategy = 'hunt';\n\n githubHttp: GithubHttp;\n\n constructor() {\n super(PodDatasource.id);\n this.githubHttp = new GithubHttp(PodDatasource.id);\n }\n\n private async requestCDN(\n url: string,\n packageName: string,\n ): Promise<string | null> {\n try {\n const resp = await this.http.getText(url);\n if (resp?.body) {\n return resp.body;\n }\n } catch (err) {\n handleError(packageName, err);\n }\n\n return null;\n }\n\n private async requestGithub<T = unknown>(\n url: string,\n packageName: string,\n ): Promise<T | null> {\n try {\n const resp = await this.githubHttp.getJsonUnchecked<T>(url);\n // v8 ignore else -- an empty json body throws before it gets here\n if (resp?.body) {\n return resp.body;\n }\n } catch (err) {\n handleError(packageName, err);\n }\n\n return null;\n }\n\n private async getReleasesFromGithub(\n packageName: string,\n opts: { hostURL: string; account: string; repo: string },\n useShard = true,\n useSpecs = true,\n urlFormatOptions: URLFormatOptions = 'withShardWithSpec',\n ): Promise<ReleaseResult | null> {\n const url = releasesGithubUrl(packageName, { ...opts, useShard, useSpecs });\n const resp = await this.requestGithub<{ name: string }[]>(url, packageName);\n if (resp) {\n const releases = resp.map(({ name }) => ({ version: name }));\n return { releases };\n }\n\n // support different url formats\n switch (urlFormatOptions) {\n case 'withShardWithSpec':\n return this.getReleasesFromGithub(\n packageName,\n opts,\n true,\n false,\n 'withShardWithoutSpec',\n );\n case 'withShardWithoutSpec':\n return this.getReleasesFromGithub(\n packageName,\n opts,\n false,\n true,\n 'withSpecsWithoutShard',\n );\n case 'withSpecsWithoutShard':\n return this.getReleasesFromGithub(\n packageName,\n opts,\n false,\n false,\n 'withoutSpecsWithoutShard',\n );\n case 'withoutSpecsWithoutShard':\n default:\n return null;\n }\n }\n\n private async getReleasesFromCDN(\n packageName: string,\n registryUrl: string,\n ): Promise<ReleaseResult | null> {\n const url = releasesCDNUrl(packageName, registryUrl);\n const resp = await this.requestCDN(url, packageName);\n if (resp) {\n const lines = resp.split(newlineRegex);\n for (const line of lines) {\n const [name, ...versions] = line.split('/');\n if (name === packageName.replace(regEx(/\\/.*$/), '')) {\n const releases = versions.map((version) => ({ version }));\n return { releases };\n }\n }\n }\n return null;\n }\n\n private async _getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n /* v8 ignore next -- should never happen */\n if (!registryUrl) {\n return null;\n }\n\n const podName = packageName.replace(regEx(/\\/.*$/), '');\n let baseUrl = registryUrl.replace(regEx(/\\/+$/), '');\n // In order to not abuse github API limits, query CDN instead\n if (isDefaultRepo(baseUrl)) {\n [baseUrl] = this.defaultRegistryUrls;\n }\n\n let result: ReleaseResult | null = null;\n const match = githubRegex.exec(baseUrl);\n // We would ideally have a reliable way to differentiate between\n // a CDN URL and a Github URL, but we'll start with detecting Artifactory\n if (match?.groups && !baseUrl.includes('/api/pods/')) {\n baseUrl = massageGithubUrl(baseUrl);\n const { hostURL, account, repo } = match.groups;\n const opts = { hostURL, account, repo };\n result = await this.getReleasesFromGithub(podName, opts);\n } else {\n result = await this.getReleasesFromCDN(podName, baseUrl);\n }\n\n return result;\n }\n\n getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {\n return withCache(\n {\n ttlMinutes: 30,\n namespace: `datasource-${PodDatasource.id}`,\n // TODO: types (#22198)\n key: `${config.registryUrl}:${config.packageName}`,\n fallback: true,\n },\n () => this._getReleases(config),\n );\n }\n}\n"],"mappings":"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"}
@@ -52,6 +52,7 @@ var RepologyDatasource = class RepologyDatasource extends Datasource {
52
52
  try {
53
53
  for (const pkgType of packageTypes) {
54
54
  response = await this.queryPackagesViaResolver(registryUrl, repoName, pkgName, pkgType);
55
+ // v8 ignore else -- the resolver either returns a body or throws
55
56
  if (response) {
56
57
  const pkg = findPackageInResponse(response, repoName, pkgName, [pkgType]);
57
58
  if (isNonEmptyArray(pkg)) return pkg;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../lib/modules/datasource/repology/index.ts"],"sourcesContent":["import { isNonEmptyArray } from '@sindresorhus/is';\nimport {\n HOST_BLOCKED,\n HOST_DISABLED,\n} from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { refusedHostMessage } from '../../../util/http/util.ts';\nimport { getQueryString, joinUrlParts } from '../../../util/url.ts';\nimport { Datasource } from '../datasource.ts';\nimport type { GetReleasesConfig, ReleaseResult } from '../types.ts';\nimport { type RepologyPackage, RepologyPackages } from './schema.ts';\nimport type { RepologyPackageType } from './types.ts';\n\nconst packageTypes: RepologyPackageType[] = ['binname', 'srcname'];\n\nfunction findPackageInResponse(\n response: RepologyPackage[],\n repoName: string,\n pkgName: string,\n types: RepologyPackageType[],\n): RepologyPackage[] | null {\n const repoPackages = response.filter((pkg) => pkg.repo === repoName);\n\n if (repoPackages.length === 0) {\n // no packages associated with repoName\n return null;\n }\n\n if (repoPackages.length === 1) {\n // repo contains exactly one package, so we can return them safely\n return repoPackages;\n }\n\n // In some cases Repology bundles multiple packages into a single project, which might result in ambiguous results.\n // We need to do additional filtering by matching allowed package types passed as params with package description.\n // Remaining packages are the one we are looking for\n const packagesWithType = repoPackages.filter((pkg) => {\n for (const pkgType of types) {\n if (pkg[pkgType] && pkg[pkgType] === pkgName) {\n return true;\n }\n }\n return false;\n });\n\n return packagesWithType.length > 0 ? packagesWithType : null;\n}\n\nexport class RepologyDatasource extends Datasource {\n static readonly id = 'repology';\n\n override readonly defaultRegistryUrls = ['https://repology.org/'];\n\n override readonly registryStrategy = 'hunt';\n\n constructor() {\n super(RepologyDatasource.id);\n }\n\n private async queryPackages(url: string): Promise<RepologyPackage[]> {\n try {\n const res = await this.http.getJson(url, RepologyPackages);\n return res.body;\n } catch (err) {\n if (err.statusCode === 404) {\n // Return an array here because the api does not return proper http codes\n // and instead of an 404 error an empty array with code 200 is returned\n // When querying the resolver 404 is thrown if package could not be resolved\n // and 403 if the repo is not supported\n // 403 is handled later because in this case we are trying the API\n return [];\n }\n\n throw err;\n }\n }\n\n private async queryPackagesViaResolver(\n registryUrl: string,\n repoName: string,\n packageName: string,\n packageType: RepologyPackageType,\n ): Promise<RepologyPackage[]> {\n const query = getQueryString({\n repo: repoName,\n name_type: packageType,\n target_page: 'api_v1_project',\n noautoresolve: 'on',\n name: packageName,\n });\n\n // Retrieve list of packages by looking up Repology project\n const packages = await this.queryPackages(\n joinUrlParts(registryUrl, `tools/project-by?${query}`),\n );\n\n return packages;\n }\n\n private async queryPackagesViaAPI(\n registryUrl: string,\n packageName: string,\n ): Promise<RepologyPackage[]> {\n // Directly query the package via the API. This will only work if `packageName` has the\n // same name as the repology project\n const packages = await this.queryPackages(\n joinUrlParts(registryUrl, `api/v1/project`, packageName),\n );\n\n return packages;\n }\n\n private async _queryPackage(\n registryUrl: string,\n repoName: string,\n pkgName: string,\n ): Promise<RepologyPackage[] | undefined> {\n let response: RepologyPackage[];\n // Try getting the packages from tools/project-by first for type binname and\n // afterwards for srcname. This needs to be done first, because some packages\n // resolve to repology projects which have a different name than the package\n // e.g. `pulseaudio-utils` resolves to project `pulseaudio`, BUT there is also\n // a project called `pulseaudio-utils` but it does not contain the package we\n // are looking for.\n try {\n for (const pkgType of packageTypes) {\n response = await this.queryPackagesViaResolver(\n registryUrl,\n repoName,\n pkgName,\n pkgType,\n );\n\n if (response) {\n const pkg = findPackageInResponse(response, repoName, pkgName, [\n pkgType,\n ]);\n if (isNonEmptyArray(pkg)) {\n // exit immediately if package found\n return pkg;\n }\n }\n }\n } catch (err) {\n if (err.statusCode === 403) {\n logger.debug(\n { repoName, pkgName },\n 'Repology does not support tools/project-by lookups for repository. Will try direct API access now',\n );\n\n // If the repository is not supported in tools/project-by we try directly accessing the\n // API. This will support all repositories but requires that the project name is equal to the\n // package name. This won't be always the case but for a good portion we might be able to resolve\n // the package this way.\n response = await this.queryPackagesViaAPI(registryUrl, pkgName);\n const pkg = findPackageInResponse(\n response,\n repoName,\n pkgName,\n packageTypes,\n );\n if (isNonEmptyArray(pkg)) {\n // exit immediately if package found\n return pkg;\n }\n } else if (err.statusCode === 300) {\n logger.warn(\n { repoName, pkgName },\n 'Ambiguous redirection from package name to project name in Repology. Skipping this package',\n );\n return undefined;\n }\n\n throw err;\n }\n\n logger.debug(\n { repoName, pkgName },\n 'Repository or package not found on Repology',\n );\n\n return undefined;\n }\n\n queryPackage(\n registryUrl: string,\n repoName: string,\n pkgName: string,\n ): Promise<RepologyPackage[] | undefined> {\n return withCache(\n {\n ttlMinutes: 60,\n namespace: `datasource-${RepologyDatasource.id}`,\n key: joinUrlParts(registryUrl, repoName, pkgName),\n },\n () => this._queryPackage(registryUrl, repoName, pkgName),\n );\n }\n\n async getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n /* v8 ignore next -- should never happen */\n if (!registryUrl) {\n return null;\n }\n // Ensure lookup name contains both repository and package\n const [repoName, pkgName] = packageName.split('/', 2);\n if (!repoName || !pkgName) {\n throw new ExternalHostError(\n new Error(\n 'Repology lookup name must contain repository and package separated by slash (<repo>/<pkg>)',\n ),\n );\n }\n\n logger.trace(`repology.getReleases(${repoName}, ${pkgName})`);\n try {\n // Try to retrieve (cached) package information from Repology\n const pkg = await this.queryPackage(registryUrl, repoName, pkgName);\n if (!pkg) {\n return null;\n }\n\n // Always prefer origversion if available, otherwise default to version\n // This is required as source packages usually have no origversion\n const releases = pkg.map((item) => ({\n version: item.origversion ?? item.version,\n }));\n return { releases };\n } catch (err) {\n if ([HOST_BLOCKED, HOST_DISABLED].includes(err.message)) {\n logger.trace({ packageName, err }, refusedHostMessage(err));\n } else {\n logger.once.warn(\n { packageName, err },\n 'Repology lookup failed with unexpected error',\n );\n }\n\n this.handleGenericErrors(err);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../lib/modules/datasource/repology/index.ts"],"sourcesContent":["import { isNonEmptyArray } from '@sindresorhus/is';\nimport {\n HOST_BLOCKED,\n HOST_DISABLED,\n} from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { refusedHostMessage } from '../../../util/http/util.ts';\nimport { getQueryString, joinUrlParts } from '../../../util/url.ts';\nimport { Datasource } from '../datasource.ts';\nimport type { GetReleasesConfig, ReleaseResult } from '../types.ts';\nimport { type RepologyPackage, RepologyPackages } from './schema.ts';\nimport type { RepologyPackageType } from './types.ts';\n\nconst packageTypes: RepologyPackageType[] = ['binname', 'srcname'];\n\nfunction findPackageInResponse(\n response: RepologyPackage[],\n repoName: string,\n pkgName: string,\n types: RepologyPackageType[],\n): RepologyPackage[] | null {\n const repoPackages = response.filter((pkg) => pkg.repo === repoName);\n\n if (repoPackages.length === 0) {\n // no packages associated with repoName\n return null;\n }\n\n if (repoPackages.length === 1) {\n // repo contains exactly one package, so we can return them safely\n return repoPackages;\n }\n\n // In some cases Repology bundles multiple packages into a single project, which might result in ambiguous results.\n // We need to do additional filtering by matching allowed package types passed as params with package description.\n // Remaining packages are the one we are looking for\n const packagesWithType = repoPackages.filter((pkg) => {\n for (const pkgType of types) {\n if (pkg[pkgType] && pkg[pkgType] === pkgName) {\n return true;\n }\n }\n return false;\n });\n\n return packagesWithType.length > 0 ? packagesWithType : null;\n}\n\nexport class RepologyDatasource extends Datasource {\n static readonly id = 'repology';\n\n override readonly defaultRegistryUrls = ['https://repology.org/'];\n\n override readonly registryStrategy = 'hunt';\n\n constructor() {\n super(RepologyDatasource.id);\n }\n\n private async queryPackages(url: string): Promise<RepologyPackage[]> {\n try {\n const res = await this.http.getJson(url, RepologyPackages);\n return res.body;\n } catch (err) {\n if (err.statusCode === 404) {\n // Return an array here because the api does not return proper http codes\n // and instead of an 404 error an empty array with code 200 is returned\n // When querying the resolver 404 is thrown if package could not be resolved\n // and 403 if the repo is not supported\n // 403 is handled later because in this case we are trying the API\n return [];\n }\n\n throw err;\n }\n }\n\n private async queryPackagesViaResolver(\n registryUrl: string,\n repoName: string,\n packageName: string,\n packageType: RepologyPackageType,\n ): Promise<RepologyPackage[]> {\n const query = getQueryString({\n repo: repoName,\n name_type: packageType,\n target_page: 'api_v1_project',\n noautoresolve: 'on',\n name: packageName,\n });\n\n // Retrieve list of packages by looking up Repology project\n const packages = await this.queryPackages(\n joinUrlParts(registryUrl, `tools/project-by?${query}`),\n );\n\n return packages;\n }\n\n private async queryPackagesViaAPI(\n registryUrl: string,\n packageName: string,\n ): Promise<RepologyPackage[]> {\n // Directly query the package via the API. This will only work if `packageName` has the\n // same name as the repology project\n const packages = await this.queryPackages(\n joinUrlParts(registryUrl, `api/v1/project`, packageName),\n );\n\n return packages;\n }\n\n private async _queryPackage(\n registryUrl: string,\n repoName: string,\n pkgName: string,\n ): Promise<RepologyPackage[] | undefined> {\n let response: RepologyPackage[];\n // Try getting the packages from tools/project-by first for type binname and\n // afterwards for srcname. This needs to be done first, because some packages\n // resolve to repology projects which have a different name than the package\n // e.g. `pulseaudio-utils` resolves to project `pulseaudio`, BUT there is also\n // a project called `pulseaudio-utils` but it does not contain the package we\n // are looking for.\n try {\n for (const pkgType of packageTypes) {\n response = await this.queryPackagesViaResolver(\n registryUrl,\n repoName,\n pkgName,\n pkgType,\n );\n\n // v8 ignore else -- the resolver either returns a body or throws\n if (response) {\n const pkg = findPackageInResponse(response, repoName, pkgName, [\n pkgType,\n ]);\n if (isNonEmptyArray(pkg)) {\n // exit immediately if package found\n return pkg;\n }\n }\n }\n } catch (err) {\n if (err.statusCode === 403) {\n logger.debug(\n { repoName, pkgName },\n 'Repology does not support tools/project-by lookups for repository. Will try direct API access now',\n );\n\n // If the repository is not supported in tools/project-by we try directly accessing the\n // API. This will support all repositories but requires that the project name is equal to the\n // package name. This won't be always the case but for a good portion we might be able to resolve\n // the package this way.\n response = await this.queryPackagesViaAPI(registryUrl, pkgName);\n const pkg = findPackageInResponse(\n response,\n repoName,\n pkgName,\n packageTypes,\n );\n if (isNonEmptyArray(pkg)) {\n // exit immediately if package found\n return pkg;\n }\n } else if (err.statusCode === 300) {\n logger.warn(\n { repoName, pkgName },\n 'Ambiguous redirection from package name to project name in Repology. Skipping this package',\n );\n return undefined;\n }\n\n throw err;\n }\n\n logger.debug(\n { repoName, pkgName },\n 'Repository or package not found on Repology',\n );\n\n return undefined;\n }\n\n queryPackage(\n registryUrl: string,\n repoName: string,\n pkgName: string,\n ): Promise<RepologyPackage[] | undefined> {\n return withCache(\n {\n ttlMinutes: 60,\n namespace: `datasource-${RepologyDatasource.id}`,\n key: joinUrlParts(registryUrl, repoName, pkgName),\n },\n () => this._queryPackage(registryUrl, repoName, pkgName),\n );\n }\n\n async getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n /* v8 ignore next -- should never happen */\n if (!registryUrl) {\n return null;\n }\n // Ensure lookup name contains both repository and package\n const [repoName, pkgName] = packageName.split('/', 2);\n if (!repoName || !pkgName) {\n throw new ExternalHostError(\n new Error(\n 'Repology lookup name must contain repository and package separated by slash (<repo>/<pkg>)',\n ),\n );\n }\n\n logger.trace(`repology.getReleases(${repoName}, ${pkgName})`);\n try {\n // Try to retrieve (cached) package information from Repology\n const pkg = await this.queryPackage(registryUrl, repoName, pkgName);\n if (!pkg) {\n return null;\n }\n\n // Always prefer origversion if available, otherwise default to version\n // This is required as source packages usually have no origversion\n const releases = pkg.map((item) => ({\n version: item.origversion ?? item.version,\n }));\n return { releases };\n } catch (err) {\n if ([HOST_BLOCKED, HOST_DISABLED].includes(err.message)) {\n logger.trace({ packageName, err }, refusedHostMessage(err));\n } else {\n logger.once.warn(\n { packageName, err },\n 'Repology lookup failed with unexpected error',\n );\n }\n\n this.handleGenericErrors(err);\n }\n }\n}\n"],"mappings":"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"}
@@ -29,6 +29,7 @@ var MetadataCache = class {
29
29
  });
30
30
  }
31
31
  async function saveCache(cache, ttlMinutes = 144e3, ttlDelta = 14400) {
32
+ // v8 ignore else -- needs a save against a non-rubygems.org host
32
33
  if (parseUrl(registryUrl)?.hostname === "rubygems.org") {
33
34
  const ttl = ttlMinutes + Math.floor(Math.random() * ttlDelta);
34
35
  await set(cacheNs, cacheKey, cache, ttl);
@@ -49,6 +50,7 @@ var MetadataCache = class {
49
50
  */
50
51
  if (err.type === "cache-stale") {
51
52
  const staleCache = err.cache;
53
+ // v8 ignore else -- needs a stale cache already marked fallback
52
54
  if (!staleCache.isFallback) await saveCache({
53
55
  ...staleCache,
54
56
  isFallback: true
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-cache.js","names":["packageCache.get","packageCache.set"],"sources":["../../../../lib/modules/datasource/rubygems/metadata-cache.ts"],"sourcesContent":["import { logger } from '../../../logger/index.ts';\nimport * as packageCache from '../../../util/cache/package/index.ts';\nimport { toSha256 } from '../../../util/hash.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport type { AsyncResult } from '../../../util/result.ts';\nimport { Result } from '../../../util/result.ts';\nimport { parseUrl } from '../../../util/url.ts';\nimport type { ReleaseResult } from '../types.ts';\nimport { getV1Releases } from './common.ts';\n\ninterface CacheRecord {\n hash: string;\n data: ReleaseResult;\n isFallback?: true;\n}\n\nfunction hashVersions(versions: string[]): string {\n return toSha256(versions.sort().join(','));\n}\n\nfunction hashReleases(releases: ReleaseResult): string {\n return hashVersions(releases.releases.map((release) => release.version));\n}\n\ninterface CacheNotFoundError {\n type: 'cache-not-found';\n}\ninterface CacheStaleError {\n type: 'cache-stale';\n cache: CacheRecord;\n}\ninterface CacheInvalidError {\n type: 'cache-invalid';\n}\ntype CacheLoadError = CacheNotFoundError | CacheStaleError;\ntype CacheError = CacheNotFoundError | CacheStaleError | CacheInvalidError;\n\nexport class MetadataCache {\n private readonly http: Http;\n\n constructor(http: Http) {\n this.http = http;\n }\n\n async getRelease(\n registryUrl: string,\n packageName: string,\n versions: string[],\n ): Promise<ReleaseResult> {\n const cacheNs = `datasource-rubygems`;\n const cacheKey = `metadata-cache:${registryUrl}:${packageName}`;\n const versionsHash = hashVersions(versions);\n\n function loadCache(): AsyncResult<ReleaseResult, CacheLoadError> {\n return Result.wrapNullable<CacheRecord, CacheLoadError, CacheLoadError>(\n packageCache.get<CacheRecord>(cacheNs, cacheKey),\n { type: 'cache-not-found' },\n ).transform((cache) => {\n return versionsHash === cache.hash\n ? Result.ok(cache.data)\n : Result.err({ type: 'cache-stale', cache });\n });\n }\n\n async function saveCache(\n cache: CacheRecord,\n ttlMinutes = 100 * 24 * 60,\n ttlDelta = 10 * 24 * 60,\n ): Promise<void> {\n const registryHostname = parseUrl(registryUrl)?.hostname;\n if (registryHostname === 'rubygems.org') {\n const ttlRandomDelta = Math.floor(Math.random() * ttlDelta);\n const ttl = ttlMinutes + ttlRandomDelta;\n await packageCache.set(cacheNs, cacheKey, cache, ttl);\n }\n }\n\n return await loadCache()\n .catch((err) =>\n getV1Releases(this.http, registryUrl, packageName).transform(\n async (\n data: ReleaseResult,\n ): Promise<Result<ReleaseResult, CacheError>> => {\n const dataHash = hashReleases(data);\n if (dataHash === versionsHash) {\n await saveCache({\n hash: dataHash,\n data,\n });\n return Result.ok(data);\n }\n\n /**\n * Return stale cache for 24 hours,\n * if metadata is inconsistent with versions list.\n */\n if (err.type === 'cache-stale') {\n const staleCache = err.cache;\n if (!staleCache.isFallback) {\n await saveCache(\n { ...staleCache, isFallback: true },\n 24 * 60,\n 0,\n );\n }\n return Result.ok(staleCache.data);\n }\n\n return Result.err({ type: 'cache-invalid' });\n },\n ),\n )\n .catch((err) => {\n logger.debug(\n { err },\n 'Rubygems: error fetching rubygems data, falling back to versions-only result',\n );\n const releases = versions.map((version) => ({ version }));\n return Result.ok({ releases });\n })\n .unwrapOrThrow();\n }\n}\n"],"mappings":";;;;;;;AAgBA,SAAS,aAAa,UAA4B;CAChD,OAAO,SAAS,SAAS,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC;AAC3C;AAEA,SAAS,aAAa,UAAiC;CACrD,OAAO,aAAa,SAAS,SAAS,KAAK,YAAY,QAAQ,OAAO,CAAC;AACzE;AAeA,IAAa,gBAAb,MAA2B;CACzB;CAEA,YAAY,MAAY;EACtB,KAAK,OAAO;CACd;CAEA,MAAM,WACJ,aACA,aACA,UACwB;EACxB,MAAM,UAAU;EAChB,MAAM,WAAW,kBAAkB,YAAY,GAAG;EAClD,MAAM,eAAe,aAAa,QAAQ;EAE1C,SAAS,YAAwD;GAC/D,OAAO,OAAO,aACZA,IAA8B,SAAS,QAAQ,GAC/C,EAAE,MAAM,kBAAkB,CAC5B,CAAC,CAAC,WAAW,UAAU;IACrB,OAAO,iBAAiB,MAAM,OAC1B,OAAO,GAAG,MAAM,IAAI,IACpB,OAAO,IAAI;KAAE,MAAM;KAAe;IAAM,CAAC;GAC/C,CAAC;EACH;EAEA,eAAe,UACb,OACA,aAAa,OACb,WAAW,OACI;GAEf,IADyB,SAAS,WAAW,CAAC,EAAE,aACvB,gBAAgB;IAEvC,MAAM,MAAM,aADW,KAAK,MAAM,KAAK,OAAO,IAAI,QACZ;IACtC,MAAMC,IAAiB,SAAS,UAAU,OAAO,GAAG;GACtD;EACF;EAEA,OAAO,MAAM,UAAU,CAAC,CACrB,OAAO,QACN,cAAc,KAAK,MAAM,aAAa,WAAW,CAAC,CAAC,UACjD,OACE,SAC+C;GAC/C,MAAM,WAAW,aAAa,IAAI;GAClC,IAAI,aAAa,cAAc;IAC7B,MAAM,UAAU;KACd,MAAM;KACN;IACF,CAAC;IACD,OAAO,OAAO,GAAG,IAAI;GACvB;;;;;GAMA,IAAI,IAAI,SAAS,eAAe;IAC9B,MAAM,aAAa,IAAI;IACvB,IAAI,CAAC,WAAW,YACd,MAAM,UACJ;KAAE,GAAG;KAAY,YAAY;IAAK,GAClC,MACA,CACF;IAEF,OAAO,OAAO,GAAG,WAAW,IAAI;GAClC;GAEA,OAAO,OAAO,IAAI,EAAE,MAAM,gBAAgB,CAAC;EAC7C,CACF,CACF,CAAC,CACA,OAAO,QAAQ;GACd,OAAO,MACL,EAAE,IAAI,GACN,8EACF;GACA,MAAM,WAAW,SAAS,KAAK,aAAa,EAAE,QAAQ,EAAE;GACxD,OAAO,OAAO,GAAG,EAAE,SAAS,CAAC;EAC/B,CAAC,CAAC,CACD,cAAc;CACnB;AACF"}
1
+ {"version":3,"file":"metadata-cache.js","names":["packageCache.get","packageCache.set"],"sources":["../../../../lib/modules/datasource/rubygems/metadata-cache.ts"],"sourcesContent":["import { logger } from '../../../logger/index.ts';\nimport * as packageCache from '../../../util/cache/package/index.ts';\nimport { toSha256 } from '../../../util/hash.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport type { AsyncResult } from '../../../util/result.ts';\nimport { Result } from '../../../util/result.ts';\nimport { parseUrl } from '../../../util/url.ts';\nimport type { ReleaseResult } from '../types.ts';\nimport { getV1Releases } from './common.ts';\n\ninterface CacheRecord {\n hash: string;\n data: ReleaseResult;\n isFallback?: true;\n}\n\nfunction hashVersions(versions: string[]): string {\n return toSha256(versions.sort().join(','));\n}\n\nfunction hashReleases(releases: ReleaseResult): string {\n return hashVersions(releases.releases.map((release) => release.version));\n}\n\ninterface CacheNotFoundError {\n type: 'cache-not-found';\n}\ninterface CacheStaleError {\n type: 'cache-stale';\n cache: CacheRecord;\n}\ninterface CacheInvalidError {\n type: 'cache-invalid';\n}\ntype CacheLoadError = CacheNotFoundError | CacheStaleError;\ntype CacheError = CacheNotFoundError | CacheStaleError | CacheInvalidError;\n\nexport class MetadataCache {\n private readonly http: Http;\n\n constructor(http: Http) {\n this.http = http;\n }\n\n async getRelease(\n registryUrl: string,\n packageName: string,\n versions: string[],\n ): Promise<ReleaseResult> {\n const cacheNs = `datasource-rubygems`;\n const cacheKey = `metadata-cache:${registryUrl}:${packageName}`;\n const versionsHash = hashVersions(versions);\n\n function loadCache(): AsyncResult<ReleaseResult, CacheLoadError> {\n return Result.wrapNullable<CacheRecord, CacheLoadError, CacheLoadError>(\n packageCache.get<CacheRecord>(cacheNs, cacheKey),\n { type: 'cache-not-found' },\n ).transform((cache) => {\n return versionsHash === cache.hash\n ? Result.ok(cache.data)\n : Result.err({ type: 'cache-stale', cache });\n });\n }\n\n async function saveCache(\n cache: CacheRecord,\n ttlMinutes = 100 * 24 * 60,\n ttlDelta = 10 * 24 * 60,\n ): Promise<void> {\n const registryHostname = parseUrl(registryUrl)?.hostname;\n // v8 ignore else -- needs a save against a non-rubygems.org host\n if (registryHostname === 'rubygems.org') {\n const ttlRandomDelta = Math.floor(Math.random() * ttlDelta);\n const ttl = ttlMinutes + ttlRandomDelta;\n await packageCache.set(cacheNs, cacheKey, cache, ttl);\n }\n }\n\n return await loadCache()\n .catch((err) =>\n getV1Releases(this.http, registryUrl, packageName).transform(\n async (\n data: ReleaseResult,\n ): Promise<Result<ReleaseResult, CacheError>> => {\n const dataHash = hashReleases(data);\n if (dataHash === versionsHash) {\n await saveCache({\n hash: dataHash,\n data,\n });\n return Result.ok(data);\n }\n\n /**\n * Return stale cache for 24 hours,\n * if metadata is inconsistent with versions list.\n */\n if (err.type === 'cache-stale') {\n const staleCache = err.cache;\n // v8 ignore else -- needs a stale cache already marked fallback\n if (!staleCache.isFallback) {\n await saveCache(\n { ...staleCache, isFallback: true },\n 24 * 60,\n 0,\n );\n }\n return Result.ok(staleCache.data);\n }\n\n return Result.err({ type: 'cache-invalid' });\n },\n ),\n )\n .catch((err) => {\n logger.debug(\n { err },\n 'Rubygems: error fetching rubygems data, falling back to versions-only result',\n );\n const releases = versions.map((version) => ({ version }));\n return Result.ok({ releases });\n })\n .unwrapOrThrow();\n }\n}\n"],"mappings":";;;;;;;AAgBA,SAAS,aAAa,UAA4B;CAChD,OAAO,SAAS,SAAS,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC;AAC3C;AAEA,SAAS,aAAa,UAAiC;CACrD,OAAO,aAAa,SAAS,SAAS,KAAK,YAAY,QAAQ,OAAO,CAAC;AACzE;AAeA,IAAa,gBAAb,MAA2B;CACzB;CAEA,YAAY,MAAY;EACtB,KAAK,OAAO;CACd;CAEA,MAAM,WACJ,aACA,aACA,UACwB;EACxB,MAAM,UAAU;EAChB,MAAM,WAAW,kBAAkB,YAAY,GAAG;EAClD,MAAM,eAAe,aAAa,QAAQ;EAE1C,SAAS,YAAwD;GAC/D,OAAO,OAAO,aACZA,IAA8B,SAAS,QAAQ,GAC/C,EAAE,MAAM,kBAAkB,CAC5B,CAAC,CAAC,WAAW,UAAU;IACrB,OAAO,iBAAiB,MAAM,OAC1B,OAAO,GAAG,MAAM,IAAI,IACpB,OAAO,IAAI;KAAE,MAAM;KAAe;IAAM,CAAC;GAC/C,CAAC;EACH;EAEA,eAAe,UACb,OACA,aAAa,OACb,WAAW,OACI;;GAGf,IAFyB,SAAS,WAAW,CAAC,EAAE,aAEvB,gBAAgB;IAEvC,MAAM,MAAM,aADW,KAAK,MAAM,KAAK,OAAO,IAAI,QACZ;IACtC,MAAMC,IAAiB,SAAS,UAAU,OAAO,GAAG;GACtD;EACF;EAEA,OAAO,MAAM,UAAU,CAAC,CACrB,OAAO,QACN,cAAc,KAAK,MAAM,aAAa,WAAW,CAAC,CAAC,UACjD,OACE,SAC+C;GAC/C,MAAM,WAAW,aAAa,IAAI;GAClC,IAAI,aAAa,cAAc;IAC7B,MAAM,UAAU;KACd,MAAM;KACN;IACF,CAAC;IACD,OAAO,OAAO,GAAG,IAAI;GACvB;;;;;GAMA,IAAI,IAAI,SAAS,eAAe;IAC9B,MAAM,aAAa,IAAI;;IAEvB,IAAI,CAAC,WAAW,YACd,MAAM,UACJ;KAAE,GAAG;KAAY,YAAY;IAAK,GAClC,MACA,CACF;IAEF,OAAO,OAAO,GAAG,WAAW,IAAI;GAClC;GAEA,OAAO,OAAO,IAAI,EAAE,MAAM,gBAAgB,CAAC;EAC7C,CACF,CACF,CAAC,CACA,OAAO,QAAQ;GACd,OAAO,MACL,EAAE,IAAI,GACN,8EACF;GACA,MAAM,WAAW,SAAS,KAAK,aAAa,EAAE,QAAQ,EAAE;GACxD,OAAO,OAAO,GAAG,EAAE,SAAS,CAAC;EAC/B,CAAC,CAAC,CACD,cAAc;CACnB;AACF"}
@@ -26,9 +26,12 @@ function reconcilePackageVersions(packageVersions, versionLines) {
26
26
  let versions = coerceArray(packageVersions.get(packageName));
27
27
  const { deletedVersions, addedVersions } = line;
28
28
  if (deletedVersions.size > 0) versions = versions.filter((v) => !deletedVersions.has(v));
29
+ // v8 ignore else -- needs a delta that only removes versions
29
30
  if (addedVersions.length > 0) {
30
31
  const existingVersions = new Set(versions);
31
- for (const addedVersion of addedVersions) if (!existingVersions.has(addedVersion)) {
32
+ for (const addedVersion of addedVersions)
33
+ // v8 ignore else -- needs a delta re-adding a version already present
34
+ if (!existingVersions.has(addedVersion)) {
32
35
  const version = copystr(addedVersion);
33
36
  versions.push(version);
34
37
  }
@@ -51,6 +54,7 @@ function parseFullBody(body) {
51
54
  }
52
55
  const memCache = /* @__PURE__ */ new Map();
53
56
  function cacheResult(registryUrl, result) {
57
+ // v8 ignore else -- needs a result cached for a non-rubygems.org host
54
58
  if (parseUrl(registryUrl)?.hostname === "rubygems.org") memCache.set(registryUrl, result);
55
59
  }
56
60
  const VersionLines = z.string().transform((x) => x.split(newlineRegex)).pipe(LooseArray(z.string().transform((line) => line.trim()).refine((line) => line.length > 0).refine((line) => !line.startsWith("created_at:")).refine((line) => line !== "---").transform((line) => line.split(" ")).pipe(z.tuple([z.string(), z.string()]).rest(z.string())).transform(([packageName, versions]) => {
@@ -166,6 +170,7 @@ var VersionsEndpointCache = class {
166
170
  contentTail
167
171
  });
168
172
  } catch (err) {
173
+ // v8 ignore else -- needs a non-http failure during the sync
169
174
  if (err instanceof RequestError) {
170
175
  const responseStatus = err.response?.statusCode;
171
176
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"versions-endpoint-cache.js","names":["HttpError"],"sources":["../../../../lib/modules/datasource/rubygems/versions-endpoint-cache.ts"],"sourcesContent":["import { DateTime } from 'luxon';\nimport { z } from 'zod/v4';\nimport { logger } from '../../../logger/index.ts';\nimport { coerceArray } from '../../../util/array.ts';\nimport { getElapsedMinutes } from '../../../util/date.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport type { HttpOptions } from '../../../util/http/types.ts';\nimport { newlineRegex } from '../../../util/regex.ts';\nimport { Result } from '../../../util/result.ts';\nimport { LooseArray } from '../../../util/schema-utils/index.ts';\nimport { copystr } from '../../../util/string.ts';\nimport { parseUrl } from '../../../util/url.ts';\nimport type { VersionsResult } from './types.ts';\n\ntype PackageVersions = Map<string, string[]>;\n\ninterface VersionsEndpointData {\n packageVersions: PackageVersions;\n syncedAt: Date;\n contentLength: number;\n\n /**\n * Last 33 characters of the response (32 hex digits + newline)\n */\n contentTail: string;\n}\n\nfunction getContentTail(content: string): string {\n return content.slice(-33);\n}\n\nfunction getContentHead(content: string): string {\n return content.slice(0, 33);\n}\n\nfunction stripContentHead(content: string): string {\n return content.slice(33);\n}\n\nfunction reconcilePackageVersions(\n packageVersions: PackageVersions,\n versionLines: VersionLines,\n): PackageVersions {\n for (const line of versionLines) {\n const packageName = copystr(line.packageName);\n let versions = coerceArray(packageVersions.get(packageName));\n\n const { deletedVersions, addedVersions } = line;\n\n if (deletedVersions.size > 0) {\n versions = versions.filter((v) => !deletedVersions.has(v));\n }\n\n if (addedVersions.length > 0) {\n const existingVersions = new Set(versions);\n for (const addedVersion of addedVersions) {\n if (!existingVersions.has(addedVersion)) {\n const version = copystr(addedVersion);\n versions.push(version);\n }\n }\n }\n\n packageVersions.set(packageName, versions);\n }\n\n return packageVersions;\n}\n\nfunction parseFullBody(body: string): VersionsEndpointResult {\n const packageVersions = reconcilePackageVersions(\n new Map<string, string[]>(),\n VersionLines.parse(body),\n );\n const syncedAt = DateTime.now().toJSDate();\n const contentLength = body.length;\n const contentTail = getContentTail(body);\n\n return Result.ok({\n packageVersions,\n syncedAt,\n contentLength,\n contentTail,\n });\n}\n\ntype VersionsEndpointResult = Result<VersionsEndpointData, 'unsupported-api'>;\n\nexport const memCache = new Map<string, VersionsEndpointResult>();\n\nfunction cacheResult(\n registryUrl: string,\n result: VersionsEndpointResult,\n): void {\n const registryHostname = parseUrl(registryUrl)?.hostname;\n if (registryHostname === 'rubygems.org') {\n memCache.set(registryUrl, result);\n }\n}\n\nconst VersionLines = z\n .string()\n .transform((x) => x.split(newlineRegex))\n .pipe(\n LooseArray(\n z\n .string()\n .transform((line) => line.trim())\n .refine((line) => line.length > 0)\n .refine((line) => !line.startsWith('created_at:'))\n .refine((line) => line !== '---')\n .transform((line) => line.split(' '))\n .pipe(z.tuple([z.string(), z.string()]).rest(z.string()))\n .transform(([packageName, versions]) => {\n const deletedVersions = new Set<string>();\n const addedVersions: string[] = [];\n for (const version of versions.split(',')) {\n if (version.startsWith('-')) {\n deletedVersions.add(version.slice(1));\n } else {\n addedVersions.push(version);\n }\n }\n return { packageName, deletedVersions, addedVersions };\n }),\n ),\n );\ntype VersionLines = z.infer<typeof VersionLines>;\n\nfunction isStale(regCache: VersionsEndpointData): boolean {\n return getElapsedMinutes(regCache.syncedAt) >= 15;\n}\n\nexport class VersionsEndpointCache {\n private readonly http: Http;\n\n constructor(http: Http) {\n this.http = http;\n }\n\n private cacheRequests = new Map<string, Promise<VersionsEndpointResult>>();\n\n /**\n * At any given time, there should only be one request for a given registryUrl.\n */\n private async getCache(registryUrl: string): Promise<VersionsEndpointResult> {\n const oldResult = memCache.get(registryUrl);\n if (!oldResult) {\n const newResult = await this.fullSync(registryUrl);\n cacheResult(registryUrl, newResult);\n return newResult;\n }\n\n const { val: data } = oldResult.unwrap();\n if (!data) {\n return oldResult;\n }\n\n if (isStale(data)) {\n memCache.delete(registryUrl); // If no error is thrown, we'll re-set the cache\n const newResult = await this.deltaSync(data, registryUrl);\n cacheResult(registryUrl, newResult);\n return newResult;\n }\n\n return oldResult;\n }\n\n async getVersions(\n registryUrl: string,\n packageName: string,\n ): Promise<VersionsResult> {\n /**\n * Ensure that only one request for a given registryUrl is in flight at a time.\n */\n let cacheRequest = this.cacheRequests.get(registryUrl);\n if (!cacheRequest) {\n cacheRequest = this.getCache(registryUrl);\n this.cacheRequests.set(registryUrl, cacheRequest);\n }\n let cachedResult: VersionsEndpointResult;\n try {\n cachedResult = await cacheRequest;\n } finally {\n this.cacheRequests.delete(registryUrl);\n }\n\n const { val: cachedData } = cachedResult.unwrap();\n if (!cachedData) {\n logger.debug(\n { packageName, registryUrl },\n 'Rubygems: endpoint not supported',\n );\n return Result.err('unsupported-api');\n }\n\n const versions = cachedData.packageVersions.get(packageName);\n if (!versions?.length) {\n logger.debug(\n { packageName, registryUrl },\n 'Rubygems: versions not found',\n );\n return Result.err('package-not-found');\n }\n\n return Result.ok(versions);\n }\n\n private async fullSync(registryUrl: string): Promise<VersionsEndpointResult> {\n try {\n const url = `${registryUrl}/versions`;\n const opts: HttpOptions = { headers: { 'Accept-Encoding': 'gzip' } };\n const { body } = await this.http.getText(url, opts);\n return parseFullBody(body);\n } catch (err) {\n if (err instanceof HttpError && err.response?.statusCode === 404) {\n return Result.err('unsupported-api');\n }\n\n throw err;\n }\n }\n\n private async deltaSync(\n oldCache: VersionsEndpointData,\n registryUrl: string,\n ): Promise<VersionsEndpointResult> {\n try {\n const url = `${registryUrl}/versions`;\n const startByte = oldCache.contentLength - oldCache.contentTail.length;\n const opts: HttpOptions = {\n headers: {\n ['Accept-Encoding']: 'deflate, compress, br', // Note: `gzip` usage breaks http client, when used with `Range` header\n ['Range']: `bytes=${startByte}-`,\n },\n };\n const { statusCode, body } = await this.http.getText(url, opts);\n\n /**\n * Rubygems will return the full body instead of `416 Range Not Satisfiable`.\n * In this case, status code will be 200 instead of 206.\n */\n if (statusCode === 200) {\n return parseFullBody(body);\n }\n\n /**\n * We request data in range that overlaps previously fetched data.\n * If the head of the response doesn't match the tail of the previous response,\n * it means that the data we have is no longer valid.\n * In this case we start over with a full sync.\n */\n const contentHead = getContentHead(body);\n if (contentHead !== oldCache.contentTail) {\n return this.fullSync(registryUrl);\n }\n\n /**\n * Update the cache with the new data.\n */\n const delta = stripContentHead(body);\n const packageVersions = reconcilePackageVersions(\n oldCache.packageVersions,\n VersionLines.parse(delta),\n );\n const syncedAt = DateTime.now().toJSDate();\n const contentLength = oldCache.contentLength + delta.length;\n const contentTail = getContentTail(body);\n\n return Result.ok({\n packageVersions,\n syncedAt,\n contentLength,\n contentTail,\n });\n } catch (err) {\n if (err instanceof HttpError) {\n const responseStatus = err.response?.statusCode;\n\n /**\n * In case of `416 Range Not Satisfiable` we do a full sync.\n * This is unlikely to happen in real life, but anyway.\n */\n if (responseStatus === 416) {\n return this.fullSync(registryUrl);\n }\n\n /**\n * If the endpoint is not supported, we stop trying.\n * This is unlikely to happen in real life, but still.\n */\n if (responseStatus === 404) {\n return Result.err('unsupported-api');\n }\n }\n\n throw err;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"versions-endpoint-cache.js","names":["HttpError"],"sources":["../../../../lib/modules/datasource/rubygems/versions-endpoint-cache.ts"],"sourcesContent":["import { DateTime } from 'luxon';\nimport { z } from 'zod/v4';\nimport { logger } from '../../../logger/index.ts';\nimport { coerceArray } from '../../../util/array.ts';\nimport { getElapsedMinutes } from '../../../util/date.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport type { HttpOptions } from '../../../util/http/types.ts';\nimport { newlineRegex } from '../../../util/regex.ts';\nimport { Result } from '../../../util/result.ts';\nimport { LooseArray } from '../../../util/schema-utils/index.ts';\nimport { copystr } from '../../../util/string.ts';\nimport { parseUrl } from '../../../util/url.ts';\nimport type { VersionsResult } from './types.ts';\n\ntype PackageVersions = Map<string, string[]>;\n\ninterface VersionsEndpointData {\n packageVersions: PackageVersions;\n syncedAt: Date;\n contentLength: number;\n\n /**\n * Last 33 characters of the response (32 hex digits + newline)\n */\n contentTail: string;\n}\n\nfunction getContentTail(content: string): string {\n return content.slice(-33);\n}\n\nfunction getContentHead(content: string): string {\n return content.slice(0, 33);\n}\n\nfunction stripContentHead(content: string): string {\n return content.slice(33);\n}\n\nfunction reconcilePackageVersions(\n packageVersions: PackageVersions,\n versionLines: VersionLines,\n): PackageVersions {\n for (const line of versionLines) {\n const packageName = copystr(line.packageName);\n let versions = coerceArray(packageVersions.get(packageName));\n\n const { deletedVersions, addedVersions } = line;\n\n if (deletedVersions.size > 0) {\n versions = versions.filter((v) => !deletedVersions.has(v));\n }\n\n // v8 ignore else -- needs a delta that only removes versions\n if (addedVersions.length > 0) {\n const existingVersions = new Set(versions);\n for (const addedVersion of addedVersions) {\n // v8 ignore else -- needs a delta re-adding a version already present\n if (!existingVersions.has(addedVersion)) {\n const version = copystr(addedVersion);\n versions.push(version);\n }\n }\n }\n\n packageVersions.set(packageName, versions);\n }\n\n return packageVersions;\n}\n\nfunction parseFullBody(body: string): VersionsEndpointResult {\n const packageVersions = reconcilePackageVersions(\n new Map<string, string[]>(),\n VersionLines.parse(body),\n );\n const syncedAt = DateTime.now().toJSDate();\n const contentLength = body.length;\n const contentTail = getContentTail(body);\n\n return Result.ok({\n packageVersions,\n syncedAt,\n contentLength,\n contentTail,\n });\n}\n\ntype VersionsEndpointResult = Result<VersionsEndpointData, 'unsupported-api'>;\n\nexport const memCache = new Map<string, VersionsEndpointResult>();\n\nfunction cacheResult(\n registryUrl: string,\n result: VersionsEndpointResult,\n): void {\n const registryHostname = parseUrl(registryUrl)?.hostname;\n // v8 ignore else -- needs a result cached for a non-rubygems.org host\n if (registryHostname === 'rubygems.org') {\n memCache.set(registryUrl, result);\n }\n}\n\nconst VersionLines = z\n .string()\n .transform((x) => x.split(newlineRegex))\n .pipe(\n LooseArray(\n z\n .string()\n .transform((line) => line.trim())\n .refine((line) => line.length > 0)\n .refine((line) => !line.startsWith('created_at:'))\n .refine((line) => line !== '---')\n .transform((line) => line.split(' '))\n .pipe(z.tuple([z.string(), z.string()]).rest(z.string()))\n .transform(([packageName, versions]) => {\n const deletedVersions = new Set<string>();\n const addedVersions: string[] = [];\n for (const version of versions.split(',')) {\n if (version.startsWith('-')) {\n deletedVersions.add(version.slice(1));\n } else {\n addedVersions.push(version);\n }\n }\n return { packageName, deletedVersions, addedVersions };\n }),\n ),\n );\ntype VersionLines = z.infer<typeof VersionLines>;\n\nfunction isStale(regCache: VersionsEndpointData): boolean {\n return getElapsedMinutes(regCache.syncedAt) >= 15;\n}\n\nexport class VersionsEndpointCache {\n private readonly http: Http;\n\n constructor(http: Http) {\n this.http = http;\n }\n\n private cacheRequests = new Map<string, Promise<VersionsEndpointResult>>();\n\n /**\n * At any given time, there should only be one request for a given registryUrl.\n */\n private async getCache(registryUrl: string): Promise<VersionsEndpointResult> {\n const oldResult = memCache.get(registryUrl);\n if (!oldResult) {\n const newResult = await this.fullSync(registryUrl);\n cacheResult(registryUrl, newResult);\n return newResult;\n }\n\n const { val: data } = oldResult.unwrap();\n if (!data) {\n return oldResult;\n }\n\n if (isStale(data)) {\n memCache.delete(registryUrl); // If no error is thrown, we'll re-set the cache\n const newResult = await this.deltaSync(data, registryUrl);\n cacheResult(registryUrl, newResult);\n return newResult;\n }\n\n return oldResult;\n }\n\n async getVersions(\n registryUrl: string,\n packageName: string,\n ): Promise<VersionsResult> {\n /**\n * Ensure that only one request for a given registryUrl is in flight at a time.\n */\n let cacheRequest = this.cacheRequests.get(registryUrl);\n if (!cacheRequest) {\n cacheRequest = this.getCache(registryUrl);\n this.cacheRequests.set(registryUrl, cacheRequest);\n }\n let cachedResult: VersionsEndpointResult;\n try {\n cachedResult = await cacheRequest;\n } finally {\n this.cacheRequests.delete(registryUrl);\n }\n\n const { val: cachedData } = cachedResult.unwrap();\n if (!cachedData) {\n logger.debug(\n { packageName, registryUrl },\n 'Rubygems: endpoint not supported',\n );\n return Result.err('unsupported-api');\n }\n\n const versions = cachedData.packageVersions.get(packageName);\n if (!versions?.length) {\n logger.debug(\n { packageName, registryUrl },\n 'Rubygems: versions not found',\n );\n return Result.err('package-not-found');\n }\n\n return Result.ok(versions);\n }\n\n private async fullSync(registryUrl: string): Promise<VersionsEndpointResult> {\n try {\n const url = `${registryUrl}/versions`;\n const opts: HttpOptions = { headers: { 'Accept-Encoding': 'gzip' } };\n const { body } = await this.http.getText(url, opts);\n return parseFullBody(body);\n } catch (err) {\n if (err instanceof HttpError && err.response?.statusCode === 404) {\n return Result.err('unsupported-api');\n }\n\n throw err;\n }\n }\n\n private async deltaSync(\n oldCache: VersionsEndpointData,\n registryUrl: string,\n ): Promise<VersionsEndpointResult> {\n try {\n const url = `${registryUrl}/versions`;\n const startByte = oldCache.contentLength - oldCache.contentTail.length;\n const opts: HttpOptions = {\n headers: {\n ['Accept-Encoding']: 'deflate, compress, br', // Note: `gzip` usage breaks http client, when used with `Range` header\n ['Range']: `bytes=${startByte}-`,\n },\n };\n const { statusCode, body } = await this.http.getText(url, opts);\n\n /**\n * Rubygems will return the full body instead of `416 Range Not Satisfiable`.\n * In this case, status code will be 200 instead of 206.\n */\n if (statusCode === 200) {\n return parseFullBody(body);\n }\n\n /**\n * We request data in range that overlaps previously fetched data.\n * If the head of the response doesn't match the tail of the previous response,\n * it means that the data we have is no longer valid.\n * In this case we start over with a full sync.\n */\n const contentHead = getContentHead(body);\n if (contentHead !== oldCache.contentTail) {\n return this.fullSync(registryUrl);\n }\n\n /**\n * Update the cache with the new data.\n */\n const delta = stripContentHead(body);\n const packageVersions = reconcilePackageVersions(\n oldCache.packageVersions,\n VersionLines.parse(delta),\n );\n const syncedAt = DateTime.now().toJSDate();\n const contentLength = oldCache.contentLength + delta.length;\n const contentTail = getContentTail(body);\n\n return Result.ok({\n packageVersions,\n syncedAt,\n contentLength,\n contentTail,\n });\n } catch (err) {\n // v8 ignore else -- needs a non-http failure during the sync\n if (err instanceof HttpError) {\n const responseStatus = err.response?.statusCode;\n\n /**\n * In case of `416 Range Not Satisfiable` we do a full sync.\n * This is unlikely to happen in real life, but anyway.\n */\n if (responseStatus === 416) {\n return this.fullSync(registryUrl);\n }\n\n /**\n * If the endpoint is not supported, we stop trying.\n * This is unlikely to happen in real life, but still.\n */\n if (responseStatus === 404) {\n return Result.err('unsupported-api');\n }\n }\n\n throw err;\n }\n }\n}\n"],"mappings":"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"}
@@ -71,6 +71,7 @@ var SbtPackageDatasource = class SbtPackageDatasource extends MavenDatasource {
71
71
  });
72
72
  if (scalaVersion) {
73
73
  const scalaSubdir = artifactSubdirs.find((x) => x.endsWith(`/${artifactId}_${scalaVersion}/`));
74
+ // v8 ignore else -- needs a nested directory listing fixture
74
75
  if (scalaSubdir) {
75
76
  packageUrls = [scalaSubdir];
76
77
  break;
@@ -105,6 +106,7 @@ var SbtPackageDatasource = class SbtPackageDatasource extends MavenDatasource {
105
106
  for (const version of versions) allVersions.add(version);
106
107
  }
107
108
  if (invalidPackageUrls.size > 0) await set("datasource-sbt-package", invalidPackageUrlsKey, [...invalidPackageUrls], 43200);
109
+ // v8 ignore else -- an empty list returns before reaching here
108
110
  if (packageUrls.length > 0) {
109
111
  const packageUrlsKey = `package-urls:${registryUrl}:${packageName}`;
110
112
  await set("datasource-sbt-package", packageUrlsKey, packageUrls, 43200);
@@ -176,6 +178,7 @@ var SbtPackageDatasource = class SbtPackageDatasource extends MavenDatasource {
176
178
  /* v8 ignore next -- should never happen */
177
179
  if (!config.registryUrl) return release;
178
180
  const res = await this.getPomInfo(config.registryUrl, config.packageName, release.version);
181
+ // v8 ignore else -- needs a pom fixture carrying no timestamp
179
182
  if (res?.releaseTimestamp) release.releaseTimestamp = res.releaseTimestamp;
180
183
  return release;
181
184
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["packageCache.get","packageCache.set"],"sources":["../../../../lib/modules/datasource/sbt-package/index.ts"],"sourcesContent":["import upath from 'upath';\nimport { XmlDocument } from 'xmldoc';\nimport { logger } from '../../../logger/index.ts';\nimport * as packageCache from '../../../util/cache/package/index.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { Http } from '../../../util/http/index.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport type { Timestamp } from '../../../util/timestamp.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport {\n ensureTrailingSlash,\n parseUrl,\n trimTrailingSlash,\n} from '../../../util/url.ts';\nimport * as ivyVersioning from '../../versioning/ivy/index.ts';\nimport { compare } from '../../versioning/maven/compare.ts';\nimport { MAVEN_REPO } from '../maven/common.ts';\nimport { MavenDatasource } from '../maven/index.ts';\nimport { downloadHttpContent, downloadHttpProtocol } from '../maven/util.ts';\nimport type {\n GetReleasesConfig,\n PostprocessReleaseConfig,\n PostprocessReleaseResult,\n RegistryStrategy,\n Release,\n ReleaseResult,\n} from '../types.ts';\nimport { extractPageLinks, getLatestVersion } from './util.ts';\n\ninterface ScalaDepCoordinate {\n groupId: string;\n artifactId: string;\n scalaVersion?: string;\n}\n\ninterface PomInfo {\n homepage?: string;\n sourceUrl?: string;\n releaseTimestamp?: Timestamp;\n}\n\nexport class SbtPackageDatasource extends MavenDatasource {\n static override readonly id = 'sbt-package';\n\n override readonly defaultRegistryUrls = [MAVEN_REPO];\n\n override readonly defaultVersioning = ivyVersioning.id;\n\n override readonly registryStrategy: RegistryStrategy = 'hunt';\n\n override readonly sourceUrlSupport = 'package';\n override readonly sourceUrlNote =\n 'The source URL is determined from the `scm` tags in the results.';\n\n constructor(id = SbtPackageDatasource.id) {\n super(id);\n this.http = new Http('sbt');\n }\n\n protected static parseDepCoordinate(packageName: string): ScalaDepCoordinate {\n const [groupId, javaArtifactId] = packageName.split(':');\n const [artifactId, scalaVersion] = javaArtifactId.split('_');\n return { groupId, artifactId, scalaVersion };\n }\n\n async getSbtReleases(\n registryUrl: string,\n packageName: string,\n ): Promise<ReleaseResult | null> {\n const { groupId, artifactId, scalaVersion } =\n SbtPackageDatasource.parseDepCoordinate(packageName);\n\n const groupIdSplit = groupId.split('.');\n const repoRootUrl = ensureTrailingSlash(registryUrl);\n\n const validRootUrlKey = `valid-root-url:${registryUrl}:${packageName}`;\n const validRootUrl = await packageCache.get<string>(\n 'datasource-sbt-package',\n validRootUrlKey,\n );\n\n const packageRootUrls: string[] = [];\n // istanbul ignore if: not easily testable\n if (validRootUrl) {\n packageRootUrls.push(validRootUrl);\n } else {\n function packageRootUrlWith(sep: string): string {\n return `${repoRootUrl}${groupIdSplit.join(sep)}`;\n }\n packageRootUrls.push(ensureTrailingSlash(packageRootUrlWith('/')));\n packageRootUrls.push(ensureTrailingSlash(packageRootUrlWith('.')));\n }\n\n let dependencyUrl: string | undefined;\n let packageUrls: string[] | undefined;\n for (const packageRootUrl of packageRootUrls) {\n const packageRootContent = await downloadHttpContent(\n this.http,\n packageRootUrl,\n );\n if (!packageRootContent) {\n continue;\n }\n\n await packageCache.set(\n 'datasource-sbt-package',\n validRootUrlKey,\n packageRootUrl,\n 30 * 24 * 60,\n );\n\n dependencyUrl = trimTrailingSlash(packageRootUrl);\n\n const parsedPackageRootUrl = parseUrl(packageRootUrl);\n if (!parsedPackageRootUrl) {\n logger.warn({ packageRootUrl }, 'Failed to parse packageURL');\n continue;\n }\n const rootPath = parsedPackageRootUrl.pathname;\n const artifactSubdirs = extractPageLinks(packageRootContent, (href) => {\n const path = href.replace(rootPath, '');\n\n if (\n path.startsWith(`${artifactId}_native`) ||\n path.startsWith(`${artifactId}_sjs`)\n ) {\n return null;\n }\n\n if (path === artifactId || path.startsWith(`${artifactId}_`)) {\n return ensureTrailingSlash(`${packageRootUrl}${path}`);\n }\n\n return null;\n });\n\n if (scalaVersion) {\n const scalaSubdir = artifactSubdirs.find((x) =>\n x.endsWith(`/${artifactId}_${scalaVersion}/`),\n );\n if (scalaSubdir) {\n packageUrls = [scalaSubdir];\n break;\n }\n }\n\n packageUrls = artifactSubdirs;\n break;\n }\n\n if (!packageUrls) {\n return null;\n }\n\n const invalidPackageUrlsKey = `invalid-package-urls:${registryUrl}:${packageName}`;\n const invalidPackageUrls = new Set(\n await packageCache.get<string[]>(\n 'datasource-sbt-package',\n invalidPackageUrlsKey,\n ),\n );\n packageUrls = packageUrls.filter((url) => !invalidPackageUrls.has(url));\n\n const allVersions = new Set<string>();\n for (const pkgUrl of packageUrls) {\n const parsedPkgUrl = parseUrl(pkgUrl);\n if (!parsedPkgUrl) {\n invalidPackageUrls.add(pkgUrl);\n continue;\n }\n\n const packageContent = await downloadHttpContent(this.http, pkgUrl);\n // istanbul ignore if\n if (!packageContent) {\n invalidPackageUrls.add(pkgUrl);\n continue;\n }\n\n const rootPath = parsedPkgUrl.pathname;\n const versions = extractPageLinks(packageContent, (href) => {\n const path = href.replace(rootPath, '');\n if (path.startsWith('.')) {\n return null;\n }\n\n return path;\n });\n\n for (const version of versions) {\n allVersions.add(version);\n }\n }\n\n if (invalidPackageUrls.size > 0) {\n await packageCache.set(\n 'datasource-sbt-package',\n invalidPackageUrlsKey,\n [...invalidPackageUrls],\n 30 * 24 * 60,\n );\n }\n\n if (packageUrls.length > 0) {\n const packageUrlsKey = `package-urls:${registryUrl}:${packageName}`;\n await packageCache.set(\n 'datasource-sbt-package',\n packageUrlsKey,\n packageUrls,\n 30 * 24 * 60,\n );\n }\n\n const versions = [...allVersions];\n if (!versions.length) {\n return null;\n }\n\n const releases: Release[] = [...allVersions]\n .sort(compare)\n .map((version) => ({ version }));\n const res: ReleaseResult = { releases, dependencyUrl };\n\n const latestVersion = getLatestVersion(versions);\n const pomInfo = await this.getPomInfo(\n registryUrl,\n packageName,\n latestVersion,\n packageUrls,\n );\n\n if (pomInfo?.homepage) {\n res.homepage = pomInfo.homepage;\n }\n\n if (pomInfo?.sourceUrl) {\n res.sourceUrl = pomInfo.sourceUrl;\n }\n\n return res;\n }\n\n async getPomInfo(\n registryUrl: string,\n packageName: string,\n version: string | null,\n pkgUrls?: string[],\n ): Promise<PomInfo | null> {\n const packageUrlsKey = `package-urls:${registryUrl}:${packageName}`;\n // istanbul ignore next: will be covered later\n const packageUrls =\n pkgUrls ??\n (await packageCache.get<string[]>(\n 'datasource-sbt-package',\n packageUrlsKey,\n ));\n\n // istanbul ignore if\n if (!packageUrls?.length) {\n return null;\n }\n\n // istanbul ignore if\n if (!version) {\n return null;\n }\n\n const invalidPomFilesKey = `invalid-pom-files:${registryUrl}:${packageName}:${version}`;\n const invalidPomFiles = new Set(\n await packageCache.get<string[]>(\n 'datasource-sbt-package',\n invalidPomFilesKey,\n ),\n );\n\n async function saveCache(): Promise<void> {\n if (invalidPomFiles.size > 0) {\n await packageCache.set(\n 'datasource-sbt-package',\n invalidPomFilesKey,\n [...invalidPomFiles],\n 30 * 24 * 60,\n );\n }\n }\n\n for (const packageUrl of packageUrls) {\n const artifactDir = upath.basename(packageUrl);\n const [artifact] = artifactDir.split('_');\n\n for (const pomFilePrefix of [artifactDir, artifact]) {\n const pomFileName = `${pomFilePrefix}-${version}.pom`;\n const pomUrl = `${packageUrl}${version}/${pomFileName}`;\n if (invalidPomFiles.has(pomUrl)) {\n continue;\n }\n\n const res = await downloadHttpProtocol(this.http, pomUrl);\n const { val } = res.unwrap();\n if (!val) {\n invalidPomFiles.add(pomUrl);\n continue;\n }\n\n const result: PomInfo = {};\n\n const releaseTimestamp = asTimestamp(val.lastModified);\n if (releaseTimestamp) {\n result.releaseTimestamp = releaseTimestamp;\n }\n\n const pomXml = new XmlDocument(val.data);\n\n const homepage = pomXml.valueWithPath('url');\n if (homepage) {\n result.homepage = homepage;\n }\n\n const sourceUrl = pomXml.valueWithPath('scm.url');\n if (sourceUrl) {\n result.sourceUrl = sourceUrl\n .replace(regEx(/^scm:/), '')\n .replace(regEx(/^git:/), '')\n .replace(regEx(/^git@github.com:/), 'https://github.com/')\n .replace(regEx(/\\.git$/), '');\n }\n\n await saveCache();\n return result;\n }\n }\n\n await saveCache();\n return null;\n }\n\n override async getReleases(\n config: GetReleasesConfig,\n ): Promise<ReleaseResult | null> {\n const { packageName, registryUrl } = config;\n // istanbul ignore if\n if (!registryUrl) {\n return null;\n }\n\n const sbtReleases = await this.getSbtReleases(registryUrl, packageName);\n if (sbtReleases) {\n return sbtReleases;\n }\n\n logger.debug(\n `Sbt: no versions discovered for ${packageName} listing organization root package folder, fallback to maven datasource for version discovery`,\n );\n const mavenReleaseResult = await super.getReleases(config);\n if (mavenReleaseResult) {\n return mavenReleaseResult;\n }\n\n logger.debug(`Sbt: no versions found for \"${packageName}\"`);\n return null;\n }\n\n private async _postprocessRelease(\n config: PostprocessReleaseConfig,\n release: Release,\n ): Promise<PostprocessReleaseResult> {\n /* v8 ignore next -- should never happen */\n if (!config.registryUrl) {\n return release;\n }\n\n const res = await this.getPomInfo(\n config.registryUrl,\n config.packageName,\n release.version,\n );\n\n if (res?.releaseTimestamp) {\n release.releaseTimestamp = res.releaseTimestamp;\n }\n\n return release;\n }\n\n override postprocessRelease(\n config: PostprocessReleaseConfig,\n release: Release,\n ): Promise<PostprocessReleaseResult> {\n return withCache(\n {\n namespace: 'datasource-sbt-package',\n key: `postprocessRelease:${config.registryUrl}:${config.packageName}:${release.version}`,\n ttlMinutes: 30 * 24 * 60,\n },\n () => this._postprocessRelease(config, release),\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["packageCache.get","packageCache.set"],"sources":["../../../../lib/modules/datasource/sbt-package/index.ts"],"sourcesContent":["import upath from 'upath';\nimport { XmlDocument } from 'xmldoc';\nimport { logger } from '../../../logger/index.ts';\nimport * as packageCache from '../../../util/cache/package/index.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { Http } from '../../../util/http/index.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport type { Timestamp } from '../../../util/timestamp.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport {\n ensureTrailingSlash,\n parseUrl,\n trimTrailingSlash,\n} from '../../../util/url.ts';\nimport * as ivyVersioning from '../../versioning/ivy/index.ts';\nimport { compare } from '../../versioning/maven/compare.ts';\nimport { MAVEN_REPO } from '../maven/common.ts';\nimport { MavenDatasource } from '../maven/index.ts';\nimport { downloadHttpContent, downloadHttpProtocol } from '../maven/util.ts';\nimport type {\n GetReleasesConfig,\n PostprocessReleaseConfig,\n PostprocessReleaseResult,\n RegistryStrategy,\n Release,\n ReleaseResult,\n} from '../types.ts';\nimport { extractPageLinks, getLatestVersion } from './util.ts';\n\ninterface ScalaDepCoordinate {\n groupId: string;\n artifactId: string;\n scalaVersion?: string;\n}\n\ninterface PomInfo {\n homepage?: string;\n sourceUrl?: string;\n releaseTimestamp?: Timestamp;\n}\n\nexport class SbtPackageDatasource extends MavenDatasource {\n static override readonly id = 'sbt-package';\n\n override readonly defaultRegistryUrls = [MAVEN_REPO];\n\n override readonly defaultVersioning = ivyVersioning.id;\n\n override readonly registryStrategy: RegistryStrategy = 'hunt';\n\n override readonly sourceUrlSupport = 'package';\n override readonly sourceUrlNote =\n 'The source URL is determined from the `scm` tags in the results.';\n\n constructor(id = SbtPackageDatasource.id) {\n super(id);\n this.http = new Http('sbt');\n }\n\n protected static parseDepCoordinate(packageName: string): ScalaDepCoordinate {\n const [groupId, javaArtifactId] = packageName.split(':');\n const [artifactId, scalaVersion] = javaArtifactId.split('_');\n return { groupId, artifactId, scalaVersion };\n }\n\n async getSbtReleases(\n registryUrl: string,\n packageName: string,\n ): Promise<ReleaseResult | null> {\n const { groupId, artifactId, scalaVersion } =\n SbtPackageDatasource.parseDepCoordinate(packageName);\n\n const groupIdSplit = groupId.split('.');\n const repoRootUrl = ensureTrailingSlash(registryUrl);\n\n const validRootUrlKey = `valid-root-url:${registryUrl}:${packageName}`;\n const validRootUrl = await packageCache.get<string>(\n 'datasource-sbt-package',\n validRootUrlKey,\n );\n\n const packageRootUrls: string[] = [];\n // istanbul ignore if: not easily testable\n if (validRootUrl) {\n packageRootUrls.push(validRootUrl);\n } else {\n function packageRootUrlWith(sep: string): string {\n return `${repoRootUrl}${groupIdSplit.join(sep)}`;\n }\n packageRootUrls.push(ensureTrailingSlash(packageRootUrlWith('/')));\n packageRootUrls.push(ensureTrailingSlash(packageRootUrlWith('.')));\n }\n\n let dependencyUrl: string | undefined;\n let packageUrls: string[] | undefined;\n for (const packageRootUrl of packageRootUrls) {\n const packageRootContent = await downloadHttpContent(\n this.http,\n packageRootUrl,\n );\n if (!packageRootContent) {\n continue;\n }\n\n await packageCache.set(\n 'datasource-sbt-package',\n validRootUrlKey,\n packageRootUrl,\n 30 * 24 * 60,\n );\n\n dependencyUrl = trimTrailingSlash(packageRootUrl);\n\n const parsedPackageRootUrl = parseUrl(packageRootUrl);\n if (!parsedPackageRootUrl) {\n logger.warn({ packageRootUrl }, 'Failed to parse packageURL');\n continue;\n }\n const rootPath = parsedPackageRootUrl.pathname;\n const artifactSubdirs = extractPageLinks(packageRootContent, (href) => {\n const path = href.replace(rootPath, '');\n\n if (\n path.startsWith(`${artifactId}_native`) ||\n path.startsWith(`${artifactId}_sjs`)\n ) {\n return null;\n }\n\n if (path === artifactId || path.startsWith(`${artifactId}_`)) {\n return ensureTrailingSlash(`${packageRootUrl}${path}`);\n }\n\n return null;\n });\n\n if (scalaVersion) {\n const scalaSubdir = artifactSubdirs.find((x) =>\n x.endsWith(`/${artifactId}_${scalaVersion}/`),\n );\n // v8 ignore else -- needs a nested directory listing fixture\n if (scalaSubdir) {\n packageUrls = [scalaSubdir];\n break;\n }\n }\n\n packageUrls = artifactSubdirs;\n break;\n }\n\n if (!packageUrls) {\n return null;\n }\n\n const invalidPackageUrlsKey = `invalid-package-urls:${registryUrl}:${packageName}`;\n const invalidPackageUrls = new Set(\n await packageCache.get<string[]>(\n 'datasource-sbt-package',\n invalidPackageUrlsKey,\n ),\n );\n packageUrls = packageUrls.filter((url) => !invalidPackageUrls.has(url));\n\n const allVersions = new Set<string>();\n for (const pkgUrl of packageUrls) {\n const parsedPkgUrl = parseUrl(pkgUrl);\n if (!parsedPkgUrl) {\n invalidPackageUrls.add(pkgUrl);\n continue;\n }\n\n const packageContent = await downloadHttpContent(this.http, pkgUrl);\n // istanbul ignore if\n if (!packageContent) {\n invalidPackageUrls.add(pkgUrl);\n continue;\n }\n\n const rootPath = parsedPkgUrl.pathname;\n const versions = extractPageLinks(packageContent, (href) => {\n const path = href.replace(rootPath, '');\n if (path.startsWith('.')) {\n return null;\n }\n\n return path;\n });\n\n for (const version of versions) {\n allVersions.add(version);\n }\n }\n\n if (invalidPackageUrls.size > 0) {\n await packageCache.set(\n 'datasource-sbt-package',\n invalidPackageUrlsKey,\n [...invalidPackageUrls],\n 30 * 24 * 60,\n );\n }\n\n // v8 ignore else -- an empty list returns before reaching here\n if (packageUrls.length > 0) {\n const packageUrlsKey = `package-urls:${registryUrl}:${packageName}`;\n await packageCache.set(\n 'datasource-sbt-package',\n packageUrlsKey,\n packageUrls,\n 30 * 24 * 60,\n );\n }\n\n const versions = [...allVersions];\n if (!versions.length) {\n return null;\n }\n\n const releases: Release[] = [...allVersions]\n .sort(compare)\n .map((version) => ({ version }));\n const res: ReleaseResult = { releases, dependencyUrl };\n\n const latestVersion = getLatestVersion(versions);\n const pomInfo = await this.getPomInfo(\n registryUrl,\n packageName,\n latestVersion,\n packageUrls,\n );\n\n if (pomInfo?.homepage) {\n res.homepage = pomInfo.homepage;\n }\n\n if (pomInfo?.sourceUrl) {\n res.sourceUrl = pomInfo.sourceUrl;\n }\n\n return res;\n }\n\n async getPomInfo(\n registryUrl: string,\n packageName: string,\n version: string | null,\n pkgUrls?: string[],\n ): Promise<PomInfo | null> {\n const packageUrlsKey = `package-urls:${registryUrl}:${packageName}`;\n // istanbul ignore next: will be covered later\n const packageUrls =\n pkgUrls ??\n (await packageCache.get<string[]>(\n 'datasource-sbt-package',\n packageUrlsKey,\n ));\n\n // istanbul ignore if\n if (!packageUrls?.length) {\n return null;\n }\n\n // istanbul ignore if\n if (!version) {\n return null;\n }\n\n const invalidPomFilesKey = `invalid-pom-files:${registryUrl}:${packageName}:${version}`;\n const invalidPomFiles = new Set(\n await packageCache.get<string[]>(\n 'datasource-sbt-package',\n invalidPomFilesKey,\n ),\n );\n\n async function saveCache(): Promise<void> {\n if (invalidPomFiles.size > 0) {\n await packageCache.set(\n 'datasource-sbt-package',\n invalidPomFilesKey,\n [...invalidPomFiles],\n 30 * 24 * 60,\n );\n }\n }\n\n for (const packageUrl of packageUrls) {\n const artifactDir = upath.basename(packageUrl);\n const [artifact] = artifactDir.split('_');\n\n for (const pomFilePrefix of [artifactDir, artifact]) {\n const pomFileName = `${pomFilePrefix}-${version}.pom`;\n const pomUrl = `${packageUrl}${version}/${pomFileName}`;\n if (invalidPomFiles.has(pomUrl)) {\n continue;\n }\n\n const res = await downloadHttpProtocol(this.http, pomUrl);\n const { val } = res.unwrap();\n if (!val) {\n invalidPomFiles.add(pomUrl);\n continue;\n }\n\n const result: PomInfo = {};\n\n const releaseTimestamp = asTimestamp(val.lastModified);\n if (releaseTimestamp) {\n result.releaseTimestamp = releaseTimestamp;\n }\n\n const pomXml = new XmlDocument(val.data);\n\n const homepage = pomXml.valueWithPath('url');\n if (homepage) {\n result.homepage = homepage;\n }\n\n const sourceUrl = pomXml.valueWithPath('scm.url');\n if (sourceUrl) {\n result.sourceUrl = sourceUrl\n .replace(regEx(/^scm:/), '')\n .replace(regEx(/^git:/), '')\n .replace(regEx(/^git@github.com:/), 'https://github.com/')\n .replace(regEx(/\\.git$/), '');\n }\n\n await saveCache();\n return result;\n }\n }\n\n await saveCache();\n return null;\n }\n\n override async getReleases(\n config: GetReleasesConfig,\n ): Promise<ReleaseResult | null> {\n const { packageName, registryUrl } = config;\n // istanbul ignore if\n if (!registryUrl) {\n return null;\n }\n\n const sbtReleases = await this.getSbtReleases(registryUrl, packageName);\n if (sbtReleases) {\n return sbtReleases;\n }\n\n logger.debug(\n `Sbt: no versions discovered for ${packageName} listing organization root package folder, fallback to maven datasource for version discovery`,\n );\n const mavenReleaseResult = await super.getReleases(config);\n if (mavenReleaseResult) {\n return mavenReleaseResult;\n }\n\n logger.debug(`Sbt: no versions found for \"${packageName}\"`);\n return null;\n }\n\n private async _postprocessRelease(\n config: PostprocessReleaseConfig,\n release: Release,\n ): Promise<PostprocessReleaseResult> {\n /* v8 ignore next -- should never happen */\n if (!config.registryUrl) {\n return release;\n }\n\n const res = await this.getPomInfo(\n config.registryUrl,\n config.packageName,\n release.version,\n );\n\n // v8 ignore else -- needs a pom fixture carrying no timestamp\n if (res?.releaseTimestamp) {\n release.releaseTimestamp = res.releaseTimestamp;\n }\n\n return release;\n }\n\n override postprocessRelease(\n config: PostprocessReleaseConfig,\n release: Release,\n ): Promise<PostprocessReleaseResult> {\n return withCache(\n {\n namespace: 'datasource-sbt-package',\n key: `postprocessRelease:${config.registryUrl}:${config.packageName}:${release.version}`,\n ttlMinutes: 30 * 24 * 60,\n },\n () => this._postprocessRelease(config, release),\n );\n }\n}\n"],"mappings":"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"}
@@ -92,15 +92,18 @@ var SbtPluginDatasource = class SbtPluginDatasource extends Datasource {
92
92
  for (const searchVersion of searchVersions) {
93
93
  const searchSubRoot = `${searchRoot}/scala_${searchVersion}`;
94
94
  const subRootContent = await downloadHttpContent(this.http, ensureTrailingSlash(searchSubRoot));
95
+ // v8 ignore else -- needs a nested directory listing fixture
95
96
  if (subRootContent) {
96
97
  const sbtVersionItems = extractPageLinks(subRootContent, hrefFilterMap);
97
98
  for (const sbtItem of sbtVersionItems) {
98
99
  const releasesRoot = `${searchSubRoot}/${sbtItem}`;
99
100
  const releasesIndexContent = await downloadHttpContent(this.http, ensureTrailingSlash(releasesRoot));
101
+ // v8 ignore else -- needs a nested directory listing fixture
100
102
  if (releasesIndexContent) extractPageLinks(releasesIndexContent, hrefFilterMap).forEach((x) => releases.push(x));
101
103
  }
102
104
  }
103
105
  }
106
+ // v8 ignore else -- needs a nested directory listing fixture
104
107
  if (releases.length) return [...new Set(releases)].sort(compare);
105
108
  }
106
109
  return null;