renovate 44.210.2 → 44.211.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 (307) hide show
  1. package/dist/config/options/index.js +1 -1
  2. package/dist/config/options/index.js.map +1 -1
  3. package/dist/config/utils.js +2 -1
  4. package/dist/config/utils.js.map +1 -1
  5. package/dist/config/validation-helpers/regex-glob-matchers.js.map +1 -1
  6. package/dist/config/validation-helpers/utils.js +5 -1
  7. package/dist/config/validation-helpers/utils.js.map +1 -1
  8. package/dist/config/validation.js +8 -2
  9. package/dist/config/validation.js.map +1 -1
  10. package/dist/config-validator.js +1 -1
  11. package/dist/config-validator.js.map +1 -1
  12. package/dist/global-config-option-defaults.generated.js +1 -1
  13. package/dist/global-config-option-defaults.generated.js.map +1 -1
  14. package/dist/instrumentation/index.js +3 -2
  15. package/dist/instrumentation/index.js.map +1 -1
  16. package/dist/logger/bunyan.js +3 -2
  17. package/dist/logger/bunyan.js.map +1 -1
  18. package/dist/logger/pretty-stdout.js +2 -1
  19. package/dist/logger/pretty-stdout.js.map +1 -1
  20. package/dist/modules/datasource/apk/index.js +2 -2
  21. package/dist/modules/datasource/apk/index.js.map +1 -1
  22. package/dist/modules/datasource/artifactory/index.js +2 -3
  23. package/dist/modules/datasource/artifactory/index.js.map +1 -1
  24. package/dist/modules/datasource/bazel/index.js +2 -1
  25. package/dist/modules/datasource/bazel/index.js.map +1 -1
  26. package/dist/modules/datasource/cdnjs/index.js +2 -1
  27. package/dist/modules/datasource/cdnjs/index.js.map +1 -1
  28. package/dist/modules/datasource/conda/index.js +2 -1
  29. package/dist/modules/datasource/conda/index.js.map +1 -1
  30. package/dist/modules/datasource/crate/index.js +2 -1
  31. package/dist/modules/datasource/crate/index.js.map +1 -1
  32. package/dist/modules/datasource/dart-version/index.js +2 -1
  33. package/dist/modules/datasource/dart-version/index.js.map +1 -1
  34. package/dist/modules/datasource/datasource.js +2 -2
  35. package/dist/modules/datasource/datasource.js.map +1 -1
  36. package/dist/modules/datasource/deb/url.js +3 -1
  37. package/dist/modules/datasource/deb/url.js.map +1 -1
  38. package/dist/modules/datasource/devbox/index.js +2 -1
  39. package/dist/modules/datasource/devbox/index.js.map +1 -1
  40. package/dist/modules/datasource/docker/common.js +10 -8
  41. package/dist/modules/datasource/docker/common.js.map +1 -1
  42. package/dist/modules/datasource/docker/ecr.js +2 -1
  43. package/dist/modules/datasource/docker/ecr.js.map +1 -1
  44. package/dist/modules/datasource/docker/index.js +20 -17
  45. package/dist/modules/datasource/docker/index.js.map +1 -1
  46. package/dist/modules/datasource/gitea-tags/base.js +2 -3
  47. package/dist/modules/datasource/gitea-tags/base.js.map +1 -1
  48. package/dist/modules/datasource/github-tags/index.js +2 -1
  49. package/dist/modules/datasource/github-tags/index.js.map +1 -1
  50. package/dist/modules/datasource/gitlab-tags/index.js +2 -3
  51. package/dist/modules/datasource/gitlab-tags/index.js.map +1 -1
  52. package/dist/modules/datasource/go/base.js +1 -1
  53. package/dist/modules/datasource/go/base.js.map +1 -1
  54. package/dist/modules/datasource/go/common.js +8 -2
  55. package/dist/modules/datasource/go/common.js.map +1 -1
  56. package/dist/modules/datasource/go/env.js +2 -1
  57. package/dist/modules/datasource/go/env.js.map +1 -1
  58. package/dist/modules/datasource/go/releases-goproxy.js +7 -5
  59. package/dist/modules/datasource/go/releases-goproxy.js.map +1 -1
  60. package/dist/modules/datasource/helm/index.js +2 -1
  61. package/dist/modules/datasource/helm/index.js.map +1 -1
  62. package/dist/modules/datasource/hexpm-bob/index.js +2 -1
  63. package/dist/modules/datasource/hexpm-bob/index.js.map +1 -1
  64. package/dist/modules/datasource/index.js +4 -3
  65. package/dist/modules/datasource/index.js.map +1 -1
  66. package/dist/modules/datasource/java-version/adoptium.js +3 -2
  67. package/dist/modules/datasource/java-version/adoptium.js.map +1 -1
  68. package/dist/modules/datasource/java-version/graalvm.js +2 -1
  69. package/dist/modules/datasource/java-version/graalvm.js.map +1 -1
  70. package/dist/modules/datasource/maven/index.js.map +1 -1
  71. package/dist/modules/datasource/maven/util.js +7 -10
  72. package/dist/modules/datasource/maven/util.js.map +1 -1
  73. package/dist/modules/datasource/npm/npmrc.js +2 -2
  74. package/dist/modules/datasource/npm/npmrc.js.map +1 -1
  75. package/dist/modules/datasource/nuget/v3.js +3 -5
  76. package/dist/modules/datasource/nuget/v3.js.map +1 -1
  77. package/dist/modules/datasource/packagist/index.js +4 -5
  78. package/dist/modules/datasource/packagist/index.js.map +1 -1
  79. package/dist/modules/datasource/pod/index.js +4 -4
  80. package/dist/modules/datasource/pod/index.js.map +1 -1
  81. package/dist/modules/datasource/pypi/index.js +19 -11
  82. package/dist/modules/datasource/pypi/index.js.map +1 -1
  83. package/dist/modules/datasource/python-version/index.js +2 -3
  84. package/dist/modules/datasource/python-version/index.js.map +1 -1
  85. package/dist/modules/datasource/repology/index.js +4 -3
  86. package/dist/modules/datasource/repology/index.js.map +1 -1
  87. package/dist/modules/datasource/rpm/providers/common.js +3 -1
  88. package/dist/modules/datasource/rpm/providers/common.js.map +1 -1
  89. package/dist/modules/datasource/rubygems/index.js +2 -5
  90. package/dist/modules/datasource/rubygems/index.js.map +1 -1
  91. package/dist/modules/datasource/rubygems/versions-endpoint-cache.js +13 -18
  92. package/dist/modules/datasource/rubygems/versions-endpoint-cache.js.map +1 -1
  93. package/dist/modules/datasource/terraform-module/base.js +2 -1
  94. package/dist/modules/datasource/terraform-module/base.js.map +1 -1
  95. package/dist/modules/datasource/terraform-provider/index.js +2 -3
  96. package/dist/modules/datasource/terraform-provider/index.js.map +1 -1
  97. package/dist/modules/manager/bazel-module/parser/context.js +0 -1
  98. package/dist/modules/manager/bazel-module/parser/context.js.map +1 -1
  99. package/dist/modules/manager/bundler/host-rules.js +4 -1
  100. package/dist/modules/manager/bundler/host-rules.js.map +1 -1
  101. package/dist/modules/manager/composer/range.js +1 -1
  102. package/dist/modules/manager/composer/range.js.map +1 -1
  103. package/dist/modules/manager/custom/utils.js +3 -1
  104. package/dist/modules/manager/custom/utils.js.map +1 -1
  105. package/dist/modules/manager/devbox/schema.js +2 -1
  106. package/dist/modules/manager/devbox/schema.js.map +1 -1
  107. package/dist/modules/manager/dockerfile/extract.js +10 -2
  108. package/dist/modules/manager/dockerfile/extract.js.map +1 -1
  109. package/dist/modules/manager/fingerprint.generated.js +1 -1
  110. package/dist/modules/manager/fingerprint.generated.js.map +1 -1
  111. package/dist/modules/manager/github-actions/parse.js +2 -1
  112. package/dist/modules/manager/github-actions/parse.js.map +1 -1
  113. package/dist/modules/manager/gomod/artifacts.js +5 -2
  114. package/dist/modules/manager/gomod/artifacts.js.map +1 -1
  115. package/dist/modules/manager/gradle/artifacts.js +5 -2
  116. package/dist/modules/manager/gradle/artifacts.js.map +1 -1
  117. package/dist/modules/manager/gradle/utils.js +2 -1
  118. package/dist/modules/manager/gradle/utils.js.map +1 -1
  119. package/dist/modules/manager/gradle-wrapper/utils.js +9 -5
  120. package/dist/modules/manager/gradle-wrapper/utils.js.map +1 -1
  121. package/dist/modules/manager/helm-values/util.js +1 -1
  122. package/dist/modules/manager/helm-values/util.js.map +1 -1
  123. package/dist/modules/manager/helmfile/artifacts.js +1 -1
  124. package/dist/modules/manager/helmfile/artifacts.js.map +1 -1
  125. package/dist/modules/manager/helmv3/artifacts.js +1 -1
  126. package/dist/modules/manager/helmv3/artifacts.js.map +1 -1
  127. package/dist/modules/manager/maven/extract.js +5 -2
  128. package/dist/modules/manager/maven/extract.js.map +1 -1
  129. package/dist/modules/manager/maven-wrapper/artifacts.js +3 -2
  130. package/dist/modules/manager/maven-wrapper/artifacts.js.map +1 -1
  131. package/dist/modules/manager/mise/artifacts.js +2 -1
  132. package/dist/modules/manager/mise/artifacts.js.map +1 -1
  133. package/dist/modules/manager/mise/extract.js.map +1 -1
  134. package/dist/modules/manager/npm/artifacts.js +3 -1
  135. package/dist/modules/manager/npm/artifacts.js.map +1 -1
  136. package/dist/modules/manager/npm/extract/common/dependency.js +4 -1
  137. package/dist/modules/manager/npm/extract/common/dependency.js.map +1 -1
  138. package/dist/modules/manager/npm/extract/common/package-file.js +2 -1
  139. package/dist/modules/manager/npm/extract/common/package-file.js.map +1 -1
  140. package/dist/modules/manager/npm/extract/index.js.map +1 -1
  141. package/dist/modules/manager/npm/extract/post/locked-versions.js.map +1 -1
  142. package/dist/modules/manager/npm/npmrc.js +2 -1
  143. package/dist/modules/manager/npm/npmrc.js.map +1 -1
  144. package/dist/modules/manager/npm/post-update/index.js +2 -1
  145. package/dist/modules/manager/npm/post-update/index.js.map +1 -1
  146. package/dist/modules/manager/npm/post-update/npm.js +2 -3
  147. package/dist/modules/manager/npm/post-update/npm.js.map +1 -1
  148. package/dist/modules/manager/npm/post-update/yarn.js.map +1 -1
  149. package/dist/modules/manager/npm/schema.js +6 -2
  150. package/dist/modules/manager/npm/schema.js.map +1 -1
  151. package/dist/modules/manager/npm/update/dependency/index.js +3 -2
  152. package/dist/modules/manager/npm/update/dependency/index.js.map +1 -1
  153. package/dist/modules/manager/npm/update/locked-dependency/common/parent-version.js +6 -2
  154. package/dist/modules/manager/npm/update/locked-dependency/common/parent-version.js.map +1 -1
  155. package/dist/modules/manager/npm/update/locked-dependency/package-lock/get-locked.js +2 -1
  156. package/dist/modules/manager/npm/update/locked-dependency/package-lock/get-locked.js.map +1 -1
  157. package/dist/modules/manager/nuget/package-tree.js +2 -1
  158. package/dist/modules/manager/nuget/package-tree.js.map +1 -1
  159. package/dist/modules/manager/paket/update-locked.js +4 -1
  160. package/dist/modules/manager/paket/update-locked.js.map +1 -1
  161. package/dist/modules/manager/pipenv/extract.js +4 -1
  162. package/dist/modules/manager/pipenv/extract.js.map +1 -1
  163. package/dist/modules/platform/azure/azure-helper.js +2 -1
  164. package/dist/modules/platform/azure/azure-helper.js.map +1 -1
  165. package/dist/modules/platform/azure/index.js +7 -3
  166. package/dist/modules/platform/azure/index.js.map +1 -1
  167. package/dist/modules/platform/bitbucket/index.js +13 -6
  168. package/dist/modules/platform/bitbucket/index.js.map +1 -1
  169. package/dist/modules/platform/bitbucket-server/index.js +13 -9
  170. package/dist/modules/platform/bitbucket-server/index.js.map +1 -1
  171. package/dist/modules/platform/codecommit/index.js +2 -2
  172. package/dist/modules/platform/codecommit/index.js.map +1 -1
  173. package/dist/modules/platform/gerrit/client.js +4 -1
  174. package/dist/modules/platform/gerrit/client.js.map +1 -1
  175. package/dist/modules/platform/gerrit/index.js +3 -2
  176. package/dist/modules/platform/gerrit/index.js.map +1 -1
  177. package/dist/modules/platform/gitea/gitea-helper.js +5 -4
  178. package/dist/modules/platform/gitea/gitea-helper.js.map +1 -1
  179. package/dist/modules/platform/gitea/index.js +11 -4
  180. package/dist/modules/platform/gitea/index.js.map +1 -1
  181. package/dist/modules/platform/github/index.js +36 -23
  182. package/dist/modules/platform/github/index.js.map +1 -1
  183. package/dist/modules/platform/gitlab/index.js +16 -9
  184. package/dist/modules/platform/gitlab/index.js.map +1 -1
  185. package/dist/modules/platform/scm-manager/index.js +4 -1
  186. package/dist/modules/platform/scm-manager/index.js.map +1 -1
  187. package/dist/modules/platform/scm-manager/utils.js +2 -1
  188. package/dist/modules/platform/scm-manager/utils.js.map +1 -1
  189. package/dist/modules/versioning/cargo/index.js.map +1 -1
  190. package/dist/modules/versioning/composer/index.js +2 -1
  191. package/dist/modules/versioning/composer/index.js.map +1 -1
  192. package/dist/modules/versioning/conan/common.js +4 -1
  193. package/dist/modules/versioning/conan/common.js.map +1 -1
  194. package/dist/modules/versioning/debian/index.js +2 -1
  195. package/dist/modules/versioning/debian/index.js.map +1 -1
  196. package/dist/modules/versioning/gradle/compare.js +2 -1
  197. package/dist/modules/versioning/gradle/compare.js.map +1 -1
  198. package/dist/modules/versioning/hex/index.js +2 -1
  199. package/dist/modules/versioning/hex/index.js.map +1 -1
  200. package/dist/modules/versioning/julia/index.js.map +1 -1
  201. package/dist/modules/versioning/maven/compare.js +17 -7
  202. package/dist/modules/versioning/maven/compare.js.map +1 -1
  203. package/dist/modules/versioning/npm/index.js +2 -1
  204. package/dist/modules/versioning/npm/index.js.map +1 -1
  205. package/dist/modules/versioning/npm/range.js.map +1 -1
  206. package/dist/modules/versioning/paket/index.js +2 -1
  207. package/dist/modules/versioning/paket/index.js.map +1 -1
  208. package/dist/modules/versioning/paket/range.js +7 -3
  209. package/dist/modules/versioning/paket/range.js.map +1 -1
  210. package/dist/modules/versioning/pep440/index.js +2 -1
  211. package/dist/modules/versioning/pep440/index.js.map +1 -1
  212. package/dist/modules/versioning/poetry/index.js +2 -1
  213. package/dist/modules/versioning/poetry/index.js.map +1 -1
  214. package/dist/modules/versioning/regex/index.js +1 -1
  215. package/dist/modules/versioning/regex/index.js.map +1 -1
  216. package/dist/modules/versioning/rez/index.js +2 -1
  217. package/dist/modules/versioning/rez/index.js.map +1 -1
  218. package/dist/modules/versioning/vcpkg/index.js +2 -1
  219. package/dist/modules/versioning/vcpkg/index.js.map +1 -1
  220. package/dist/util/cache/package/impl/base.js +2 -1
  221. package/dist/util/cache/package/impl/base.js.map +1 -1
  222. package/dist/util/cache/package/with-cache.js +2 -1
  223. package/dist/util/cache/package/with-cache.js.map +1 -1
  224. package/dist/util/check-token.js +7 -4
  225. package/dist/util/check-token.js.map +1 -1
  226. package/dist/util/exec/utils.js +4 -1
  227. package/dist/util/exec/utils.js.map +1 -1
  228. package/dist/util/git/auth.js +5 -1
  229. package/dist/util/git/auth.js.map +1 -1
  230. package/dist/util/git/error.js.map +1 -1
  231. package/dist/util/host-rules.js +5 -3
  232. package/dist/util/host-rules.js.map +1 -1
  233. package/dist/util/http/cache/abstract-http-cache-provider.js +3 -2
  234. package/dist/util/http/cache/abstract-http-cache-provider.js.map +1 -1
  235. package/dist/util/http/cache/package-http-cache-provider.js +2 -1
  236. package/dist/util/http/cache/package-http-cache-provider.js.map +1 -1
  237. package/dist/util/http/github.js +15 -11
  238. package/dist/util/http/github.js.map +1 -1
  239. package/dist/util/http/gitlab.js +5 -4
  240. package/dist/util/http/gitlab.js.map +1 -1
  241. package/dist/util/http/http.js +2 -1
  242. package/dist/util/http/http.js.map +1 -1
  243. package/dist/util/http/jwt.js +2 -1
  244. package/dist/util/http/jwt.js.map +1 -1
  245. package/dist/util/http/retry-after.js +2 -1
  246. package/dist/util/http/retry-after.js.map +1 -1
  247. package/dist/util/http/status.js +95 -0
  248. package/dist/util/http/status.js.map +1 -0
  249. package/dist/util/merge-confidence/index.js +3 -2
  250. package/dist/util/merge-confidence/index.js.map +1 -1
  251. package/dist/util/package-rules/current-version.js +4 -1
  252. package/dist/util/package-rules/current-version.js.map +1 -1
  253. package/dist/util/package-rules/index.js +7 -3
  254. package/dist/util/package-rules/index.js.map +1 -1
  255. package/dist/util/schema-utils/index.js +5 -0
  256. package/dist/util/schema-utils/index.js.map +1 -1
  257. package/dist/workers/global/config/parse/host-rules-from-env.js +2 -1
  258. package/dist/workers/global/config/parse/host-rules-from-env.js.map +1 -1
  259. package/dist/workers/repository/common.js +4 -1
  260. package/dist/workers/repository/common.js.map +1 -1
  261. package/dist/workers/repository/config-migration/branch/index.js +2 -1
  262. package/dist/workers/repository/config-migration/branch/index.js.map +1 -1
  263. package/dist/workers/repository/dependency-dashboard.js +6 -2
  264. package/dist/workers/repository/dependency-dashboard.js.map +1 -1
  265. package/dist/workers/repository/finalize/repository-statistics.js +4 -1
  266. package/dist/workers/repository/finalize/repository-statistics.js.map +1 -1
  267. package/dist/workers/repository/init/merge.js +13 -3
  268. package/dist/workers/repository/init/merge.js.map +1 -1
  269. package/dist/workers/repository/onboarding/branch/check.js +2 -1
  270. package/dist/workers/repository/onboarding/branch/check.js.map +1 -1
  271. package/dist/workers/repository/onboarding/pr/pr-list.js +4 -2
  272. package/dist/workers/repository/onboarding/pr/pr-list.js.map +1 -1
  273. package/dist/workers/repository/process/extract-update.js +4 -1
  274. package/dist/workers/repository/process/extract-update.js.map +1 -1
  275. package/dist/workers/repository/process/lookup/digest.js +2 -1
  276. package/dist/workers/repository/process/lookup/digest.js.map +1 -1
  277. package/dist/workers/repository/process/lookup/generate.js +1 -1
  278. package/dist/workers/repository/process/lookup/generate.js.map +1 -1
  279. package/dist/workers/repository/process/lookup/index.js +4 -1
  280. package/dist/workers/repository/process/lookup/index.js.map +1 -1
  281. package/dist/workers/repository/process/lookup/utils.js +7 -1
  282. package/dist/workers/repository/process/lookup/utils.js.map +1 -1
  283. package/dist/workers/repository/process/vulnerabilities.js +10 -3
  284. package/dist/workers/repository/process/vulnerabilities.js.map +1 -1
  285. package/dist/workers/repository/reconfigure/index.js +2 -1
  286. package/dist/workers/repository/reconfigure/index.js.map +1 -1
  287. package/dist/workers/repository/update/branch/auto-replace.js +6 -2
  288. package/dist/workers/repository/update/branch/auto-replace.js.map +1 -1
  289. package/dist/workers/repository/update/branch/errors.js +2 -1
  290. package/dist/workers/repository/update/branch/errors.js.map +1 -1
  291. package/dist/workers/repository/update/branch/execute-post-upgrade-commands.js +2 -2
  292. package/dist/workers/repository/update/branch/execute-post-upgrade-commands.js.map +1 -1
  293. package/dist/workers/repository/update/branch/get-updated.js +1 -2
  294. package/dist/workers/repository/update/branch/get-updated.js.map +1 -1
  295. package/dist/workers/repository/update/branch/index.js +2 -1
  296. package/dist/workers/repository/update/branch/index.js.map +1 -1
  297. package/dist/workers/repository/update/branch/status-checks.js +2 -1
  298. package/dist/workers/repository/update/branch/status-checks.js.map +1 -1
  299. package/dist/workers/repository/update/pr/body/index.js.map +1 -1
  300. package/dist/workers/repository/update/pr/changelog/release-notes.js +3 -2
  301. package/dist/workers/repository/update/pr/changelog/release-notes.js.map +1 -1
  302. package/dist/workers/repository/update/pr/index.js +7 -3
  303. package/dist/workers/repository/update/pr/index.js.map +1 -1
  304. package/dist/workers/repository/updates/generate.js +6 -3
  305. package/dist/workers/repository/updates/generate.js.map +1 -1
  306. package/package.json +2 -2
  307. package/renovate-schema.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"host-rules.js","names":[],"sources":["../../lib/util/host-rules.ts"],"sourcesContent":["import {\n isFalsy,\n isNonEmptyString,\n isString,\n isTruthy,\n isUndefined,\n} from '@sindresorhus/is';\nimport { GlobalConfig } from '../config/global.ts';\nimport type { PlatformFamilyId } from '../constants/platforms.ts';\nimport { PLATFORM_FAMILIES } from '../constants/platforms.ts';\nimport { logger } from '../logger/index.ts';\nimport type {\n CombinedHostRule,\n HostRule,\n InternalHostGrant,\n} from '../types/index.ts';\nimport { clone } from './clone.ts';\nimport { toSha256 } from './hash.ts';\nimport { regEx } from './regex.ts';\nimport * as sanitize from './sanitize.ts';\nimport { toBase64 } from './string.ts';\nimport { matchRegexOrGlobList } from './string-match.ts';\nimport {\n decodeUserinfo,\n ensureTrailingSlash,\n isCredentialUserinfo,\n isHttpUrl,\n isPlaceholderUsername,\n isUserinfoPlaceholder,\n massageHostUrl,\n parseUrl,\n stripUrlCredentials,\n trimTrailingSlash,\n urlUserinfoRegex,\n} from './url.ts';\n\n/**\n * How much a host rule is trusted, according to the configuration it came from, from most to least trusted:\n *\n * - `admin`: the self-hosted administrator's own configuration - their global config, or a `repositories[]` entry\n * - `inherit`: organization-inherited config, and only where the administrator has opted into trusting it through `inheritConfigTrusted`\n * - `untrusted`: repository configuration, and the presets it extends\n */\ntype HostRuleTrustTier = 'admin' | 'inherit' | 'untrusted';\n\n/**\n * A host rule as registered through {@link add}.\n *\n * `trustTier` is deliberately not a field of `HostRule`: it is set from {@link AddHostRuleOptions} at registration time and must never be settable through configuration.\n */\ninterface RegisteredHostRule extends HostRule {\n /** which configuration the rule came from - see {@link HostRuleTrustTier}. Always set by `add()`, but optional so that `find()` can delete it from its result */\n trustTier?: HostRuleTrustTier;\n\n /** never set by us, and declared only so that a `trusted` smuggled in through configuration can be deleted before the rule is registered. It was the boolean `trustTier` replaced, so a rule carrying one may well be an attempt at the administrator's precedence */\n trusted?: never;\n\n /** set only by {@link find}; see {@link CombinedHostRuleWithTrustedHeaders.trustedHeaderNames} */\n trustedHeaderNames?: string[];\n}\n\n/**\n * A {@link CombinedHostRule} returned by {@link find}, additionally reporting which of its `headers` came from an `admin` tier rule.\n *\n * `trustedHeaderNames` is deliberately not a field of `HostRule`, for the same reason `trustTier` is not: it must never be settable through configuration.\n */\nexport interface CombinedHostRuleWithTrustedHeaders extends CombinedHostRule {\n /** header names in `headers` that came from an `admin` tier rule (the self-hosted administrator's own config), and so already bypassed `allowedHeaders` at registration - `applyHostRule`'s request-time defence-in-depth must not re-check, and drop, them */\n trustedHeaderNames?: string[];\n}\n\nlet hostRules: RegisteredHostRule[] = [];\n\n/**\n * The credentials of a host rule registered by {@link detachUrlCredentials}.\n */\ninterface DetachedCredentials {\n /** the URL the credentials apply to, without a trailing slash */\n url: string;\n username: string;\n password: string;\n}\n\nlet detachedCredentials: DetachedCredentials[] = [];\n\n/**\n * The host rules registered by {@link detachUrlCredentials}.\n */\nlet detachedRules = new WeakSet<HostRule>();\n\n/**\n * Fields within `HostRule`s that must have their value registered for sanitising through `sanitize.addSecretForSanitizing()`.\n *\n * Kept in sync with `redactedFields` through tests.\n */\nexport const confidentialFields: (keyof HostRule)[] = [\n 'password',\n 'token',\n 'httpsPrivateKey',\n /* not actually sensitive, but redacted nonetheless */\n 'httpsCertificate',\n /* not actually sensitive, but redacted nonetheless */\n 'httpsCertificateAuthority',\n];\n\nexport interface LegacyHostRule {\n hostName?: string;\n domainName?: string;\n baseUrl?: string;\n host?: string;\n endpoint?: string;\n}\n\nexport function migrateRule(rule: LegacyHostRule & HostRule): HostRule {\n const cloned: LegacyHostRule & HostRule = clone(rule);\n delete cloned.hostName;\n delete cloned.domainName;\n delete cloned.baseUrl;\n const result: HostRule = cloned;\n\n const { matchHost } = result;\n const { hostName, domainName, baseUrl } = rule;\n const hostValues = [matchHost, hostName, domainName, baseUrl].filter(\n isTruthy,\n );\n if (hostValues.length === 1) {\n const [matchHost] = hostValues;\n result.matchHost = matchHost;\n } else if (hostValues.length > 1) {\n throw new Error(\n `hostRules cannot contain more than one host-matching field - use \"matchHost\" only.`,\n );\n }\n\n return result;\n}\n\n/**\n * Enforce the `allowedHeaders` allowlist on a set of host rules.\n *\n * Loudly remove anything that's not permitted, logging a WARN. Used by `add()` for an untrusted rule's `headers`; a `trusted` rule's are exempt, so never reach this.\n *\n * Headers that survive this allowlist are still subject to how {@link find} combines them: an admin's headers for a host are applied over those of any repository or preset rule matching the same request, so a repository can neither drop nor substitute them.\n */\nexport function filterAllowedHeaders(rules: HostRule[]): HostRule[] {\n const allowlist = GlobalConfig.get('allowedHeaders');\n const denied: string[] = [];\n\n const result = rules.map((rule) => {\n if (!rule.headers) {\n return rule;\n }\n\n const allowed: Record<string, string> = {};\n const ruleDenied: string[] = [];\n for (const [name, value] of Object.entries(rule.headers)) {\n if (matchRegexOrGlobList(name, allowlist)) {\n allowed[name] = value;\n } else {\n ruleDenied.push(name);\n }\n }\n\n if (!ruleDenied.length) {\n return rule;\n }\n denied.push(...ruleDenied);\n\n const filtered: HostRule = { ...rule, headers: allowed };\n if (!Object.keys(allowed).length) {\n // drop the key rather than leave an empty object behind: `find()` treats any rule that sets `headers` as replacing the headers of the broader rules of its own tier, so a rule left with none would suppress them\n delete filtered.headers;\n }\n return filtered;\n });\n\n if (denied.length) {\n logger.warn(\n { denied },\n \"Ignoring hostRules headers not permitted by this Renovate instance's `allowedHeaders`\",\n );\n }\n return result;\n}\n\nexport interface AddHostRuleOptions {\n /**\n * Whether this rule comes from the self-hosted administrator's own configuration, rather than from repository or preset config.\n *\n * Registers the rule into the `admin` tier - see {@link HostRuleTrustTier}.\n */\n trusted?: boolean;\n\n /**\n * Whether this rule comes from organization-inherited config which the administrator has opted into trusting, through `inheritConfigTrusted`.\n *\n * Registers the rule into the `inherit` tier - see {@link HostRuleTrustTier}. Ignored when `trusted` is also set.\n */\n inherited?: boolean;\n}\n\n/**\n * The trust tier a caller asked for.\n *\n * Deliberately opt-in: a caller that says nothing registers into the untrusted tier, so a new call site cannot grant itself the administrator's precedence by omission.\n */\nfunction trustTierFor(options?: AddHostRuleOptions): HostRuleTrustTier {\n if (options?.trusted) {\n return 'admin';\n }\n if (options?.inherited) {\n return 'inherit';\n }\n return 'untrusted';\n}\n\n/**\n * Registers a host rule, without registering its confidential fields for sanitizing.\n *\n * @returns the registered rule\n */\nfunction register(\n params: HostRule,\n options?: AddHostRuleOptions,\n): RegisteredHostRule {\n let rule: RegisteredHostRule = {\n ...migrateRule(params),\n // set only from `options`, and assigned unconditionally so that it cannot be carried over from `params`: `HostRule` has no `trustTier` field, but configuration is parsed from JSON, so a repository could otherwise smuggle one in and have its rules treated as the administrator's\n trustTier: trustTierFor(options),\n };\n // as above, for the boolean this tier replaced, and for the header names `find()` computes from it\n delete rule.trusted;\n delete rule.trustedHeaderNames;\n\n // like the trust tier, `allowInternal` may only come from configuration the administrator trusts: a repository or preset rule must not be able to grant itself access to internal hosts\n if (!isUndefined(rule.allowInternal) && rule.trustTier === 'untrusted') {\n logger.debug(\n `Ignoring hostRules allowInternal for ${rule.matchHost ?? rule.hostType} from untrusted config`,\n );\n delete rule.allowInternal;\n }\n\n if (rule.headers && rule.trustTier !== 'admin') {\n // enforced here, at the single registration chokepoint, so that no current or future caller can register a rule whose headers bypass `allowedHeaders`; `applyHostRule` filters by header name again at request time as defence in depth\n // `allowedHeaders` constrains what a repository or preset may set, not the self-hosted administrator - the same exemption `filterAllowedEnv` gives the admin's own `env`, just enforced through the `admin` trust tier here rather than a name+value comparison, as `headers` are host-scoped\n [rule] = filterAllowedHeaders([rule]);\n }\n\n if (rule.matchHost) {\n rule.matchHost = massageHostUrl(rule.matchHost);\n const parsedUrl = parseUrl(rule.matchHost);\n rule.resolvedHost = parsedUrl?.hostname ?? rule.matchHost;\n confidentialFields.forEach((field) => {\n if (rule[field]) {\n logger.debug(\n // TODO: types (#22198)\n `Adding ${field} authentication for ${rule.matchHost!} (hostType=${\n rule.hostType\n }) to hostRules`,\n );\n }\n });\n }\n hostRules.push(rule);\n return rule;\n}\n\nexport function add(params: HostRule, options?: AddHostRuleOptions): void {\n const rule = register(params, options);\n confidentialFields.forEach((field) => {\n const secret = rule[field];\n if (isString(secret) && secret.length > 3) {\n sanitize.addSecretForSanitizing(secret);\n }\n });\n if (rule.username && rule.password) {\n sanitize.addSecretForSanitizing(\n toBase64(`${rule.username}:${rule.password}`),\n );\n }\n}\n\n/**\n * The platform host types whose host rules `findMatchingRule()` falls back to for the other host types of their family.\n */\nconst fallbackPlatformHostTypes = [\n 'bitbucket',\n 'bitbucket-server',\n 'forgejo',\n 'gitea',\n 'github',\n 'gitlab',\n] as const satisfies PlatformFamilyId[];\n\n/**\n * Returns `hostTypes` and the platform host type each of them falls back to.\n */\nfunction withFallbackHostTypes(hostTypes: string[]): string[] {\n const platformHostTypes = fallbackPlatformHostTypes.filter((platform) =>\n PLATFORM_FAMILIES[platform].apiUsingHostTypes.some((hostType) =>\n hostTypes.includes(hostType),\n ),\n );\n return [...hostTypes, ...platformHostTypes];\n}\n\n/**\n * Whether a request of `url` with any `hostType` falls back to the `github` host rules: a request of the GitHub.com API, or of the host of the GitHub platform endpoint, such as GitHub Enterprise Server or GitHub Enterprise Cloud.\n */\nexport function usesGithubUrlFallback(url: string): boolean {\n if (url.startsWith('https://api.github.com/')) {\n return true;\n }\n const platformEndpoint = GlobalConfig.get('endpoint');\n if (GlobalConfig.get('platform') !== 'github' || !platformEndpoint) {\n return false;\n }\n const requestHost = parseUrl(url)?.hostname;\n return !!requestHost && requestHost === parseUrl(platformEndpoint)?.hostname;\n}\n\n/**\n * Whether `rule` was registered by {@link detachUrlCredentials}.\n */\nfunction isDetached(rule: RegisteredHostRule): boolean {\n return detachedRules.has(rule);\n}\n\n/**\n * Returns the host rules other than those registered by {@link detachUrlCredentials}.\n */\nfunction configuredRules(): RegisteredHostRule[] {\n return hostRules.filter((rule) => !isDetached(rule));\n}\n\n/**\n * Whether {@link find} matches `rule` for a search with `readOnly` whatever its `hostType`: when the rule sets `readOnly` to the same value.\n */\nfunction matchesReadOnly(\n rule: HostRule,\n readOnly: boolean | undefined,\n): boolean {\n return !isUndefined(rule.readOnly) && rule.readOnly === readOnly;\n}\n\n/**\n * Whether `rule` applies to the scope `matchHost` of {@link detachUrlCredentials} requested with one of `hostTypes`: a detached rule only for the same `matchHost`, any other rule when it has no `hostType`, one of `hostTypes` or `readOnly: true`, and no `matchHost` or one which matches `matchHost` or lies within it.\n */\nfunction appliesToScope(\n rule: RegisteredHostRule,\n matchHost: string,\n hostTypes: string[],\n): boolean {\n if (isDetached(rule)) {\n return rule.matchHost === matchHost && hostTypes.includes(rule.hostType!);\n }\n // a rule for another `hostType`, such as the platform's for the same host, is not used for these requests\n // unless it is one for read-only requests, which `find()` uses for the lookups whatever their `hostType`\n if (\n rule.hostType &&\n !hostTypes.includes(rule.hostType) &&\n !matchesReadOnly(rule, true)\n ) {\n return false;\n }\n return (\n !rule.matchHost ||\n matchesHost(matchHost, rule.matchHost) ||\n matchesHost(rule.matchHost, matchHost)\n );\n}\n\n/**\n * Whether a host rule which {@link appliesToScope} `matchHost` requested with one of `hostTypes`, the platform host type they fall back to, or `github` when {@link usesGithubUrlFallback} for `matchHost`, supplies credentials.\n */\nfunction hasConfiguredCredentials(\n matchHost: string,\n hostTypes: string[],\n): boolean {\n const searchedHostTypes = withFallbackHostTypes(hostTypes);\n if (usesGithubUrlFallback(matchHost)) {\n searchedHostTypes.push('github');\n }\n return hostRules.some(\n (rule) =>\n appliesToScope(rule, matchHost, searchedHostTypes) &&\n [rule.username, rule.password, rule.token].some(\n (value) => !isUndefined(value),\n ),\n );\n}\n\n/**\n * Returns `text` without the userinfo of the URL at its start, or `text` unchanged when that URL has none.\n */\nfunction withoutLeadingUserinfo(text: string): string {\n // only a userinfo at the start of `text` belongs to that URL, a later one is part of its path or query\n const [match] = text.matchAll(urlUserinfoRegex);\n if (match?.index !== 0) {\n return text;\n }\n return `${match.groups!.scheme}://${text.slice(match[0].length)}`;\n}\n\n/**\n * Returns `url` without its userinfo, spelled as given, or as the parser normalizes it when the two disagree.\n */\nfunction withoutUserinfo(url: string, parsed: URL): string {\n const stripped = withoutLeadingUserinfo(url);\n const reparsed = parseUrl(stripped);\n if (\n reparsed?.host !== parsed.host ||\n reparsed?.username ||\n reparsed?.password\n ) {\n return stripUrlCredentials(parsed);\n }\n return stripped;\n}\n\n/**\n * Returns the parsed `url` when it is an HTTP(S) URL with a userinfo other than a placeholder username alone, or `null`.\n */\nfunction parseUserinfoUrl(url: string): URL | null {\n if (!url.includes('@')) {\n return null;\n }\n const parsed = parseUrl(url);\n if (!parsed || !isHttpUrl(parsed) || (!parsed.username && !parsed.password)) {\n return null;\n }\n // a rule for the placeholder would supply it as a credential and stop the lookup which resolves the real one\n if (!parsed.password && isPlaceholderUsername(parsed.username)) {\n return null;\n }\n return parsed;\n}\n\n/**\n * Whether the password of `parsed` is a template placeholder, or the username when there is no password, as {@link isCredentialUserinfo} reads them.\n */\nfunction hasTemplatePlaceholder(parsed: URL): boolean {\n // the URL parser percent-encodes the braces of a placeholder, which `isUserinfoPlaceholder` decodes\n // a placeholder username next to a literal password does not make that password a placeholder\n return isUserinfoPlaceholder(parsed.password || parsed.username);\n}\n\n/**\n * Whether the userinfo of `parsed` holds a credential according to {@link isCredentialUserinfo}.\n */\nfunction hasCredentialUserinfo(parsed: URL): boolean {\n const scheme = parsed.protocol.slice(0, -1);\n const userinfo = parsed.password\n ? `${parsed.username}:${parsed.password}`\n : parsed.username;\n return isCredentialUserinfo(\n scheme,\n userinfo,\n `${parsed.host}${parsed.pathname}`,\n );\n}\n\n/**\n * The minimum length of a credential from a URL which is registered for sanitizing.\n */\nconst minSanitizedUrlSecretLength = 16;\n\n/**\n * Registers each of `secrets` for sanitizing which is at least {@link minSanitizedUrlSecretLength} characters long.\n */\nfunction addUrlSecretsForSanitizing(secrets: string[]): void {\n for (const secret of secrets.filter(\n (value) => value.length >= minSanitizedUrlSecretLength,\n )) {\n sanitize.addSecretForSanitizing(secret);\n }\n}\n\n/**\n * The prefix of a crate sparse registry URL such as `sparse+https://`, which is requested without it.\n */\nconst sparsePrefix = 'sparse+';\n\n/**\n * Moves the `user:password@` userinfo of an HTTP(S) URL, or of the HTTP(S) URL of a `sparse+https://` one, into a host rule per host type in `hostTypes` for the path of that URL and every path below it, unless credentials for that host type are already supplied by a rule registered from URL credentials for exactly that path and host type, or by another rule for no host type, that host type or the platform host type it falls back to, which has no `matchHost` or one for that path, a path above or a path below it.\n * The credentials are dropped when no host type is left.\n *\n * @param hostTypes the host types the URL is requested with\n * @returns the URL with only its userinfo removed, or `url` unchanged when it carries none, only a placeholder username, a short username or a username equal to its first path segment\n */\nexport function detachUrlCredentials(url: string, hostTypes: string[]): string {\n // the crate datasource requests a `sparse+https://` registry by its `https://` URL\n if (url.startsWith(sparsePrefix)) {\n return `${sparsePrefix}${detachUrlCredentials(url.slice(sparsePrefix.length), hostTypes)}`;\n }\n const parsed = parseUserinfoUrl(url);\n if (!parsed) {\n return url;\n }\n // textually, so that the URL is spelled as the same URL without credentials\n const strippedUrl = withoutUserinfo(url, parsed);\n // a `${USER}:${PASS}@` placeholder is not a credential either, the configured rules supply it\n if (hasTemplatePlaceholder(parsed)) {\n return strippedUrl;\n }\n if (!hasCredentialUserinfo(parsed)) {\n return url;\n }\n const username = decodeUserinfo(parsed.username);\n const password = decodeUserinfo(parsed.password);\n // `token@host` is Basic auth with the token as the username\n // a short credential is more likely a plain account name than a token, and would redact every occurrence of that name, such as in branch names\n const secret = password || username;\n // the path of the URL without its query, and every path below it, but no sibling path such as `/projects/10` next to `/projects/1`\n const scopeUrl = trimTrailingSlash(`${parsed.origin}${parsed.pathname}`);\n // a `matchHost` ending with a slash also matches the URL of that path itself\n const matchHost = `${scopeUrl}/`;\n // configured credentials for the path win over the ones embedded in a URL, so a repository cannot replace them\n // a configured rule counts for the `hostType` it is used with by `find()`: without one, the same one, or the platform host type it falls back to\n // a configured rule for a path below this one counts as well, as the rule registered here would override its token on the requests below that path\n // this also keeps the credentials registered first for the same path and `hostType`, while those of a narrower path are registered next to them and win below it\n const unsuppliedHostTypes = [...new Set(hostTypes)].filter(\n (hostType) => !hasConfiguredCredentials(matchHost, [hostType]),\n );\n if (!unsuppliedHostTypes.length) {\n // dropped, but still redacted wherever the URL is repeated\n addUrlSecretsForSanitizing([secret]);\n return strippedUrl;\n }\n // one rule per `hostType`, so that only the requests of those host types receive the credentials\n // registered without `add()`, which would register a short password for sanitizing as well\n for (const hostType of unsuppliedHostTypes) {\n detachedRules.add(register({ hostType, matchHost, username, password }));\n }\n // the same credentials detached again for other host types keep a single entry, so that their hash does not change\n const isKnown = detachedCredentials.some(\n (credentials) =>\n credentials.url === scopeUrl &&\n credentials.username === username &&\n credentials.password === password,\n );\n if (!isKnown) {\n detachedCredentials.push({ url: scopeUrl, username, password });\n }\n const basicAuth = password ? toBase64(`${username}:${password}`) : '';\n addUrlSecretsForSanitizing([secret, basicAuth]);\n return strippedUrl;\n}\n\n/**\n * Matches the characters which may follow a URL inside a text without extending its last path segment.\n */\nconst urlEndRegex = regEx(/^(?:[/?#:,\\s]|$)/);\n\n/**\n * Matches the scheme, host, numeric port and path of the URL at the start of a text, if any, which ends at its query, fragment, a comma, whitespace or a colon after the host.\n */\nconst leadingUrlRegex = regEx(\n /^(?:[a-z][a-z\\d+.-]*:\\/\\/[^/?#,\\s:]*(?::\\d+)?(?:\\/[^?#,\\s]*)?)?/i,\n);\n\n/**\n * Returns `text` with the origin and path of the HTTP(S) URL at its start spelled as {@link detachUrlCredentials} scopes them, or `text` unchanged when there is no such URL or it has a userinfo.\n */\nfunction withNormalizedLeadingUrl(text: string): string {\n // the regex matches every text, an empty match when it does not start with a URL\n const [leadingUrl] = leadingUrlRegex.exec(text)!;\n const parsed = parseUrl(leadingUrl);\n if (!parsed || !isHttpUrl(parsed) || parsed.username || parsed.password) {\n return text;\n }\n const scopeUrl = trimTrailingSlash(`${parsed.origin}${parsed.pathname}`);\n return `${scopeUrl}${text.slice(leadingUrl.length)}`;\n}\n\n/**\n * Whether {@link detachUrlCredentials} registered any credentials.\n */\nexport function hasDetachedCredentials(): boolean {\n return detachedCredentials.length > 0;\n}\n\n/**\n * Returns the username and password which {@link detachUrlCredentials} registered for `url` requested with `hostType`, those of the longest matching path, or `null` when there are none.\n */\nexport function findDetachedCredentials({\n hostType,\n url,\n}: {\n hostType: string;\n url: string;\n}): Pick<HostRule, 'username' | 'password'> | null {\n const rule = hostRules\n .filter(\n (rule) =>\n isDetached(rule) &&\n rule.hostType === hostType &&\n matchesHost(url, rule.matchHost!),\n )\n .sort(fromShorterToLongerMatchHost)\n .pop();\n if (!rule) {\n return null;\n }\n return { username: rule.username, password: rule.password };\n}\n\n/**\n * Returns the SHA-256 of the credentials which {@link detachUrlCredentials} registered for the URL at the start of `text`, in any spelling of its origin and path and with or without its userinfo, or `null` when there are none.\n */\nexport function getDetachedCredentialsHash(text: string): string | null {\n // a `sparse+https://` URL has the credentials of its `https://` URL\n const urlText = text.startsWith(sparsePrefix)\n ? text.slice(sparsePrefix.length)\n : text;\n // compared as given and without the userinfo of its URL: `find()` ignores that userinfo, while in a key such as `https://host:@scope/pkg` it is part of a package name\n const normalizedTexts = [urlText, withoutLeadingUserinfo(urlText)].map(\n withNormalizedLeadingUrl,\n );\n const credentials = detachedCredentials\n .filter(({ url }) =>\n normalizedTexts.some(\n (normalizedText) =>\n normalizedText.startsWith(url) &&\n urlEndRegex.test(normalizedText.slice(url.length)),\n ),\n )\n .map(({ username, password }) => [username, password]);\n if (!credentials.length) {\n return null;\n }\n return toSha256(JSON.stringify(credentials));\n}\n\nexport interface HostRuleSearch {\n hostType?: string;\n url?: string;\n readOnly?: boolean;\n}\n\nexport interface MatchesHostOptions {\n /** match an HTTP(S) `url` by its prefix only, so that a `matchHost` ending with a slash does not match the URL of that path without the slash */\n prefixOnly?: boolean;\n}\n\nexport function matchesHost(\n url: string,\n matchHost: string,\n { prefixOnly = false }: MatchesHostOptions = {},\n): boolean {\n const parsedUrl = parseUrl(url);\n if (!parsedUrl) {\n return false;\n }\n\n const parsedMatchHost = parseUrl(matchHost);\n if (isHttpUrl(parsedUrl) && isHttpUrl(parsedMatchHost)) {\n const { href } = parsedMatchHost!;\n // ignore any userinfo (`user:pass@`) on the searched URL, so a `matchHost` without credentials still matches\n // a `matchHost` ending with a slash also matches the URL of that path itself, without the slash\n return (\n stripUrlCredentials(parsedUrl).startsWith(href) ||\n (!prefixOnly &&\n ensureTrailingSlash(`${parsedUrl.origin}${parsedUrl.pathname}`) ===\n href)\n );\n }\n\n const { hostname } = parsedUrl;\n if (!hostname) {\n return false;\n }\n\n if (hostname === matchHost) {\n return true;\n }\n\n const topLevelSuffix = matchHost.startsWith('.')\n ? matchHost\n : `.${matchHost}`;\n return hostname.endsWith(topLevelSuffix);\n}\n\nfunction fromShorterToLongerMatchHost(a: HostRule, b: HostRule): number {\n if (!a.matchHost || !b.matchHost) {\n return 0;\n }\n return a.matchHost.length - b.matchHost.length;\n}\n\nfunction hostRuleRank({ hostType, matchHost, readOnly }: HostRule): number {\n if ((hostType || readOnly) && matchHost) {\n return 3;\n }\n\n if (matchHost) {\n return 2;\n }\n\n if (hostType) {\n return 1;\n }\n\n return 0;\n}\n\nfunction fromLowerToHigherRank(a: HostRule, b: HostRule): number {\n return hostRuleRank(a) - hostRuleRank(b);\n}\n\n// A single comparator, rather than two consecutive sorts: `fromShorterToLongerMatchHost` returns 0 for rules without a `matchHost`, which makes it non-transitive on its own, so a standalone length sort could leave two `matchHost` rules unordered when a host-less rule sits between them\nfunction fromLowerRankAndShorterMatchHost(a: HostRule, b: HostRule): number {\n return fromLowerToHigherRank(a, b) || fromShorterToLongerMatchHost(a, b);\n}\n\n/**\n * The `headers` that apply from a set of matching rules of the same trust tier.\n *\n * The last rule to set any wins outright, as `find()`'s callers receive them sorted from least to most specific. That is the behaviour every matching rule had before `headers` were combined across tiers, and it is what lets a broad rule's headers be masked by a narrower rule from the same source.\n */\nfunction headersOfLastRuleToSetThem(\n rules: RegisteredHostRule[],\n): Record<string, string> | undefined {\n return rules\n .map((rule) => rule.headers)\n .filter(isTruthy)\n .pop();\n}\n\n/**\n * The last defined value, following the same \"most specific rule wins\" ordering as {@link headersOfLastRuleToSetThem}.\n */\nfunction lastDefined<T>(values: (T | undefined)[]): T | undefined {\n return values.filter((value) => !isUndefined(value)).pop();\n}\n\n/**\n * The rules whose grant is deliberately scoped, by a `hostType` or by a URL-prefix `matchHost` - see {@link InternalHostGrant}.\n */\nfunction scopedRules(rules: RegisteredHostRule[]): RegisteredHostRule[] {\n return rules.filter(\n (rule) => isNonEmptyString(rule.hostType) || isHttpUrl(rule.matchHost),\n );\n}\n\nexport function find(\n search: HostRuleSearch,\n): CombinedHostRuleWithTrustedHeaders {\n if ([search.hostType, search.url].every(isFalsy)) {\n logger.warn({ search }, 'Invalid hostRules search');\n return {};\n }\n\n // Sort primarily by rank, and secondarily by matchHost length\n const sortedRules = hostRules.sort(fromLowerRankAndShorterMatchHost);\n\n const matchedRules: RegisteredHostRule[] = [];\n for (const rule of sortedRules) {\n let hostTypeMatch = true;\n let hostMatch = true;\n let readOnlyMatch = true;\n\n if (rule.hostType) {\n hostTypeMatch = false;\n // v8 ignore else -- TODO: add test #40625\n if (search.hostType === rule.hostType) {\n hostTypeMatch = true;\n }\n }\n\n if (rule.matchHost && rule.resolvedHost) {\n hostMatch = false;\n if (search.url) {\n hostMatch = matchesHost(search.url, rule.matchHost);\n }\n }\n\n if (!isUndefined(rule.readOnly)) {\n readOnlyMatch = false;\n // v8 ignore else -- TODO: add test #40625\n if (matchesReadOnly(rule, search.readOnly)) {\n readOnlyMatch = true;\n hostTypeMatch = true; // When we match `readOnly`, we don't care about `hostType`\n }\n }\n\n if (hostTypeMatch && readOnlyMatch && hostMatch) {\n matchedRules.push(clone(rule));\n }\n }\n\n const res: RegisteredHostRule & Pick<CombinedHostRule, 'internalHostGrant'> =\n Object.assign({}, ...matchedRules);\n\n // the sensitive fields below are resolved a trust tier at a time, so that the outcome cannot depend on how specific a rule from a less trusted tier makes itself\n const trustedRules = matchedRules.filter(\n (rule) => rule.trustTier === 'admin',\n );\n const inheritedRules = matchedRules.filter(\n (rule) => rule.trustTier === 'inherit',\n );\n const untrustedRules = matchedRules.filter(\n (rule) => rule.trustTier === 'untrusted',\n );\n\n // `headers` are resolved per trust tier and then combined key by key, so that repository or preset config can no longer discard - or substitute - the headers a self-hosted admin configured for the same host\n // Within a tier nothing changes: the most specific rule's `headers` still replace those of the broader rules it is combined with, so an admin masking their own broad rule with a narrower one keeps working, as does a repository doing the same among its own rules\n // This is deliberately scoped to `headers` alone, as combining any more of the fields than we already do has caused authentication regressions before: an inherited `token` and a specific rule's `username`/`password` are not meant to be used together\n const untrustedHeaders = headersOfLastRuleToSetThem(untrustedRules);\n const inheritedHeaders = headersOfLastRuleToSetThem(inheritedRules);\n const trustedHeaders = headersOfLastRuleToSetThem(trustedRules);\n if (untrustedHeaders ?? inheritedHeaders ?? trustedHeaders) {\n // the admin's own headers are applied last, so neither a repository nor inherited config can override one they set for this host\n res.headers = {\n ...untrustedHeaders,\n ...inheritedHeaders,\n ...trustedHeaders,\n };\n // set as a pair with `headers`, even to `[]` when nothing is trusted, rather than only when there are trusted headers: `findMatchingRule`'s hostType fallbacks combine results with `{ ...fallbackResult, ...res }`, and a plain object spread only overrides a key that `res` actually carries - a key `res` never set at all would let a `fallbackResult.trustedHeaderNames` show through underneath it\n // concretely, this stops an admin's trusted `github` hostType rule leaking its trusted status onto an unrelated, untrusted `github-tags` rule for the same header name: without `res.trustedHeaderNames` set to `[]` here, the repository's own header would inherit the admin's exemption from `allowedHeaders` once the two results are combined - see \"does not let a fallback hostType's trustedHeaderNames leak onto a rule's own untrusted headers\" in `lib/util/http/host-rules.spec.ts`, and \"keeps `trustedHeaderNames` as an explicit key, set to `[]`, when only untrusted headers match\" below, for this in practice\n // an empty array rather than `undefined`, unlike the rest of this function's optional fields: `undefined`-valued properties are invisible to `toEqual`, which would silently let a caller's assertion miss a regression in the shadowing above - every other test asserting on a plain `{ headers: {...} }` result must now spell out `trustedHeaderNames: []` too, which is deliberate\n // never set when `res.headers` isn't either, so a `find()` with no matching headers still returns `{}` rather than `{ trustedHeaderNames: [] }`, which callers that check for an empty result (e.g. `isNonEmptyObject`) rely on\n // tracked so that `applyHostRule`'s request-time defence-in-depth knows which of `res.headers` already bypassed `allowedHeaders` at registration as the administrator's own, and does not re-drop them\n res.trustedHeaderNames = trustedHeaders ? Object.keys(trustedHeaders) : [];\n }\n\n // `enabled` is resolved per trust tier like `headers`: a repository or preset rule must not be able to re-enable a host the administrator's own rules disabled (or vice versa) by out-specifying them with a longer `matchHost`\n const enabled =\n lastDefined(trustedRules.map((rule) => rule.enabled)) ??\n lastDefined(inheritedRules.map((rule) => rule.enabled)) ??\n lastDefined(untrustedRules.map((rule) => rule.enabled));\n if (!isUndefined(enabled)) {\n res.enabled = enabled;\n }\n\n // computed here and never from configuration: `add()` strips `allowInternal` from untrusted registrations, so only rules from configuration the administrator trusts can contribute\n // the admin's own rules are resolved first, and an `allowInternal: false` of theirs is an absolute veto: inherited config cannot talk an organization's way back in through any grant shape, whether scoped, unscoped, or merely implicit in naming the host\n // each shape must therefore be resolved from the admin tier's verdict rather than falling through to the inherit tier on its own - a bare admin rule sets no scoped verdict, and an inherited scoped grant would otherwise fill the gap for exactly the requests which consult it, such as fetching config over HTTP\n const trustedExplicit = lastDefined(\n trustedRules.map((rule) => rule.allowInternal),\n );\n const trustedScoped = lastDefined(\n scopedRules(trustedRules).map((rule) => rule.allowInternal),\n );\n // derived from the resolved values, so an admin masking a broad `allowInternal: false` of their own with a narrower `true` is not a veto\n const adminVeto = trustedExplicit === false || trustedScoped === false;\n // a vetoed request has no inherit tier left to fall through to, and is clamped to `false` rather than to undefined so that a consumer's `explicit ?? implicit` still short-circuits\n const inheritedExplicit = adminVeto\n ? false\n : lastDefined(inheritedRules.map((rule) => rule.allowInternal));\n const inheritedScoped = adminVeto\n ? false\n : lastDefined(\n scopedRules(inheritedRules).map((rule) => rule.allowInternal),\n );\n const internalHostGrant: InternalHostGrant = {\n explicit: trustedExplicit ?? inheritedExplicit,\n scoped: trustedScoped ?? inheritedScoped,\n implicit:\n !adminVeto &&\n [...trustedRules, ...inheritedRules].some((rule) =>\n isNonEmptyString(rule.matchHost),\n ),\n };\n if (\n !isUndefined(internalHostGrant.explicit) ||\n !isUndefined(internalHostGrant.scoped) ||\n internalHostGrant.implicit\n ) {\n res.internalHostGrant = internalHostGrant;\n }\n\n delete res.hostType;\n delete res.resolvedHost;\n delete res.matchHost;\n delete res.readOnly;\n delete res.trustTier;\n delete res.trusted;\n delete res.allowInternal;\n return res;\n}\n\n// the functions below leave out the rules registered by `detachUrlCredentials()`, which apply only to the requests `find()` is searched for\n\nexport function hosts({ hostType }: { hostType: string }): string[] {\n return configuredRules()\n .filter((rule) => rule.hostType === hostType)\n .map((rule) => rule.resolvedHost)\n .filter(isTruthy);\n}\n\nexport function hostType({ url }: { url: string }): string | null {\n return (\n configuredRules()\n .filter((rule) => rule.matchHost && matchesHost(url, rule.matchHost))\n .sort(fromShorterToLongerMatchHost)\n .map((rule) => rule.hostType)\n .filter(isTruthy)\n .pop() ?? null\n );\n}\n\nexport function findAll({ hostType }: { hostType: string }): HostRule[] {\n return configuredRules().filter((rule) => rule.hostType === hostType);\n}\n\n/**\n * @returns a deep copy of all host rules other than those registered by {@link detachUrlCredentials}\n */\nexport function getAll(): HostRule[] {\n return clone(configuredRules());\n}\n\nexport function clear(): void {\n logger.debug('Clearing hostRules');\n hostRules = [];\n detachedCredentials = [];\n detachedRules = new WeakSet();\n sanitize.clearRepoSanitizedSecretsList();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"host-rules.js","names":[],"sources":["../../lib/util/host-rules.ts"],"sourcesContent":["import {\n isFalsy,\n isNonEmptyString,\n isString,\n isTruthy,\n isUndefined,\n} from '@sindresorhus/is';\nimport { GlobalConfig } from '../config/global.ts';\nimport type { PlatformFamilyId } from '../constants/platforms.ts';\nimport { PLATFORM_FAMILIES } from '../constants/platforms.ts';\nimport { logger } from '../logger/index.ts';\nimport type {\n CombinedHostRule,\n HostRule,\n InternalHostGrant,\n} from '../types/index.ts';\nimport { clone } from './clone.ts';\nimport { toSha256 } from './hash.ts';\nimport { regEx } from './regex.ts';\nimport * as sanitize from './sanitize.ts';\nimport { toBase64 } from './string.ts';\nimport { matchRegexOrGlobList } from './string-match.ts';\nimport {\n decodeUserinfo,\n ensureTrailingSlash,\n isCredentialUserinfo,\n isHttpUrl,\n isPlaceholderUsername,\n isUserinfoPlaceholder,\n massageHostUrl,\n parseUrl,\n stripUrlCredentials,\n trimTrailingSlash,\n urlUserinfoRegex,\n} from './url.ts';\n\n/**\n * How much a host rule is trusted, according to the configuration it came from, from most to least trusted:\n *\n * - `admin`: the self-hosted administrator's own configuration - their global config, or a `repositories[]` entry\n * - `inherit`: organization-inherited config, and only where the administrator has opted into trusting it through `inheritConfigTrusted`\n * - `untrusted`: repository configuration, and the presets it extends\n */\ntype HostRuleTrustTier = 'admin' | 'inherit' | 'untrusted';\n\n/**\n * A host rule as registered through {@link add}.\n *\n * `trustTier` is deliberately not a field of `HostRule`: it is set from {@link AddHostRuleOptions} at registration time and must never be settable through configuration.\n */\ninterface RegisteredHostRule extends HostRule {\n /** which configuration the rule came from - see {@link HostRuleTrustTier}. Always set by `add()`, but optional so that `find()` can delete it from its result */\n trustTier?: HostRuleTrustTier;\n\n /** never set by us, and declared only so that a `trusted` smuggled in through configuration can be deleted before the rule is registered. It was the boolean `trustTier` replaced, so a rule carrying one may well be an attempt at the administrator's precedence */\n trusted?: never;\n\n /** set only by {@link find}; see {@link CombinedHostRuleWithTrustedHeaders.trustedHeaderNames} */\n trustedHeaderNames?: string[];\n}\n\n/**\n * A {@link CombinedHostRule} returned by {@link find}, additionally reporting which of its `headers` came from an `admin` tier rule.\n *\n * `trustedHeaderNames` is deliberately not a field of `HostRule`, for the same reason `trustTier` is not: it must never be settable through configuration.\n */\nexport interface CombinedHostRuleWithTrustedHeaders extends CombinedHostRule {\n /** header names in `headers` that came from an `admin` tier rule (the self-hosted administrator's own config), and so already bypassed `allowedHeaders` at registration - `applyHostRule`'s request-time defence-in-depth must not re-check, and drop, them */\n trustedHeaderNames?: string[];\n}\n\nlet hostRules: RegisteredHostRule[] = [];\n\n/**\n * The credentials of a host rule registered by {@link detachUrlCredentials}.\n */\ninterface DetachedCredentials {\n /** the URL the credentials apply to, without a trailing slash */\n url: string;\n username: string;\n password: string;\n}\n\nlet detachedCredentials: DetachedCredentials[] = [];\n\n/**\n * The host rules registered by {@link detachUrlCredentials}.\n */\nlet detachedRules = new WeakSet<HostRule>();\n\n/**\n * Fields within `HostRule`s that must have their value registered for sanitising through `sanitize.addSecretForSanitizing()`.\n *\n * Kept in sync with `redactedFields` through tests.\n */\nexport const confidentialFields: (keyof HostRule)[] = [\n 'password',\n 'token',\n 'httpsPrivateKey',\n /* not actually sensitive, but redacted nonetheless */\n 'httpsCertificate',\n /* not actually sensitive, but redacted nonetheless */\n 'httpsCertificateAuthority',\n];\n\nexport interface LegacyHostRule {\n hostName?: string;\n domainName?: string;\n baseUrl?: string;\n host?: string;\n endpoint?: string;\n}\n\nexport function migrateRule(rule: LegacyHostRule & HostRule): HostRule {\n const cloned: LegacyHostRule & HostRule = clone(rule);\n delete cloned.hostName;\n delete cloned.domainName;\n delete cloned.baseUrl;\n const result: HostRule = cloned;\n\n const { matchHost } = result;\n const { hostName, domainName, baseUrl } = rule;\n const hostValues = [matchHost, hostName, domainName, baseUrl].filter(\n isTruthy,\n );\n if (hostValues.length === 1) {\n const [matchHost] = hostValues;\n result.matchHost = matchHost;\n } else if (hostValues.length > 1) {\n throw new Error(\n `hostRules cannot contain more than one host-matching field - use \"matchHost\" only.`,\n );\n }\n\n return result;\n}\n\n/**\n * Enforce the `allowedHeaders` allowlist on a set of host rules.\n *\n * Loudly remove anything that's not permitted, logging a WARN. Used by `add()` for an untrusted rule's `headers`; a `trusted` rule's are exempt, so never reach this.\n *\n * Headers that survive this allowlist are still subject to how {@link find} combines them: an admin's headers for a host are applied over those of any repository or preset rule matching the same request, so a repository can neither drop nor substitute them.\n */\nexport function filterAllowedHeaders(rules: HostRule[]): HostRule[] {\n const allowlist = GlobalConfig.get('allowedHeaders');\n const denied: string[] = [];\n\n const result = rules.map((rule) => {\n if (!rule.headers) {\n return rule;\n }\n\n const allowed: Record<string, string> = {};\n const ruleDenied: string[] = [];\n for (const [name, value] of Object.entries(rule.headers)) {\n if (matchRegexOrGlobList(name, allowlist)) {\n allowed[name] = value;\n } else {\n ruleDenied.push(name);\n }\n }\n\n if (!ruleDenied.length) {\n return rule;\n }\n denied.push(...ruleDenied);\n\n const filtered: HostRule = { ...rule, headers: allowed };\n if (!Object.keys(allowed).length) {\n // drop the key rather than leave an empty object behind: `find()` treats any rule that sets `headers` as replacing the headers of the broader rules of its own tier, so a rule left with none would suppress them\n delete filtered.headers;\n }\n return filtered;\n });\n\n if (denied.length) {\n logger.warn(\n { denied },\n \"Ignoring hostRules headers not permitted by this Renovate instance's `allowedHeaders`\",\n );\n }\n return result;\n}\n\nexport interface AddHostRuleOptions {\n /**\n * Whether this rule comes from the self-hosted administrator's own configuration, rather than from repository or preset config.\n *\n * Registers the rule into the `admin` tier - see {@link HostRuleTrustTier}.\n */\n trusted?: boolean;\n\n /**\n * Whether this rule comes from organization-inherited config which the administrator has opted into trusting, through `inheritConfigTrusted`.\n *\n * Registers the rule into the `inherit` tier - see {@link HostRuleTrustTier}. Ignored when `trusted` is also set.\n */\n inherited?: boolean;\n}\n\n/**\n * The trust tier a caller asked for.\n *\n * Deliberately opt-in: a caller that says nothing registers into the untrusted tier, so a new call site cannot grant itself the administrator's precedence by omission.\n */\nfunction trustTierFor(options?: AddHostRuleOptions): HostRuleTrustTier {\n if (options?.trusted) {\n return 'admin';\n }\n if (options?.inherited) {\n return 'inherit';\n }\n return 'untrusted';\n}\n\n/**\n * Registers a host rule, without registering its confidential fields for sanitizing.\n *\n * @returns the registered rule\n */\nfunction register(\n params: HostRule,\n options?: AddHostRuleOptions,\n): RegisteredHostRule {\n let rule: RegisteredHostRule = {\n ...migrateRule(params),\n // set only from `options`, and assigned unconditionally so that it cannot be carried over from `params`: `HostRule` has no `trustTier` field, but configuration is parsed from JSON, so a repository could otherwise smuggle one in and have its rules treated as the administrator's\n trustTier: trustTierFor(options),\n };\n // as above, for the boolean this tier replaced, and for the header names `find()` computes from it\n delete rule.trusted;\n delete rule.trustedHeaderNames;\n\n // like the trust tier, `allowInternal` may only come from configuration the administrator trusts: a repository or preset rule must not be able to grant itself access to internal hosts\n if (!isUndefined(rule.allowInternal) && rule.trustTier === 'untrusted') {\n logger.debug(\n `Ignoring hostRules allowInternal for ${rule.matchHost ?? rule.hostType} from untrusted config`,\n );\n delete rule.allowInternal;\n }\n\n if (rule.headers && rule.trustTier !== 'admin') {\n // enforced here, at the single registration chokepoint, so that no current or future caller can register a rule whose headers bypass `allowedHeaders`; `applyHostRule` filters by header name again at request time as defence in depth\n // `allowedHeaders` constrains what a repository or preset may set, not the self-hosted administrator - the same exemption `filterAllowedEnv` gives the admin's own `env`, just enforced through the `admin` trust tier here rather than a name+value comparison, as `headers` are host-scoped\n [rule] = filterAllowedHeaders([rule]);\n }\n\n if (rule.matchHost) {\n rule.matchHost = massageHostUrl(rule.matchHost);\n const parsedUrl = parseUrl(rule.matchHost);\n rule.resolvedHost = parsedUrl?.hostname ?? rule.matchHost;\n confidentialFields.forEach((field) => {\n if (rule[field]) {\n logger.debug(\n // TODO: types (#22198)\n `Adding ${field} authentication for ${rule.matchHost!} (hostType=${\n rule.hostType\n }) to hostRules`,\n );\n }\n });\n }\n hostRules.push(rule);\n return rule;\n}\n\nexport function add(params: HostRule, options?: AddHostRuleOptions): void {\n const rule = register(params, options);\n confidentialFields.forEach((field) => {\n const secret = rule[field];\n if (isString(secret) && secret.length > 3) {\n sanitize.addSecretForSanitizing(secret);\n }\n });\n if (rule.username && rule.password) {\n sanitize.addSecretForSanitizing(\n toBase64(`${rule.username}:${rule.password}`),\n );\n }\n}\n\n/**\n * The platform host types whose host rules `findMatchingRule()` falls back to for the other host types of their family.\n */\nconst fallbackPlatformHostTypes = [\n 'bitbucket',\n 'bitbucket-server',\n 'forgejo',\n 'gitea',\n 'github',\n 'gitlab',\n] as const satisfies PlatformFamilyId[];\n\n/**\n * Returns `hostTypes` and the platform host type each of them falls back to.\n */\nfunction withFallbackHostTypes(hostTypes: string[]): string[] {\n const platformHostTypes = fallbackPlatformHostTypes.filter((platform) =>\n PLATFORM_FAMILIES[platform].apiUsingHostTypes.some((hostType) =>\n hostTypes.includes(hostType),\n ),\n );\n return [...hostTypes, ...platformHostTypes];\n}\n\n/**\n * Whether a request of `url` with any `hostType` falls back to the `github` host rules: a request of the GitHub.com API, or of the host of the GitHub platform endpoint, such as GitHub Enterprise Server or GitHub Enterprise Cloud.\n */\nexport function usesGithubUrlFallback(url: string): boolean {\n if (url.startsWith('https://api.github.com/')) {\n return true;\n }\n const platformEndpoint = GlobalConfig.get('endpoint');\n if (GlobalConfig.get('platform') !== 'github' || !platformEndpoint) {\n return false;\n }\n const requestHost = parseUrl(url)?.hostname;\n return !!requestHost && requestHost === parseUrl(platformEndpoint)?.hostname;\n}\n\n/**\n * Whether `rule` was registered by {@link detachUrlCredentials}.\n */\nfunction isDetached(rule: RegisteredHostRule): boolean {\n return detachedRules.has(rule);\n}\n\n/**\n * Returns the host rules other than those registered by {@link detachUrlCredentials}.\n */\nfunction configuredRules(): RegisteredHostRule[] {\n return hostRules.filter((rule) => !isDetached(rule));\n}\n\n/**\n * Whether {@link find} matches `rule` for a search with `readOnly` whatever its `hostType`: when the rule sets `readOnly` to the same value.\n */\nfunction matchesReadOnly(\n rule: HostRule,\n readOnly: boolean | undefined,\n): boolean {\n return !isUndefined(rule.readOnly) && rule.readOnly === readOnly;\n}\n\n/**\n * Whether `rule` applies to the scope `matchHost` of {@link detachUrlCredentials} requested with one of `hostTypes`: a detached rule only for the same `matchHost`, any other rule when it has no `hostType`, one of `hostTypes` or `readOnly: true`, and no `matchHost` or one which matches `matchHost` or lies within it.\n */\nfunction appliesToScope(\n rule: RegisteredHostRule,\n matchHost: string,\n hostTypes: string[],\n): boolean {\n if (isDetached(rule)) {\n return rule.matchHost === matchHost && hostTypes.includes(rule.hostType!);\n }\n // a rule for another `hostType`, such as the platform's for the same host, is not used for these requests\n // unless it is one for read-only requests, which `find()` uses for the lookups whatever their `hostType`\n if (\n rule.hostType &&\n !hostTypes.includes(rule.hostType) &&\n !matchesReadOnly(rule, true)\n ) {\n return false;\n }\n return (\n !rule.matchHost ||\n matchesHost(matchHost, rule.matchHost) ||\n matchesHost(rule.matchHost, matchHost)\n );\n}\n\n/**\n * Whether a host rule which {@link appliesToScope} `matchHost` requested with one of `hostTypes`, the platform host type they fall back to, or `github` when {@link usesGithubUrlFallback} for `matchHost`, supplies credentials.\n */\nfunction hasConfiguredCredentials(\n matchHost: string,\n hostTypes: string[],\n): boolean {\n const searchedHostTypes = withFallbackHostTypes(hostTypes);\n if (usesGithubUrlFallback(matchHost)) {\n searchedHostTypes.push('github');\n }\n return hostRules.some(\n (rule) =>\n appliesToScope(rule, matchHost, searchedHostTypes) &&\n [rule.username, rule.password, rule.token].some(\n (value) => !isUndefined(value),\n ),\n );\n}\n\n/**\n * Returns `text` without the userinfo of the URL at its start, or `text` unchanged when that URL has none.\n */\nfunction withoutLeadingUserinfo(text: string): string {\n // only a userinfo at the start of `text` belongs to that URL, a later one is part of its path or query\n const [match] = text.matchAll(urlUserinfoRegex);\n if (match?.index !== 0) {\n return text;\n }\n return `${match.groups!.scheme}://${text.slice(match[0].length)}`;\n}\n\n/**\n * Returns `url` without its userinfo, spelled as given, or as the parser normalizes it when the two disagree.\n */\nfunction withoutUserinfo(url: string, parsed: URL): string {\n const stripped = withoutLeadingUserinfo(url);\n const reparsed = parseUrl(stripped);\n if (\n reparsed?.host !== parsed.host ||\n reparsed?.username ||\n reparsed?.password\n ) {\n return stripUrlCredentials(parsed);\n }\n return stripped;\n}\n\n/**\n * Returns the parsed `url` when it is an HTTP(S) URL with a userinfo other than a placeholder username alone, or `null`.\n */\nfunction parseUserinfoUrl(url: string): URL | null {\n if (!url.includes('@')) {\n return null;\n }\n const parsed = parseUrl(url);\n if (!parsed || !isHttpUrl(parsed)) {\n return null;\n }\n if (!parsed.username && !parsed.password) {\n return null;\n }\n // a rule for the placeholder would supply it as a credential and stop the lookup which resolves the real one\n if (!parsed.password && isPlaceholderUsername(parsed.username)) {\n return null;\n }\n return parsed;\n}\n\n/**\n * Whether the password of `parsed` is a template placeholder, or the username when there is no password, as {@link isCredentialUserinfo} reads them.\n */\nfunction hasTemplatePlaceholder(parsed: URL): boolean {\n // the URL parser percent-encodes the braces of a placeholder, which `isUserinfoPlaceholder` decodes\n // a placeholder username next to a literal password does not make that password a placeholder\n return isUserinfoPlaceholder(parsed.password || parsed.username);\n}\n\n/**\n * Whether the userinfo of `parsed` holds a credential according to {@link isCredentialUserinfo}.\n */\nfunction hasCredentialUserinfo(parsed: URL): boolean {\n const scheme = parsed.protocol.slice(0, -1);\n const userinfo = parsed.password\n ? `${parsed.username}:${parsed.password}`\n : parsed.username;\n return isCredentialUserinfo(\n scheme,\n userinfo,\n `${parsed.host}${parsed.pathname}`,\n );\n}\n\n/**\n * The minimum length of a credential from a URL which is registered for sanitizing.\n */\nconst minSanitizedUrlSecretLength = 16;\n\n/**\n * Registers each of `secrets` for sanitizing which is at least {@link minSanitizedUrlSecretLength} characters long.\n */\nfunction addUrlSecretsForSanitizing(secrets: string[]): void {\n for (const secret of secrets.filter(\n (value) => value.length >= minSanitizedUrlSecretLength,\n )) {\n sanitize.addSecretForSanitizing(secret);\n }\n}\n\n/**\n * The prefix of a crate sparse registry URL such as `sparse+https://`, which is requested without it.\n */\nconst sparsePrefix = 'sparse+';\n\n/**\n * Moves the `user:password@` userinfo of an HTTP(S) URL, or of the HTTP(S) URL of a `sparse+https://` one, into a host rule per host type in `hostTypes` for the path of that URL and every path below it, unless credentials for that host type are already supplied by a rule registered from URL credentials for exactly that path and host type, or by another rule for no host type, that host type or the platform host type it falls back to, which has no `matchHost` or one for that path, a path above or a path below it.\n * The credentials are dropped when no host type is left.\n *\n * @param hostTypes the host types the URL is requested with\n * @returns the URL with only its userinfo removed, or `url` unchanged when it carries none, only a placeholder username, a short username or a username equal to its first path segment\n */\nexport function detachUrlCredentials(url: string, hostTypes: string[]): string {\n // the crate datasource requests a `sparse+https://` registry by its `https://` URL\n if (url.startsWith(sparsePrefix)) {\n return `${sparsePrefix}${detachUrlCredentials(url.slice(sparsePrefix.length), hostTypes)}`;\n }\n const parsed = parseUserinfoUrl(url);\n if (!parsed) {\n return url;\n }\n // textually, so that the URL is spelled as the same URL without credentials\n const strippedUrl = withoutUserinfo(url, parsed);\n // a `${USER}:${PASS}@` placeholder is not a credential either, the configured rules supply it\n if (hasTemplatePlaceholder(parsed)) {\n return strippedUrl;\n }\n if (!hasCredentialUserinfo(parsed)) {\n return url;\n }\n const username = decodeUserinfo(parsed.username);\n const password = decodeUserinfo(parsed.password);\n // `token@host` is Basic auth with the token as the username\n // a short credential is more likely a plain account name than a token, and would redact every occurrence of that name, such as in branch names\n const secret = password || username;\n // the path of the URL without its query, and every path below it, but no sibling path such as `/projects/10` next to `/projects/1`\n const scopeUrl = trimTrailingSlash(`${parsed.origin}${parsed.pathname}`);\n // a `matchHost` ending with a slash also matches the URL of that path itself\n const matchHost = `${scopeUrl}/`;\n // configured credentials for the path win over the ones embedded in a URL, so a repository cannot replace them\n // a configured rule counts for the `hostType` it is used with by `find()`: without one, the same one, or the platform host type it falls back to\n // a configured rule for a path below this one counts as well, as the rule registered here would override its token on the requests below that path\n // this also keeps the credentials registered first for the same path and `hostType`, while those of a narrower path are registered next to them and win below it\n const unsuppliedHostTypes = [...new Set(hostTypes)].filter(\n (hostType) => !hasConfiguredCredentials(matchHost, [hostType]),\n );\n if (!unsuppliedHostTypes.length) {\n // dropped, but still redacted wherever the URL is repeated\n addUrlSecretsForSanitizing([secret]);\n return strippedUrl;\n }\n // one rule per `hostType`, so that only the requests of those host types receive the credentials\n // registered without `add()`, which would register a short password for sanitizing as well\n for (const hostType of unsuppliedHostTypes) {\n detachedRules.add(register({ hostType, matchHost, username, password }));\n }\n // the same credentials detached again for other host types keep a single entry, so that their hash does not change\n const isKnown = detachedCredentials.some(\n (credentials) =>\n credentials.url === scopeUrl &&\n credentials.username === username &&\n credentials.password === password,\n );\n if (!isKnown) {\n detachedCredentials.push({ url: scopeUrl, username, password });\n }\n const basicAuth = password ? toBase64(`${username}:${password}`) : '';\n addUrlSecretsForSanitizing([secret, basicAuth]);\n return strippedUrl;\n}\n\n/**\n * Matches the characters which may follow a URL inside a text without extending its last path segment.\n */\nconst urlEndRegex = regEx(/^(?:[/?#:,\\s]|$)/);\n\n/**\n * Matches the scheme, host, numeric port and path of the URL at the start of a text, if any, which ends at its query, fragment, a comma, whitespace or a colon after the host.\n */\nconst leadingUrlRegex = regEx(\n /^(?:[a-z][a-z\\d+.-]*:\\/\\/[^/?#,\\s:]*(?::\\d+)?(?:\\/[^?#,\\s]*)?)?/i,\n);\n\n/**\n * Returns `text` with the origin and path of the HTTP(S) URL at its start spelled as {@link detachUrlCredentials} scopes them, or `text` unchanged when there is no such URL or it has a userinfo.\n */\nfunction withNormalizedLeadingUrl(text: string): string {\n // the regex matches every text, an empty match when it does not start with a URL\n const [leadingUrl] = leadingUrlRegex.exec(text)!;\n const parsed = parseUrl(leadingUrl);\n if (!parsed || !isHttpUrl(parsed) || parsed.username || parsed.password) {\n return text;\n }\n const scopeUrl = trimTrailingSlash(`${parsed.origin}${parsed.pathname}`);\n return `${scopeUrl}${text.slice(leadingUrl.length)}`;\n}\n\n/**\n * Whether {@link detachUrlCredentials} registered any credentials.\n */\nexport function hasDetachedCredentials(): boolean {\n return detachedCredentials.length > 0;\n}\n\n/**\n * Returns the username and password which {@link detachUrlCredentials} registered for `url` requested with `hostType`, those of the longest matching path, or `null` when there are none.\n */\nexport function findDetachedCredentials({\n hostType,\n url,\n}: {\n hostType: string;\n url: string;\n}): Pick<HostRule, 'username' | 'password'> | null {\n const rule = hostRules\n .filter(\n (rule) =>\n isDetached(rule) &&\n rule.hostType === hostType &&\n matchesHost(url, rule.matchHost!),\n )\n .sort(fromShorterToLongerMatchHost)\n .pop();\n if (!rule) {\n return null;\n }\n return { username: rule.username, password: rule.password };\n}\n\n/**\n * Returns the SHA-256 of the credentials which {@link detachUrlCredentials} registered for the URL at the start of `text`, in any spelling of its origin and path and with or without its userinfo, or `null` when there are none.\n */\nexport function getDetachedCredentialsHash(text: string): string | null {\n // a `sparse+https://` URL has the credentials of its `https://` URL\n const urlText = text.startsWith(sparsePrefix)\n ? text.slice(sparsePrefix.length)\n : text;\n // compared as given and without the userinfo of its URL: `find()` ignores that userinfo, while in a key such as `https://host:@scope/pkg` it is part of a package name\n const normalizedTexts = [urlText, withoutLeadingUserinfo(urlText)].map(\n withNormalizedLeadingUrl,\n );\n const credentials = detachedCredentials\n .filter(({ url }) =>\n normalizedTexts.some(\n (normalizedText) =>\n normalizedText.startsWith(url) &&\n urlEndRegex.test(normalizedText.slice(url.length)),\n ),\n )\n .map(({ username, password }) => [username, password]);\n if (!credentials.length) {\n return null;\n }\n return toSha256(JSON.stringify(credentials));\n}\n\nexport interface HostRuleSearch {\n hostType?: string;\n url?: string;\n readOnly?: boolean;\n}\n\nexport interface MatchesHostOptions {\n /** match an HTTP(S) `url` by its prefix only, so that a `matchHost` ending with a slash does not match the URL of that path without the slash */\n prefixOnly?: boolean;\n}\n\nexport function matchesHost(\n url: string,\n matchHost: string,\n { prefixOnly = false }: MatchesHostOptions = {},\n): boolean {\n const parsedUrl = parseUrl(url);\n if (!parsedUrl) {\n return false;\n }\n\n const parsedMatchHost = parseUrl(matchHost);\n if (isHttpUrl(parsedUrl) && isHttpUrl(parsedMatchHost)) {\n const { href } = parsedMatchHost!;\n // ignore any userinfo (`user:pass@`) on the searched URL, so a `matchHost` without credentials still matches\n // a `matchHost` ending with a slash also matches the URL of that path itself, without the slash\n if (stripUrlCredentials(parsedUrl).startsWith(href)) {\n return true;\n }\n return (\n !prefixOnly &&\n ensureTrailingSlash(`${parsedUrl.origin}${parsedUrl.pathname}`) === href\n );\n }\n\n const { hostname } = parsedUrl;\n if (!hostname) {\n return false;\n }\n\n if (hostname === matchHost) {\n return true;\n }\n\n const topLevelSuffix = matchHost.startsWith('.')\n ? matchHost\n : `.${matchHost}`;\n return hostname.endsWith(topLevelSuffix);\n}\n\nfunction fromShorterToLongerMatchHost(a: HostRule, b: HostRule): number {\n if (!a.matchHost || !b.matchHost) {\n return 0;\n }\n return a.matchHost.length - b.matchHost.length;\n}\n\nfunction hostRuleRank({ hostType, matchHost, readOnly }: HostRule): number {\n const isScoped = !!hostType || !!readOnly;\n if (isScoped && matchHost) {\n return 3;\n }\n\n if (matchHost) {\n return 2;\n }\n\n if (hostType) {\n return 1;\n }\n\n return 0;\n}\n\nfunction fromLowerToHigherRank(a: HostRule, b: HostRule): number {\n return hostRuleRank(a) - hostRuleRank(b);\n}\n\n// A single comparator, rather than two consecutive sorts: `fromShorterToLongerMatchHost` returns 0 for rules without a `matchHost`, which makes it non-transitive on its own, so a standalone length sort could leave two `matchHost` rules unordered when a host-less rule sits between them\nfunction fromLowerRankAndShorterMatchHost(a: HostRule, b: HostRule): number {\n return fromLowerToHigherRank(a, b) || fromShorterToLongerMatchHost(a, b);\n}\n\n/**\n * The `headers` that apply from a set of matching rules of the same trust tier.\n *\n * The last rule to set any wins outright, as `find()`'s callers receive them sorted from least to most specific. That is the behaviour every matching rule had before `headers` were combined across tiers, and it is what lets a broad rule's headers be masked by a narrower rule from the same source.\n */\nfunction headersOfLastRuleToSetThem(\n rules: RegisteredHostRule[],\n): Record<string, string> | undefined {\n return rules\n .map((rule) => rule.headers)\n .filter(isTruthy)\n .pop();\n}\n\n/**\n * The last defined value, following the same \"most specific rule wins\" ordering as {@link headersOfLastRuleToSetThem}.\n */\nfunction lastDefined<T>(values: (T | undefined)[]): T | undefined {\n return values.filter((value) => !isUndefined(value)).pop();\n}\n\n/**\n * The rules whose grant is deliberately scoped, by a `hostType` or by a URL-prefix `matchHost` - see {@link InternalHostGrant}.\n */\nfunction scopedRules(rules: RegisteredHostRule[]): RegisteredHostRule[] {\n return rules.filter(\n (rule) => isNonEmptyString(rule.hostType) || isHttpUrl(rule.matchHost),\n );\n}\n\nexport function find(\n search: HostRuleSearch,\n): CombinedHostRuleWithTrustedHeaders {\n if ([search.hostType, search.url].every(isFalsy)) {\n logger.warn({ search }, 'Invalid hostRules search');\n return {};\n }\n\n // Sort primarily by rank, and secondarily by matchHost length\n const sortedRules = hostRules.sort(fromLowerRankAndShorterMatchHost);\n\n const matchedRules: RegisteredHostRule[] = [];\n for (const rule of sortedRules) {\n let hostTypeMatch = true;\n let hostMatch = true;\n let readOnlyMatch = true;\n\n if (rule.hostType) {\n hostTypeMatch = false;\n // v8 ignore else -- TODO: add test #40625\n if (search.hostType === rule.hostType) {\n hostTypeMatch = true;\n }\n }\n\n if (rule.matchHost && rule.resolvedHost) {\n hostMatch = false;\n if (search.url) {\n hostMatch = matchesHost(search.url, rule.matchHost);\n }\n }\n\n if (!isUndefined(rule.readOnly)) {\n readOnlyMatch = false;\n // v8 ignore else -- TODO: add test #40625\n if (matchesReadOnly(rule, search.readOnly)) {\n readOnlyMatch = true;\n hostTypeMatch = true; // When we match `readOnly`, we don't care about `hostType`\n }\n }\n\n if (hostTypeMatch && readOnlyMatch && hostMatch) {\n matchedRules.push(clone(rule));\n }\n }\n\n const res: RegisteredHostRule & Pick<CombinedHostRule, 'internalHostGrant'> =\n Object.assign({}, ...matchedRules);\n\n // the sensitive fields below are resolved a trust tier at a time, so that the outcome cannot depend on how specific a rule from a less trusted tier makes itself\n const trustedRules = matchedRules.filter(\n (rule) => rule.trustTier === 'admin',\n );\n const inheritedRules = matchedRules.filter(\n (rule) => rule.trustTier === 'inherit',\n );\n const untrustedRules = matchedRules.filter(\n (rule) => rule.trustTier === 'untrusted',\n );\n\n // `headers` are resolved per trust tier and then combined key by key, so that repository or preset config can no longer discard - or substitute - the headers a self-hosted admin configured for the same host\n // Within a tier nothing changes: the most specific rule's `headers` still replace those of the broader rules it is combined with, so an admin masking their own broad rule with a narrower one keeps working, as does a repository doing the same among its own rules\n // This is deliberately scoped to `headers` alone, as combining any more of the fields than we already do has caused authentication regressions before: an inherited `token` and a specific rule's `username`/`password` are not meant to be used together\n const untrustedHeaders = headersOfLastRuleToSetThem(untrustedRules);\n const inheritedHeaders = headersOfLastRuleToSetThem(inheritedRules);\n const trustedHeaders = headersOfLastRuleToSetThem(trustedRules);\n if (untrustedHeaders ?? inheritedHeaders ?? trustedHeaders) {\n // the admin's own headers are applied last, so neither a repository nor inherited config can override one they set for this host\n res.headers = {\n ...untrustedHeaders,\n ...inheritedHeaders,\n ...trustedHeaders,\n };\n // set as a pair with `headers`, even to `[]` when nothing is trusted, rather than only when there are trusted headers: `findMatchingRule`'s hostType fallbacks combine results with `{ ...fallbackResult, ...res }`, and a plain object spread only overrides a key that `res` actually carries - a key `res` never set at all would let a `fallbackResult.trustedHeaderNames` show through underneath it\n // concretely, this stops an admin's trusted `github` hostType rule leaking its trusted status onto an unrelated, untrusted `github-tags` rule for the same header name: without `res.trustedHeaderNames` set to `[]` here, the repository's own header would inherit the admin's exemption from `allowedHeaders` once the two results are combined - see \"does not let a fallback hostType's trustedHeaderNames leak onto a rule's own untrusted headers\" in `lib/util/http/host-rules.spec.ts`, and \"keeps `trustedHeaderNames` as an explicit key, set to `[]`, when only untrusted headers match\" below, for this in practice\n // an empty array rather than `undefined`, unlike the rest of this function's optional fields: `undefined`-valued properties are invisible to `toEqual`, which would silently let a caller's assertion miss a regression in the shadowing above - every other test asserting on a plain `{ headers: {...} }` result must now spell out `trustedHeaderNames: []` too, which is deliberate\n // never set when `res.headers` isn't either, so a `find()` with no matching headers still returns `{}` rather than `{ trustedHeaderNames: [] }`, which callers that check for an empty result (e.g. `isNonEmptyObject`) rely on\n // tracked so that `applyHostRule`'s request-time defence-in-depth knows which of `res.headers` already bypassed `allowedHeaders` at registration as the administrator's own, and does not re-drop them\n res.trustedHeaderNames = trustedHeaders ? Object.keys(trustedHeaders) : [];\n }\n\n // `enabled` is resolved per trust tier like `headers`: a repository or preset rule must not be able to re-enable a host the administrator's own rules disabled (or vice versa) by out-specifying them with a longer `matchHost`\n const enabled =\n lastDefined(trustedRules.map((rule) => rule.enabled)) ??\n lastDefined(inheritedRules.map((rule) => rule.enabled)) ??\n lastDefined(untrustedRules.map((rule) => rule.enabled));\n if (!isUndefined(enabled)) {\n res.enabled = enabled;\n }\n\n // computed here and never from configuration: `add()` strips `allowInternal` from untrusted registrations, so only rules from configuration the administrator trusts can contribute\n // the admin's own rules are resolved first, and an `allowInternal: false` of theirs is an absolute veto: inherited config cannot talk an organization's way back in through any grant shape, whether scoped, unscoped, or merely implicit in naming the host\n // each shape must therefore be resolved from the admin tier's verdict rather than falling through to the inherit tier on its own - a bare admin rule sets no scoped verdict, and an inherited scoped grant would otherwise fill the gap for exactly the requests which consult it, such as fetching config over HTTP\n const trustedExplicit = lastDefined(\n trustedRules.map((rule) => rule.allowInternal),\n );\n const trustedScoped = lastDefined(\n scopedRules(trustedRules).map((rule) => rule.allowInternal),\n );\n // derived from the resolved values, so an admin masking a broad `allowInternal: false` of their own with a narrower `true` is not a veto\n const adminVeto = trustedExplicit === false || trustedScoped === false;\n // a vetoed request has no inherit tier left to fall through to, and is clamped to `false` rather than to undefined so that a consumer's `explicit ?? implicit` still short-circuits\n const inheritedExplicit = adminVeto\n ? false\n : lastDefined(inheritedRules.map((rule) => rule.allowInternal));\n const inheritedScoped = adminVeto\n ? false\n : lastDefined(\n scopedRules(inheritedRules).map((rule) => rule.allowInternal),\n );\n const internalHostGrant: InternalHostGrant = {\n explicit: trustedExplicit ?? inheritedExplicit,\n scoped: trustedScoped ?? inheritedScoped,\n implicit:\n !adminVeto &&\n [...trustedRules, ...inheritedRules].some((rule) =>\n isNonEmptyString(rule.matchHost),\n ),\n };\n if (\n !isUndefined(internalHostGrant.explicit) ||\n !isUndefined(internalHostGrant.scoped) ||\n internalHostGrant.implicit\n ) {\n res.internalHostGrant = internalHostGrant;\n }\n\n delete res.hostType;\n delete res.resolvedHost;\n delete res.matchHost;\n delete res.readOnly;\n delete res.trustTier;\n delete res.trusted;\n delete res.allowInternal;\n return res;\n}\n\n// the functions below leave out the rules registered by `detachUrlCredentials()`, which apply only to the requests `find()` is searched for\n\nexport function hosts({ hostType }: { hostType: string }): string[] {\n return configuredRules()\n .filter((rule) => rule.hostType === hostType)\n .map((rule) => rule.resolvedHost)\n .filter(isTruthy);\n}\n\nexport function hostType({ url }: { url: string }): string | null {\n const matchingRules = configuredRules().filter(\n (rule) => rule.matchHost && matchesHost(url, rule.matchHost),\n );\n return (\n matchingRules\n .sort(fromShorterToLongerMatchHost)\n .map((rule) => rule.hostType)\n .filter(isTruthy)\n .pop() ?? null\n );\n}\n\nexport function findAll({ hostType }: { hostType: string }): HostRule[] {\n return configuredRules().filter((rule) => rule.hostType === hostType);\n}\n\n/**\n * @returns a deep copy of all host rules other than those registered by {@link detachUrlCredentials}\n */\nexport function getAll(): HostRule[] {\n return clone(configuredRules());\n}\n\nexport function clear(): void {\n logger.debug('Clearing hostRules');\n hostRules = [];\n detachedCredentials = [];\n detachedRules = new WeakSet();\n sanitize.clearRepoSanitizedSecretsList();\n}\n"],"mappings":";;;;;;;;;;;;AAuEA,IAAI,YAAkC,CAAC;AAYvC,IAAI,sBAA6C,CAAC;;;;AAKlD,IAAI,gCAAgB,IAAI,QAAkB;;;;;;AAO1C,MAAa,qBAAyC;CACpD;CACA;CACA;CAEA;CAEA;AACF;AAUA,SAAgB,YAAY,MAA2C;CACrE,MAAM,SAAoC,MAAM,IAAI;CACpD,OAAO,OAAO;CACd,OAAO,OAAO;CACd,OAAO,OAAO;CACd,MAAM,SAAmB;CAEzB,MAAM,EAAE,cAAc;CACtB,MAAM,EAAE,UAAU,YAAY,YAAY;CAC1C,MAAM,aAAa;EAAC;EAAW;EAAU;EAAY;CAAO,CAAC,CAAC,OAC5D,QACF;CACA,IAAI,WAAW,WAAW,GAAG;EAC3B,MAAM,CAAC,aAAa;EACpB,OAAO,YAAY;CACrB,OAAO,IAAI,WAAW,SAAS,GAC7B,MAAM,IAAI,MACR,oFACF;CAGF,OAAO;AACT;;;;;;;;AASA,SAAgB,qBAAqB,OAA+B;CAClE,MAAM,YAAY,aAAa,IAAI,gBAAgB;CACnD,MAAM,SAAmB,CAAC;CAE1B,MAAM,SAAS,MAAM,KAAK,SAAS;EACjC,IAAI,CAAC,KAAK,SACR,OAAO;EAGT,MAAM,UAAkC,CAAC;EACzC,MAAM,aAAuB,CAAC;EAC9B,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,OAAO,GACrD,IAAI,qBAAqB,MAAM,SAAS,GACtC,QAAQ,QAAQ;OAEhB,WAAW,KAAK,IAAI;EAIxB,IAAI,CAAC,WAAW,QACd,OAAO;EAET,OAAO,KAAK,GAAG,UAAU;EAEzB,MAAM,WAAqB;GAAE,GAAG;GAAM,SAAS;EAAQ;EACvD,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,QAExB,OAAO,SAAS;EAElB,OAAO;CACT,CAAC;CAED,IAAI,OAAO,QACT,OAAO,KACL,EAAE,OAAO,GACT,uFACF;CAEF,OAAO;AACT;;;;;;AAuBA,SAAS,aAAa,SAAiD;CACrE,IAAI,SAAS,SACX,OAAO;CAET,IAAI,SAAS,WACX,OAAO;CAET,OAAO;AACT;;;;;;AAOA,SAAS,SACP,QACA,SACoB;CACpB,IAAI,OAA2B;EAC7B,GAAG,YAAY,MAAM;EAErB,WAAW,aAAa,OAAO;CACjC;CAEA,OAAO,KAAK;CACZ,OAAO,KAAK;CAGZ,IAAI,CAAC,YAAY,KAAK,aAAa,KAAK,KAAK,cAAc,aAAa;EACtE,OAAO,MACL,wCAAwC,KAAK,aAAa,KAAK,SAAS,uBAC1E;EACA,OAAO,KAAK;CACd;CAEA,IAAI,KAAK,WAAW,KAAK,cAAc,SAGrC,CAAC,QAAQ,qBAAqB,CAAC,IAAI,CAAC;CAGtC,IAAI,KAAK,WAAW;EAClB,KAAK,YAAY,eAAe,KAAK,SAAS;EAC9C,MAAM,YAAY,SAAS,KAAK,SAAS;EACzC,KAAK,eAAe,WAAW,YAAY,KAAK;EAChD,mBAAmB,SAAS,UAAU;GACpC,IAAI,KAAK,QACP,OAAO,MAEL,UAAU,MAAM,sBAAsB,KAAK,UAAW,aACpD,KAAK,SACN,eACH;EAEJ,CAAC;CACH;CACA,UAAU,KAAK,IAAI;CACnB,OAAO;AACT;AAEA,SAAgB,IAAI,QAAkB,SAAoC;CACxE,MAAM,OAAO,SAAS,QAAQ,OAAO;CACrC,mBAAmB,SAAS,UAAU;EACpC,MAAM,SAAS,KAAK;EACpB,IAAI,SAAS,MAAM,KAAK,OAAO,SAAS,GACtC,uBAAgC,MAAM;CAE1C,CAAC;CACD,IAAI,KAAK,YAAY,KAAK,UACxB,uBACE,SAAS,GAAG,KAAK,SAAS,GAAG,KAAK,UAAU,CAC9C;AAEJ;;;;AAKA,MAAM,4BAA4B;CAChC;CACA;CACA;CACA;CACA;CACA;AACF;;;;AAKA,SAAS,sBAAsB,WAA+B;CAC5D,MAAM,oBAAoB,0BAA0B,QAAQ,aAC1D,kBAAkB,SAAS,CAAC,kBAAkB,MAAM,aAClD,UAAU,SAAS,QAAQ,CAC7B,CACF;CACA,OAAO,CAAC,GAAG,WAAW,GAAG,iBAAiB;AAC5C;;;;AAKA,SAAgB,sBAAsB,KAAsB;CAC1D,IAAI,IAAI,WAAW,yBAAyB,GAC1C,OAAO;CAET,MAAM,mBAAmB,aAAa,IAAI,UAAU;CACpD,IAAI,aAAa,IAAI,UAAU,MAAM,YAAY,CAAC,kBAChD,OAAO;CAET,MAAM,cAAc,SAAS,GAAG,CAAC,EAAE;CACnC,OAAO,CAAC,CAAC,eAAe,gBAAgB,SAAS,gBAAgB,CAAC,EAAE;AACtE;;;;AAKA,SAAS,WAAW,MAAmC;CACrD,OAAO,cAAc,IAAI,IAAI;AAC/B;;;;AAKA,SAAS,kBAAwC;CAC/C,OAAO,UAAU,QAAQ,SAAS,CAAC,WAAW,IAAI,CAAC;AACrD;;;;AAKA,SAAS,gBACP,MACA,UACS;CACT,OAAO,CAAC,YAAY,KAAK,QAAQ,KAAK,KAAK,aAAa;AAC1D;;;;AAKA,SAAS,eACP,MACA,WACA,WACS;CACT,IAAI,WAAW,IAAI,GACjB,OAAO,KAAK,cAAc,aAAa,UAAU,SAAS,KAAK,QAAS;CAI1E,IACE,KAAK,YACL,CAAC,UAAU,SAAS,KAAK,QAAQ,KACjC,CAAC,gBAAgB,MAAM,IAAI,GAE3B,OAAO;CAET,OACE,CAAC,KAAK,aACN,YAAY,WAAW,KAAK,SAAS,KACrC,YAAY,KAAK,WAAW,SAAS;AAEzC;;;;AAKA,SAAS,yBACP,WACA,WACS;CACT,MAAM,oBAAoB,sBAAsB,SAAS;CACzD,IAAI,sBAAsB,SAAS,GACjC,kBAAkB,KAAK,QAAQ;CAEjC,OAAO,UAAU,MACd,SACC,eAAe,MAAM,WAAW,iBAAiB,KACjD;EAAC,KAAK;EAAU,KAAK;EAAU,KAAK;CAAK,CAAC,CAAC,MACxC,UAAU,CAAC,YAAY,KAAK,CAC/B,CACJ;AACF;;;;AAKA,SAAS,uBAAuB,MAAsB;CAEpD,MAAM,CAAC,SAAS,KAAK,SAAS,gBAAgB;CAC9C,IAAI,OAAO,UAAU,GACnB,OAAO;CAET,OAAO,GAAG,MAAM,OAAQ,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE,CAAC,MAAM;AAChE;;;;AAKA,SAAS,gBAAgB,KAAa,QAAqB;CACzD,MAAM,WAAW,uBAAuB,GAAG;CAC3C,MAAM,WAAW,SAAS,QAAQ;CAClC,IACE,UAAU,SAAS,OAAO,QAC1B,UAAU,YACV,UAAU,UAEV,OAAO,oBAAoB,MAAM;CAEnC,OAAO;AACT;;;;AAKA,SAAS,iBAAiB,KAAyB;CACjD,IAAI,CAAC,IAAI,SAAS,GAAG,GACnB,OAAO;CAET,MAAM,SAAS,SAAS,GAAG;CAC3B,IAAI,CAAC,UAAU,CAAC,UAAU,MAAM,GAC9B,OAAO;CAET,IAAI,CAAC,OAAO,YAAY,CAAC,OAAO,UAC9B,OAAO;CAGT,IAAI,CAAC,OAAO,YAAY,sBAAsB,OAAO,QAAQ,GAC3D,OAAO;CAET,OAAO;AACT;;;;AAKA,SAAS,uBAAuB,QAAsB;CAGpD,OAAO,sBAAsB,OAAO,YAAY,OAAO,QAAQ;AACjE;;;;AAKA,SAAS,sBAAsB,QAAsB;CACnD,MAAM,SAAS,OAAO,SAAS,MAAM,GAAG,EAAE;CAC1C,MAAM,WAAW,OAAO,WACpB,GAAG,OAAO,SAAS,GAAG,OAAO,aAC7B,OAAO;CACX,OAAO,qBACL,QACA,UACA,GAAG,OAAO,OAAO,OAAO,UAC1B;AACF;;;;AAKA,MAAM,8BAA8B;;;;AAKpC,SAAS,2BAA2B,SAAyB;CAC3D,KAAK,MAAM,UAAU,QAAQ,QAC1B,UAAU,MAAM,UAAU,2BAC7B,GACE,uBAAgC,MAAM;AAE1C;;;;AAKA,MAAM,eAAe;;;;;;;;AASrB,SAAgB,qBAAqB,KAAa,WAA6B;CAE7E,IAAI,IAAI,WAAW,YAAY,GAC7B,OAAO,GAAG,eAAe,qBAAqB,IAAI,MAAM,CAAmB,GAAG,SAAS;CAEzF,MAAM,SAAS,iBAAiB,GAAG;CACnC,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,cAAc,gBAAgB,KAAK,MAAM;CAE/C,IAAI,uBAAuB,MAAM,GAC/B,OAAO;CAET,IAAI,CAAC,sBAAsB,MAAM,GAC/B,OAAO;CAET,MAAM,WAAW,eAAe,OAAO,QAAQ;CAC/C,MAAM,WAAW,eAAe,OAAO,QAAQ;CAG/C,MAAM,SAAS,YAAY;CAE3B,MAAM,WAAW,kBAAkB,GAAG,OAAO,SAAS,OAAO,UAAU;CAEvE,MAAM,YAAY,GAAG,SAAS;CAK9B,MAAM,sBAAsB,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,QACjD,aAAa,CAAC,yBAAyB,WAAW,CAAC,QAAQ,CAAC,CAC/D;CACA,IAAI,CAAC,oBAAoB,QAAQ;EAE/B,2BAA2B,CAAC,MAAM,CAAC;EACnC,OAAO;CACT;CAGA,KAAK,MAAM,YAAY,qBACrB,cAAc,IAAI,SAAS;EAAE;EAAU;EAAW;EAAU;CAAS,CAAC,CAAC;CASzE,IAAI,CANY,oBAAoB,MACjC,gBACC,YAAY,QAAQ,YACpB,YAAY,aAAa,YACzB,YAAY,aAAa,QAElB,GACT,oBAAoB,KAAK;EAAE,KAAK;EAAU;EAAU;CAAS,CAAC;CAGhE,2BAA2B,CAAC,QADV,WAAW,SAAS,GAAG,SAAS,GAAG,UAAU,IAAI,EACtB,CAAC;CAC9C,OAAO;AACT;;;;AAKA,MAAM,cAAc,MAAM,kBAAkB;;;;AAK5C,MAAM,kBAAkB,MACtB,kEACF;;;;AAKA,SAAS,yBAAyB,MAAsB;CAEtD,MAAM,CAAC,cAAc,gBAAgB,KAAK,IAAI;CAC9C,MAAM,SAAS,SAAS,UAAU;CAClC,IAAI,CAAC,UAAU,CAAC,UAAU,MAAM,KAAK,OAAO,YAAY,OAAO,UAC7D,OAAO;CAGT,OAAO,GADU,kBAAkB,GAAG,OAAO,SAAS,OAAO,UAC5C,IAAI,KAAK,MAAM,WAAW,MAAM;AACnD;;;;AAKA,SAAgB,yBAAkC;CAChD,OAAO,oBAAoB,SAAS;AACtC;;;;AAKA,SAAgB,wBAAwB,EACtC,UACA,OAIiD;CACjD,MAAM,OAAO,UACV,QACE,SACC,WAAW,IAAI,KACf,KAAK,aAAa,YAClB,YAAY,KAAK,KAAK,SAAU,CACpC,CAAC,CACA,KAAK,4BAA4B,CAAC,CAClC,IAAI;CACP,IAAI,CAAC,MACH,OAAO;CAET,OAAO;EAAE,UAAU,KAAK;EAAU,UAAU,KAAK;CAAS;AAC5D;;;;AAKA,SAAgB,2BAA2B,MAA6B;CAEtE,MAAM,UAAU,KAAK,WAAW,YAAY,IACxC,KAAK,MAAM,CAAmB,IAC9B;CAEJ,MAAM,kBAAkB,CAAC,SAAS,uBAAuB,OAAO,CAAC,CAAC,CAAC,IACjE,wBACF;CACA,MAAM,cAAc,oBACjB,QAAQ,EAAE,UACT,gBAAgB,MACb,mBACC,eAAe,WAAW,GAAG,KAC7B,YAAY,KAAK,eAAe,MAAM,IAAI,MAAM,CAAC,CACrD,CACF,CAAC,CACA,KAAK,EAAE,UAAU,eAAe,CAAC,UAAU,QAAQ,CAAC;CACvD,IAAI,CAAC,YAAY,QACf,OAAO;CAET,OAAO,SAAS,KAAK,UAAU,WAAW,CAAC;AAC7C;AAaA,SAAgB,YACd,KACA,WACA,EAAE,aAAa,UAA8B,CAAC,GACrC;CACT,MAAM,YAAY,SAAS,GAAG;CAC9B,IAAI,CAAC,WACH,OAAO;CAGT,MAAM,kBAAkB,SAAS,SAAS;CAC1C,IAAI,UAAU,SAAS,KAAK,UAAU,eAAe,GAAG;EACtD,MAAM,EAAE,SAAS;EAGjB,IAAI,oBAAoB,SAAS,CAAC,CAAC,WAAW,IAAI,GAChD,OAAO;EAET,OACE,CAAC,cACD,oBAAoB,GAAG,UAAU,SAAS,UAAU,UAAU,MAAM;CAExE;CAEA,MAAM,EAAE,aAAa;CACrB,IAAI,CAAC,UACH,OAAO;CAGT,IAAI,aAAa,WACf,OAAO;CAGT,MAAM,iBAAiB,UAAU,WAAW,GAAG,IAC3C,YACA,IAAI;CACR,OAAO,SAAS,SAAS,cAAc;AACzC;AAEA,SAAS,6BAA6B,GAAa,GAAqB;CACtE,IAAI,CAAC,EAAE,aAAa,CAAC,EAAE,WACrB,OAAO;CAET,OAAO,EAAE,UAAU,SAAS,EAAE,UAAU;AAC1C;AAEA,SAAS,aAAa,EAAE,UAAU,WAAW,YAA8B;CAEzE,KADiB,CAAC,CAAC,YAAY,CAAC,CAAC,aACjB,WACd,OAAO;CAGT,IAAI,WACF,OAAO;CAGT,IAAI,UACF,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,sBAAsB,GAAa,GAAqB;CAC/D,OAAO,aAAa,CAAC,IAAI,aAAa,CAAC;AACzC;AAGA,SAAS,iCAAiC,GAAa,GAAqB;CAC1E,OAAO,sBAAsB,GAAG,CAAC,KAAK,6BAA6B,GAAG,CAAC;AACzE;;;;;;AAOA,SAAS,2BACP,OACoC;CACpC,OAAO,MACJ,KAAK,SAAS,KAAK,OAAO,CAAC,CAC3B,OAAO,QAAQ,CAAC,CAChB,IAAI;AACT;;;;AAKA,SAAS,YAAe,QAA0C;CAChE,OAAO,OAAO,QAAQ,UAAU,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI;AAC3D;;;;AAKA,SAAS,YAAY,OAAmD;CACtE,OAAO,MAAM,QACV,SAAS,iBAAiB,KAAK,QAAQ,KAAK,UAAU,KAAK,SAAS,CACvE;AACF;AAEA,SAAgB,KACd,QACoC;CACpC,IAAI,CAAC,OAAO,UAAU,OAAO,GAAG,CAAC,CAAC,MAAM,OAAO,GAAG;EAChD,OAAO,KAAK,EAAE,OAAO,GAAG,0BAA0B;EAClD,OAAO,CAAC;CACV;CAGA,MAAM,cAAc,UAAU,KAAK,gCAAgC;CAEnE,MAAM,eAAqC,CAAC;CAC5C,KAAK,MAAM,QAAQ,aAAa;EAC9B,IAAI,gBAAgB;EACpB,IAAI,YAAY;EAChB,IAAI,gBAAgB;EAEpB,IAAI,KAAK,UAAU;GACjB,gBAAgB;;GAEhB,IAAI,OAAO,aAAa,KAAK,UAC3B,gBAAgB;EAEpB;EAEA,IAAI,KAAK,aAAa,KAAK,cAAc;GACvC,YAAY;GACZ,IAAI,OAAO,KACT,YAAY,YAAY,OAAO,KAAK,KAAK,SAAS;EAEtD;EAEA,IAAI,CAAC,YAAY,KAAK,QAAQ,GAAG;GAC/B,gBAAgB;;GAEhB,IAAI,gBAAgB,MAAM,OAAO,QAAQ,GAAG;IAC1C,gBAAgB;IAChB,gBAAgB;GAClB;EACF;EAEA,IAAI,iBAAiB,iBAAiB,WACpC,aAAa,KAAK,MAAM,IAAI,CAAC;CAEjC;CAEA,MAAM,MACJ,OAAO,OAAO,CAAC,GAAG,GAAG,YAAY;CAGnC,MAAM,eAAe,aAAa,QAC/B,SAAS,KAAK,cAAc,OAC/B;CACA,MAAM,iBAAiB,aAAa,QACjC,SAAS,KAAK,cAAc,SAC/B;CACA,MAAM,iBAAiB,aAAa,QACjC,SAAS,KAAK,cAAc,WAC/B;CAKA,MAAM,mBAAmB,2BAA2B,cAAc;CAClE,MAAM,mBAAmB,2BAA2B,cAAc;CAClE,MAAM,iBAAiB,2BAA2B,YAAY;CAC9D,IAAI,oBAAoB,oBAAoB,gBAAgB;EAE1D,IAAI,UAAU;GACZ,GAAG;GACH,GAAG;GACH,GAAG;EACL;EAMA,IAAI,qBAAqB,iBAAiB,OAAO,KAAK,cAAc,IAAI,CAAC;CAC3E;CAGA,MAAM,UACJ,YAAY,aAAa,KAAK,SAAS,KAAK,OAAO,CAAC,KACpD,YAAY,eAAe,KAAK,SAAS,KAAK,OAAO,CAAC,KACtD,YAAY,eAAe,KAAK,SAAS,KAAK,OAAO,CAAC;CACxD,IAAI,CAAC,YAAY,OAAO,GACtB,IAAI,UAAU;CAMhB,MAAM,kBAAkB,YACtB,aAAa,KAAK,SAAS,KAAK,aAAa,CAC/C;CACA,MAAM,gBAAgB,YACpB,YAAY,YAAY,CAAC,CAAC,KAAK,SAAS,KAAK,aAAa,CAC5D;CAEA,MAAM,YAAY,oBAAoB,SAAS,kBAAkB;CAEjE,MAAM,oBAAoB,YACtB,QACA,YAAY,eAAe,KAAK,SAAS,KAAK,aAAa,CAAC;CAChE,MAAM,kBAAkB,YACpB,QACA,YACE,YAAY,cAAc,CAAC,CAAC,KAAK,SAAS,KAAK,aAAa,CAC9D;CACJ,MAAM,oBAAuC;EAC3C,UAAU,mBAAmB;EAC7B,QAAQ,iBAAiB;EACzB,UACE,CAAC,aACD,CAAC,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC,MAAM,SACzC,iBAAiB,KAAK,SAAS,CACjC;CACJ;CACA,IACE,CAAC,YAAY,kBAAkB,QAAQ,KACvC,CAAC,YAAY,kBAAkB,MAAM,KACrC,kBAAkB,UAElB,IAAI,oBAAoB;CAG1B,OAAO,IAAI;CACX,OAAO,IAAI;CACX,OAAO,IAAI;CACX,OAAO,IAAI;CACX,OAAO,IAAI;CACX,OAAO,IAAI;CACX,OAAO,IAAI;CACX,OAAO;AACT;AAIA,SAAgB,MAAM,EAAE,YAA4C;CAClE,OAAO,gBAAgB,CAAC,CACrB,QAAQ,SAAS,KAAK,aAAa,QAAQ,CAAC,CAC5C,KAAK,SAAS,KAAK,YAAY,CAAC,CAChC,OAAO,QAAQ;AACpB;AAEA,SAAgB,SAAS,EAAE,OAAuC;CAIhE,OAHsB,gBAAgB,CAAC,CAAC,QACrC,SAAS,KAAK,aAAa,YAAY,KAAK,KAAK,SAAS,CAG/C,CAAC,CACV,KAAK,4BAA4B,CAAC,CAClC,KAAK,SAAS,KAAK,QAAQ,CAAC,CAC5B,OAAO,QAAQ,CAAC,CAChB,IAAI,KAAK;AAEhB;AAEA,SAAgB,QAAQ,EAAE,YAA8C;CACtE,OAAO,gBAAgB,CAAC,CAAC,QAAQ,SAAS,KAAK,aAAa,QAAQ;AACtE;;;;AAKA,SAAgB,SAAqB;CACnC,OAAO,MAAM,gBAAgB,CAAC;AAChC;AAEA,SAAgB,QAAc;CAC5B,OAAO,MAAM,oBAAoB;CACjC,YAAY,CAAC;CACb,sBAAsB,CAAC;CACvB,gCAAgB,IAAI,QAAQ;CAC5B,8BAAuC;AACzC"}
@@ -1,5 +1,6 @@
1
1
  import { logger } from "../../../logger/index.js";
2
2
  import { HttpCacheStats } from "../../stats.js";
3
+ import { isNotModified, isOk } from "../status.js";
3
4
  import { copyResponse } from "../util.js";
4
5
  import { persistedResponseHeaders, pickHeaders } from "./persisted-headers.js";
5
6
  import { HttpCache } from "./schema.js";
@@ -40,7 +41,7 @@ var AbstractHttpCacheProvider = class {
40
41
  return Promise.resolve(null);
41
42
  }
42
43
  async wrapServerResponse(method, url, resp) {
43
- if (!resp.cached && resp.statusCode === 200) {
44
+ if (!resp.cached && isOk(resp)) {
44
45
  const etag = resp.headers?.etag;
45
46
  const lastModified = resp.headers?.["last-modified"];
46
47
  HttpCacheStats.incRemoteMisses(url);
@@ -62,7 +63,7 @@ var AbstractHttpCacheProvider = class {
62
63
  await this.persist(method, url, newHttpCache);
63
64
  return resp;
64
65
  }
65
- if (resp.statusCode === 304) {
66
+ if (isNotModified(resp)) {
66
67
  const httpCache = await this.get(method, url);
67
68
  if (!httpCache) return resp;
68
69
  const timestamp = httpCache.timestamp;
@@ -1 +1 @@
1
- {"version":3,"file":"abstract-http-cache-provider.js","names":[],"sources":["../../../../lib/util/http/cache/abstract-http-cache-provider.ts"],"sourcesContent":["import { DateTime } from 'luxon';\nimport { logger } from '../../../logger/index.ts';\nimport { HttpCacheStats } from '../../stats.ts';\nimport type { GotOptions, HttpResponse } from '../types.ts';\nimport { copyResponse } from '../util.ts';\nimport { persistedResponseHeaders, pickHeaders } from './persisted-headers.ts';\nimport { HttpCache, type HttpCacheResponse } from './schema.ts';\nimport type { HttpCacheProvider } from './types.ts';\n\nexport abstract class AbstractHttpCacheProvider implements HttpCacheProvider {\n /** the response headers kept in a persisted entry, or `null` to keep all */\n protected readonly persistedHeaders: readonly string[] | null =\n persistedResponseHeaders;\n\n protected abstract load(method: string, url: string): Promise<unknown>;\n protected abstract persist(\n method: string,\n url: string,\n data: HttpCache,\n ): Promise<void>;\n\n /**\n * Reduces the headers of a persisted or loaded response to `persistedHeaders`.\n *\n * @returns whether any header was dropped\n */\n private pickPersistedHeaders(\n response: Pick<HttpCacheResponse, 'headers'>,\n ): boolean {\n if (!this.persistedHeaders) {\n return false;\n }\n const headers = pickHeaders(response.headers, this.persistedHeaders);\n const dropped =\n Object.keys(headers).length < Object.keys(response.headers).length;\n response.headers = headers;\n return dropped;\n }\n\n async get(method: string, url: string): Promise<HttpCache | null> {\n const cache = await this.load(method, url);\n const httpCache = HttpCache.parse(cache);\n if (!httpCache) {\n return null;\n }\n\n httpCache.httpResponse.cached = true;\n // ensure that a previous cache entry, which may not have filtered all headers, has had the header filtering applied\n if (this.pickPersistedHeaders(httpCache.httpResponse)) {\n await this.persist(method, url, httpCache);\n }\n return httpCache;\n }\n\n async setCacheHeaders<T extends Pick<GotOptions, 'headers'>>(\n method: string,\n url: string,\n opts: T,\n ): Promise<void> {\n const httpCache = await this.get(method, url);\n if (!httpCache) {\n return;\n }\n\n opts.headers ??= {};\n\n if (httpCache.etag) {\n opts.headers['If-None-Match'] = httpCache.etag;\n }\n\n if (httpCache.lastModified) {\n opts.headers['If-Modified-Since'] = httpCache.lastModified;\n }\n }\n\n // v8 ignore next -- TODO: add test #40625\n bypassServer<T>(\n _method: string,\n _url: string,\n _ignoreSoftTtl: boolean,\n ): Promise<HttpResponse<T> | null> {\n return Promise.resolve(null);\n }\n\n async wrapServerResponse<T>(\n method: string,\n url: string,\n resp: HttpResponse<T>,\n ): Promise<HttpResponse<T>> {\n if (!resp.cached && resp.statusCode === 200) {\n const etag = resp.headers?.etag;\n const lastModified = resp.headers?.['last-modified'];\n\n HttpCacheStats.incRemoteMisses(url);\n\n const httpResponse = copyResponse(resp, true);\n this.pickPersistedHeaders(httpResponse);\n const timestamp = DateTime.utc().toISO();\n\n const newHttpCache = HttpCache.parse({\n etag,\n lastModified,\n httpResponse,\n timestamp,\n });\n\n /* v8 ignore next: should never happen */\n if (!newHttpCache) {\n logger.debug(`http cache: failed to persist cache for ${url}`);\n return resp;\n }\n\n logger.debug(\n `http cache: saving ${url} (etag=${etag}, lastModified=${lastModified})`,\n );\n await this.persist(method, url, newHttpCache);\n return resp;\n }\n\n if (resp.statusCode === 304) {\n const httpCache = await this.get(method, url);\n if (!httpCache) {\n return resp;\n }\n\n const timestamp = httpCache.timestamp;\n logger.debug(\n `http cache: Using cached response: ${url} from ${timestamp}`,\n );\n httpCache.timestamp = DateTime.utc().toISO();\n await this.persist(method, url, httpCache);\n\n HttpCacheStats.incRemoteHits(url);\n const cachedResp = copyResponse(\n httpCache.httpResponse as HttpResponse<T>,\n true,\n );\n cachedResp.authorization = resp.authorization;\n return cachedResp;\n }\n\n return resp;\n }\n}\n"],"mappings":";;;;;;;AASA,IAAsB,4BAAtB,MAA6E;;CAE3E,mBACE;;;;;;CAcF,qBACE,UACS;EACT,IAAI,CAAC,KAAK,kBACR,OAAO;EAET,MAAM,UAAU,YAAY,SAAS,SAAS,KAAK,gBAAgB;EACnE,MAAM,UACJ,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,OAAO,KAAK,SAAS,OAAO,CAAC,CAAC;EAC9D,SAAS,UAAU;EACnB,OAAO;CACT;CAEA,MAAM,IAAI,QAAgB,KAAwC;EAChE,MAAM,QAAQ,MAAM,KAAK,KAAK,QAAQ,GAAG;EACzC,MAAM,YAAY,UAAU,MAAM,KAAK;EACvC,IAAI,CAAC,WACH,OAAO;EAGT,UAAU,aAAa,SAAS;EAEhC,IAAI,KAAK,qBAAqB,UAAU,YAAY,GAClD,MAAM,KAAK,QAAQ,QAAQ,KAAK,SAAS;EAE3C,OAAO;CACT;CAEA,MAAM,gBACJ,QACA,KACA,MACe;EACf,MAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,GAAG;EAC5C,IAAI,CAAC,WACH;EAGF,KAAK,YAAY,CAAC;EAElB,IAAI,UAAU,MACZ,KAAK,QAAQ,mBAAmB,UAAU;EAG5C,IAAI,UAAU,cACZ,KAAK,QAAQ,uBAAuB,UAAU;CAElD;;CAGA,aACE,SACA,MACA,gBACiC;EACjC,OAAO,QAAQ,QAAQ,IAAI;CAC7B;CAEA,MAAM,mBACJ,QACA,KACA,MAC0B;EAC1B,IAAI,CAAC,KAAK,UAAU,KAAK,eAAe,KAAK;GAC3C,MAAM,OAAO,KAAK,SAAS;GAC3B,MAAM,eAAe,KAAK,UAAU;GAEpC,eAAe,gBAAgB,GAAG;GAElC,MAAM,eAAe,aAAa,MAAM,IAAI;GAC5C,KAAK,qBAAqB,YAAY;GACtC,MAAM,YAAY,SAAS,IAAI,CAAC,CAAC,MAAM;GAEvC,MAAM,eAAe,UAAU,MAAM;IACnC;IACA;IACA;IACA;GACF,CAAC;;GAGD,IAAI,CAAC,cAAc;IACjB,OAAO,MAAM,2CAA2C,KAAK;IAC7D,OAAO;GACT;GAEA,OAAO,MACL,sBAAsB,IAAI,SAAS,KAAK,iBAAiB,aAAa,EACxE;GACA,MAAM,KAAK,QAAQ,QAAQ,KAAK,YAAY;GAC5C,OAAO;EACT;EAEA,IAAI,KAAK,eAAe,KAAK;GAC3B,MAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,GAAG;GAC5C,IAAI,CAAC,WACH,OAAO;GAGT,MAAM,YAAY,UAAU;GAC5B,OAAO,MACL,sCAAsC,IAAI,QAAQ,WACpD;GACA,UAAU,YAAY,SAAS,IAAI,CAAC,CAAC,MAAM;GAC3C,MAAM,KAAK,QAAQ,QAAQ,KAAK,SAAS;GAEzC,eAAe,cAAc,GAAG;GAChC,MAAM,aAAa,aACjB,UAAU,cACV,IACF;GACA,WAAW,gBAAgB,KAAK;GAChC,OAAO;EACT;EAEA,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"abstract-http-cache-provider.js","names":[],"sources":["../../../../lib/util/http/cache/abstract-http-cache-provider.ts"],"sourcesContent":["import { DateTime } from 'luxon';\nimport { logger } from '../../../logger/index.ts';\nimport { HttpCacheStats } from '../../stats.ts';\nimport { isNotModified, isOk } from '../status.ts';\nimport type { GotOptions, HttpResponse } from '../types.ts';\nimport { copyResponse } from '../util.ts';\nimport { persistedResponseHeaders, pickHeaders } from './persisted-headers.ts';\nimport { HttpCache, type HttpCacheResponse } from './schema.ts';\nimport type { HttpCacheProvider } from './types.ts';\n\nexport abstract class AbstractHttpCacheProvider implements HttpCacheProvider {\n /** the response headers kept in a persisted entry, or `null` to keep all */\n protected readonly persistedHeaders: readonly string[] | null =\n persistedResponseHeaders;\n\n protected abstract load(method: string, url: string): Promise<unknown>;\n protected abstract persist(\n method: string,\n url: string,\n data: HttpCache,\n ): Promise<void>;\n\n /**\n * Reduces the headers of a persisted or loaded response to `persistedHeaders`.\n *\n * @returns whether any header was dropped\n */\n private pickPersistedHeaders(\n response: Pick<HttpCacheResponse, 'headers'>,\n ): boolean {\n if (!this.persistedHeaders) {\n return false;\n }\n const headers = pickHeaders(response.headers, this.persistedHeaders);\n const dropped =\n Object.keys(headers).length < Object.keys(response.headers).length;\n response.headers = headers;\n return dropped;\n }\n\n async get(method: string, url: string): Promise<HttpCache | null> {\n const cache = await this.load(method, url);\n const httpCache = HttpCache.parse(cache);\n if (!httpCache) {\n return null;\n }\n\n httpCache.httpResponse.cached = true;\n // ensure that a previous cache entry, which may not have filtered all headers, has had the header filtering applied\n if (this.pickPersistedHeaders(httpCache.httpResponse)) {\n await this.persist(method, url, httpCache);\n }\n return httpCache;\n }\n\n async setCacheHeaders<T extends Pick<GotOptions, 'headers'>>(\n method: string,\n url: string,\n opts: T,\n ): Promise<void> {\n const httpCache = await this.get(method, url);\n if (!httpCache) {\n return;\n }\n\n opts.headers ??= {};\n\n if (httpCache.etag) {\n opts.headers['If-None-Match'] = httpCache.etag;\n }\n\n if (httpCache.lastModified) {\n opts.headers['If-Modified-Since'] = httpCache.lastModified;\n }\n }\n\n // v8 ignore next -- TODO: add test #40625\n bypassServer<T>(\n _method: string,\n _url: string,\n _ignoreSoftTtl: boolean,\n ): Promise<HttpResponse<T> | null> {\n return Promise.resolve(null);\n }\n\n async wrapServerResponse<T>(\n method: string,\n url: string,\n resp: HttpResponse<T>,\n ): Promise<HttpResponse<T>> {\n if (!resp.cached && isOk(resp)) {\n const etag = resp.headers?.etag;\n const lastModified = resp.headers?.['last-modified'];\n\n HttpCacheStats.incRemoteMisses(url);\n\n const httpResponse = copyResponse(resp, true);\n this.pickPersistedHeaders(httpResponse);\n const timestamp = DateTime.utc().toISO();\n\n const newHttpCache = HttpCache.parse({\n etag,\n lastModified,\n httpResponse,\n timestamp,\n });\n\n /* v8 ignore next: should never happen */\n if (!newHttpCache) {\n logger.debug(`http cache: failed to persist cache for ${url}`);\n return resp;\n }\n\n logger.debug(\n `http cache: saving ${url} (etag=${etag}, lastModified=${lastModified})`,\n );\n await this.persist(method, url, newHttpCache);\n return resp;\n }\n\n if (isNotModified(resp)) {\n const httpCache = await this.get(method, url);\n if (!httpCache) {\n return resp;\n }\n\n const timestamp = httpCache.timestamp;\n logger.debug(\n `http cache: Using cached response: ${url} from ${timestamp}`,\n );\n httpCache.timestamp = DateTime.utc().toISO();\n await this.persist(method, url, httpCache);\n\n HttpCacheStats.incRemoteHits(url);\n const cachedResp = copyResponse(\n httpCache.httpResponse as HttpResponse<T>,\n true,\n );\n cachedResp.authorization = resp.authorization;\n return cachedResp;\n }\n\n return resp;\n }\n}\n"],"mappings":";;;;;;;;AAUA,IAAsB,4BAAtB,MAA6E;;CAE3E,mBACE;;;;;;CAcF,qBACE,UACS;EACT,IAAI,CAAC,KAAK,kBACR,OAAO;EAET,MAAM,UAAU,YAAY,SAAS,SAAS,KAAK,gBAAgB;EACnE,MAAM,UACJ,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,OAAO,KAAK,SAAS,OAAO,CAAC,CAAC;EAC9D,SAAS,UAAU;EACnB,OAAO;CACT;CAEA,MAAM,IAAI,QAAgB,KAAwC;EAChE,MAAM,QAAQ,MAAM,KAAK,KAAK,QAAQ,GAAG;EACzC,MAAM,YAAY,UAAU,MAAM,KAAK;EACvC,IAAI,CAAC,WACH,OAAO;EAGT,UAAU,aAAa,SAAS;EAEhC,IAAI,KAAK,qBAAqB,UAAU,YAAY,GAClD,MAAM,KAAK,QAAQ,QAAQ,KAAK,SAAS;EAE3C,OAAO;CACT;CAEA,MAAM,gBACJ,QACA,KACA,MACe;EACf,MAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,GAAG;EAC5C,IAAI,CAAC,WACH;EAGF,KAAK,YAAY,CAAC;EAElB,IAAI,UAAU,MACZ,KAAK,QAAQ,mBAAmB,UAAU;EAG5C,IAAI,UAAU,cACZ,KAAK,QAAQ,uBAAuB,UAAU;CAElD;;CAGA,aACE,SACA,MACA,gBACiC;EACjC,OAAO,QAAQ,QAAQ,IAAI;CAC7B;CAEA,MAAM,mBACJ,QACA,KACA,MAC0B;EAC1B,IAAI,CAAC,KAAK,UAAU,KAAK,IAAI,GAAG;GAC9B,MAAM,OAAO,KAAK,SAAS;GAC3B,MAAM,eAAe,KAAK,UAAU;GAEpC,eAAe,gBAAgB,GAAG;GAElC,MAAM,eAAe,aAAa,MAAM,IAAI;GAC5C,KAAK,qBAAqB,YAAY;GACtC,MAAM,YAAY,SAAS,IAAI,CAAC,CAAC,MAAM;GAEvC,MAAM,eAAe,UAAU,MAAM;IACnC;IACA;IACA;IACA;GACF,CAAC;;GAGD,IAAI,CAAC,cAAc;IACjB,OAAO,MAAM,2CAA2C,KAAK;IAC7D,OAAO;GACT;GAEA,OAAO,MACL,sBAAsB,IAAI,SAAS,KAAK,iBAAiB,aAAa,EACxE;GACA,MAAM,KAAK,QAAQ,QAAQ,KAAK,YAAY;GAC5C,OAAO;EACT;EAEA,IAAI,cAAc,IAAI,GAAG;GACvB,MAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,GAAG;GAC5C,IAAI,CAAC,WACH,OAAO;GAGT,MAAM,YAAY,UAAU;GAC5B,OAAO,MACL,sCAAsC,IAAI,QAAQ,WACpD;GACA,UAAU,YAAY,SAAS,IAAI,CAAC,CAAC,MAAM;GAC3C,MAAM,KAAK,QAAQ,QAAQ,KAAK,SAAS;GAEzC,eAAe,cAAc,GAAG;GAChC,MAAM,aAAa,aACjB,UAAU,cACV,IACF;GACA,WAAW,gBAAgB,KAAK;GAChC,OAAO;EACT;EAEA,OAAO;CACT;AACF"}
@@ -3,6 +3,7 @@ import { GlobalConfig } from "../../../config/global.js";
3
3
  import { logger } from "../../../logger/index.js";
4
4
  import { buildCacheKey } from "../../cache/package/key.js";
5
5
  import { HttpCacheStats } from "../../stats.js";
6
+ import { isOk } from "../status.js";
6
7
  import { resolveTtlValues } from "../../cache/package/ttl.js";
7
8
  import { get, setWithRawTtl } from "../../cache/package/index.js";
8
9
  import { copyResponse } from "../util.js";
@@ -84,7 +85,7 @@ var PackageHttpCacheProvider = class extends AbstractHttpCacheProvider {
84
85
  return true;
85
86
  }
86
87
  async wrapServerResponse(method, url, resp) {
87
- if (resp.statusCode === 200 && !this.cacheAllowed(resp)) return resp;
88
+ if (isOk(resp) && !this.cacheAllowed(resp)) return resp;
88
89
  return await super.wrapServerResponse(method, url, resp);
89
90
  }
90
91
  };
@@ -1 +1 @@
1
- {"version":3,"file":"package-http-cache-provider.js","names":["packageCache.get","packageCache.setWithRawTtl"],"sources":["../../../../lib/util/http/cache/package-http-cache-provider.ts"],"sourcesContent":["import { isString } from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport type { ZodType } from 'zod/v4';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport { logger } from '../../../logger/index.ts';\nimport * as packageCache from '../../cache/package/index.ts';\nimport { buildCacheKey } from '../../cache/package/key.ts';\nimport { resolveTtlValues } from '../../cache/package/ttl.ts';\nimport type { PackageCacheNamespace } from '../../cache/package/types.ts';\nimport { regEx } from '../../regex.ts';\nimport { HttpCacheStats } from '../../stats.ts';\nimport type { HttpResponse } from '../types.ts';\nimport { copyResponse } from '../util.ts';\nimport { AbstractHttpCacheProvider } from './abstract-http-cache-provider.ts';\nimport type { HttpCache } from './schema.ts';\n\nexport interface PackageHttpCacheProviderOptions {\n namespace: PackageCacheNamespace;\n softTtlMinutes?: number;\n checkCacheControlHeader: boolean;\n checkAuthorizationHeader: boolean;\n writeSchema?: ZodType;\n}\n\nexport class PackageHttpCacheProvider extends AbstractHttpCacheProvider {\n private namespace: PackageCacheNamespace;\n private defaultTtlMinutes: number;\n private writeSchema?: ZodType;\n\n checkCacheControlHeader: boolean;\n checkAuthorizationHeader: boolean;\n\n constructor({\n namespace,\n softTtlMinutes = 15,\n checkCacheControlHeader,\n checkAuthorizationHeader,\n writeSchema,\n }: PackageHttpCacheProviderOptions) {\n super();\n this.namespace = namespace;\n this.defaultTtlMinutes = softTtlMinutes;\n this.checkCacheControlHeader = checkCacheControlHeader;\n this.checkAuthorizationHeader = checkAuthorizationHeader;\n this.writeSchema = writeSchema;\n }\n\n private get softTtlMinutes(): number {\n const { softTtlMinutes } = resolveTtlValues(\n this.namespace,\n this.defaultTtlMinutes,\n );\n return softTtlMinutes;\n }\n\n private get hardTtlMinutes(): number {\n const { hardTtlMinutes } = resolveTtlValues(\n this.namespace,\n this.defaultTtlMinutes,\n );\n return hardTtlMinutes;\n }\n\n private cacheKey(method: string, url: string): string {\n if (method !== 'get') {\n return buildCacheKey(method, url);\n }\n return url;\n }\n\n async load(method: string, url: string): Promise<unknown> {\n return await packageCache.get(this.namespace, this.cacheKey(method, url));\n }\n\n async persist(method: string, url: string, data: HttpCache): Promise<void> {\n if (!data) {\n return;\n }\n\n if (!this.writeSchema) {\n await packageCache.setWithRawTtl(\n this.namespace,\n this.cacheKey(method, url),\n data,\n this.hardTtlMinutes,\n );\n return;\n }\n\n const httpResponse = copyResponse(data.httpResponse, false);\n\n const { data: body, error: err } = this.writeSchema.safeParse(\n httpResponse.body,\n );\n\n if (err) {\n logger.once.debug(\n { err, method, namespace: this.namespace, url },\n 'http cache: writeSchema validation failed for response body, skipping cache write',\n );\n return;\n }\n\n httpResponse.body = body;\n\n await packageCache.setWithRawTtl(\n this.namespace,\n this.cacheKey(method, url),\n { ...data, httpResponse },\n this.hardTtlMinutes,\n );\n }\n\n override async bypassServer<T>(\n method: string,\n url: string,\n ignoreSoftTtl = false,\n ): Promise<HttpResponse<T> | null> {\n const cached = await this.get(method, url);\n if (!cached) {\n return null;\n }\n\n if (ignoreSoftTtl) {\n return cached.httpResponse as HttpResponse<T>;\n }\n\n const cachedAt = DateTime.fromISO(cached.timestamp);\n const deadline = cachedAt.plus({ minutes: this.softTtlMinutes });\n const now = DateTime.now();\n if (now >= deadline) {\n HttpCacheStats.incLocalMisses(url);\n return null;\n }\n\n HttpCacheStats.incLocalHits(url);\n return cached.httpResponse as HttpResponse<T>;\n }\n\n cacheAllowed<T>(resp: HttpResponse<T>): boolean {\n const allowedViaGlobalConfig = GlobalConfig.get('cachePrivatePackages');\n if (allowedViaGlobalConfig) {\n return true;\n }\n\n if (this.checkCacheControlHeader) {\n const cacheControl = resp.headers['cache-control'];\n const isPublic =\n isString(cacheControl) &&\n cacheControl\n .toLocaleLowerCase()\n .split(regEx(/\\s*,\\s*/))\n .includes('public');\n\n if (!isPublic) {\n return false;\n }\n }\n\n if (this.checkAuthorizationHeader && resp.authorization) {\n return false;\n }\n\n return true;\n }\n\n override async wrapServerResponse<T>(\n method: string,\n url: string,\n resp: HttpResponse<T>,\n ): Promise<HttpResponse<T>> {\n if (resp.statusCode === 200 && !this.cacheAllowed(resp)) {\n return resp;\n }\n\n return await super.wrapServerResponse(method, url, resp);\n }\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,IAAa,2BAAb,cAA8C,0BAA0B;CACtE;CACA;CACA;CAEA;CACA;CAEA,YAAY,EACV,WACA,iBAAiB,IACjB,yBACA,0BACA,eACkC;EAClC,MAAM;EACN,KAAK,YAAY;EACjB,KAAK,oBAAoB;EACzB,KAAK,0BAA0B;EAC/B,KAAK,2BAA2B;EAChC,KAAK,cAAc;CACrB;CAEA,IAAY,iBAAyB;EACnC,MAAM,EAAE,mBAAmB,iBACzB,KAAK,WACL,KAAK,iBACP;EACA,OAAO;CACT;CAEA,IAAY,iBAAyB;EACnC,MAAM,EAAE,mBAAmB,iBACzB,KAAK,WACL,KAAK,iBACP;EACA,OAAO;CACT;CAEA,SAAiB,QAAgB,KAAqB;EACpD,IAAI,WAAW,OACb,OAAO,cAAc,QAAQ,GAAG;EAElC,OAAO;CACT;CAEA,MAAM,KAAK,QAAgB,KAA+B;EACxD,OAAO,MAAMA,IAAiB,KAAK,WAAW,KAAK,SAAS,QAAQ,GAAG,CAAC;CAC1E;CAEA,MAAM,QAAQ,QAAgB,KAAa,MAAgC;EACzE,IAAI,CAAC,MACH;EAGF,IAAI,CAAC,KAAK,aAAa;GACrB,MAAMC,cACJ,KAAK,WACL,KAAK,SAAS,QAAQ,GAAG,GACzB,MACA,KAAK,cACP;GACA;EACF;EAEA,MAAM,eAAe,aAAa,KAAK,cAAc,KAAK;EAE1D,MAAM,EAAE,MAAM,MAAM,OAAO,QAAQ,KAAK,YAAY,UAClD,aAAa,IACf;EAEA,IAAI,KAAK;GACP,OAAO,KAAK,MACV;IAAE;IAAK;IAAQ,WAAW,KAAK;IAAW;GAAI,GAC9C,mFACF;GACA;EACF;EAEA,aAAa,OAAO;EAEpB,MAAMA,cACJ,KAAK,WACL,KAAK,SAAS,QAAQ,GAAG,GACzB;GAAE,GAAG;GAAM;EAAa,GACxB,KAAK,cACP;CACF;CAEA,MAAe,aACb,QACA,KACA,gBAAgB,OACiB;EACjC,MAAM,SAAS,MAAM,KAAK,IAAI,QAAQ,GAAG;EACzC,IAAI,CAAC,QACH,OAAO;EAGT,IAAI,eACF,OAAO,OAAO;EAIhB,MAAM,WADW,SAAS,QAAQ,OAAO,SACjB,CAAC,CAAC,KAAK,EAAE,SAAS,KAAK,eAAe,CAAC;EAE/D,IADY,SAAS,IACf,KAAK,UAAU;GACnB,eAAe,eAAe,GAAG;GACjC,OAAO;EACT;EAEA,eAAe,aAAa,GAAG;EAC/B,OAAO,OAAO;CAChB;CAEA,aAAgB,MAAgC;EAE9C,IAD+B,aAAa,IAAI,sBACvB,GACvB,OAAO;EAGT,IAAI,KAAK,yBAAyB;GAChC,MAAM,eAAe,KAAK,QAAQ;GAQlC,IAAI,EANF,SAAS,YAAY,KACrB,aACG,kBAAkB,CAAC,CACnB,MAAM,MAAM,SAAS,CAAC,CAAC,CACvB,SAAS,QAAQ,IAGpB,OAAO;EAEX;EAEA,IAAI,KAAK,4BAA4B,KAAK,eACxC,OAAO;EAGT,OAAO;CACT;CAEA,MAAe,mBACb,QACA,KACA,MAC0B;EAC1B,IAAI,KAAK,eAAe,OAAO,CAAC,KAAK,aAAa,IAAI,GACpD,OAAO;EAGT,OAAO,MAAM,MAAM,mBAAmB,QAAQ,KAAK,IAAI;CACzD;AACF"}
1
+ {"version":3,"file":"package-http-cache-provider.js","names":["packageCache.get","packageCache.setWithRawTtl"],"sources":["../../../../lib/util/http/cache/package-http-cache-provider.ts"],"sourcesContent":["import { isString } from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport type { ZodType } from 'zod/v4';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport { logger } from '../../../logger/index.ts';\nimport * as packageCache from '../../cache/package/index.ts';\nimport { buildCacheKey } from '../../cache/package/key.ts';\nimport { resolveTtlValues } from '../../cache/package/ttl.ts';\nimport type { PackageCacheNamespace } from '../../cache/package/types.ts';\nimport { regEx } from '../../regex.ts';\nimport { HttpCacheStats } from '../../stats.ts';\nimport { isOk } from '../status.ts';\nimport type { HttpResponse } from '../types.ts';\nimport { copyResponse } from '../util.ts';\nimport { AbstractHttpCacheProvider } from './abstract-http-cache-provider.ts';\nimport type { HttpCache } from './schema.ts';\n\nexport interface PackageHttpCacheProviderOptions {\n namespace: PackageCacheNamespace;\n softTtlMinutes?: number;\n checkCacheControlHeader: boolean;\n checkAuthorizationHeader: boolean;\n writeSchema?: ZodType;\n}\n\nexport class PackageHttpCacheProvider extends AbstractHttpCacheProvider {\n private namespace: PackageCacheNamespace;\n private defaultTtlMinutes: number;\n private writeSchema?: ZodType;\n\n checkCacheControlHeader: boolean;\n checkAuthorizationHeader: boolean;\n\n constructor({\n namespace,\n softTtlMinutes = 15,\n checkCacheControlHeader,\n checkAuthorizationHeader,\n writeSchema,\n }: PackageHttpCacheProviderOptions) {\n super();\n this.namespace = namespace;\n this.defaultTtlMinutes = softTtlMinutes;\n this.checkCacheControlHeader = checkCacheControlHeader;\n this.checkAuthorizationHeader = checkAuthorizationHeader;\n this.writeSchema = writeSchema;\n }\n\n private get softTtlMinutes(): number {\n const { softTtlMinutes } = resolveTtlValues(\n this.namespace,\n this.defaultTtlMinutes,\n );\n return softTtlMinutes;\n }\n\n private get hardTtlMinutes(): number {\n const { hardTtlMinutes } = resolveTtlValues(\n this.namespace,\n this.defaultTtlMinutes,\n );\n return hardTtlMinutes;\n }\n\n private cacheKey(method: string, url: string): string {\n if (method !== 'get') {\n return buildCacheKey(method, url);\n }\n return url;\n }\n\n async load(method: string, url: string): Promise<unknown> {\n return await packageCache.get(this.namespace, this.cacheKey(method, url));\n }\n\n async persist(method: string, url: string, data: HttpCache): Promise<void> {\n if (!data) {\n return;\n }\n\n if (!this.writeSchema) {\n await packageCache.setWithRawTtl(\n this.namespace,\n this.cacheKey(method, url),\n data,\n this.hardTtlMinutes,\n );\n return;\n }\n\n const httpResponse = copyResponse(data.httpResponse, false);\n\n const { data: body, error: err } = this.writeSchema.safeParse(\n httpResponse.body,\n );\n\n if (err) {\n logger.once.debug(\n { err, method, namespace: this.namespace, url },\n 'http cache: writeSchema validation failed for response body, skipping cache write',\n );\n return;\n }\n\n httpResponse.body = body;\n\n await packageCache.setWithRawTtl(\n this.namespace,\n this.cacheKey(method, url),\n { ...data, httpResponse },\n this.hardTtlMinutes,\n );\n }\n\n override async bypassServer<T>(\n method: string,\n url: string,\n ignoreSoftTtl = false,\n ): Promise<HttpResponse<T> | null> {\n const cached = await this.get(method, url);\n if (!cached) {\n return null;\n }\n\n if (ignoreSoftTtl) {\n return cached.httpResponse as HttpResponse<T>;\n }\n\n const cachedAt = DateTime.fromISO(cached.timestamp);\n const deadline = cachedAt.plus({ minutes: this.softTtlMinutes });\n const now = DateTime.now();\n if (now >= deadline) {\n HttpCacheStats.incLocalMisses(url);\n return null;\n }\n\n HttpCacheStats.incLocalHits(url);\n return cached.httpResponse as HttpResponse<T>;\n }\n\n cacheAllowed<T>(resp: HttpResponse<T>): boolean {\n const allowedViaGlobalConfig = GlobalConfig.get('cachePrivatePackages');\n if (allowedViaGlobalConfig) {\n return true;\n }\n\n if (this.checkCacheControlHeader) {\n const cacheControl = resp.headers['cache-control'];\n const isPublic =\n isString(cacheControl) &&\n cacheControl\n .toLocaleLowerCase()\n .split(regEx(/\\s*,\\s*/))\n .includes('public');\n\n if (!isPublic) {\n return false;\n }\n }\n\n if (this.checkAuthorizationHeader && resp.authorization) {\n return false;\n }\n\n return true;\n }\n\n override async wrapServerResponse<T>(\n method: string,\n url: string,\n resp: HttpResponse<T>,\n ): Promise<HttpResponse<T>> {\n if (isOk(resp) && !this.cacheAllowed(resp)) {\n return resp;\n }\n\n return await super.wrapServerResponse(method, url, resp);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAyBA,IAAa,2BAAb,cAA8C,0BAA0B;CACtE;CACA;CACA;CAEA;CACA;CAEA,YAAY,EACV,WACA,iBAAiB,IACjB,yBACA,0BACA,eACkC;EAClC,MAAM;EACN,KAAK,YAAY;EACjB,KAAK,oBAAoB;EACzB,KAAK,0BAA0B;EAC/B,KAAK,2BAA2B;EAChC,KAAK,cAAc;CACrB;CAEA,IAAY,iBAAyB;EACnC,MAAM,EAAE,mBAAmB,iBACzB,KAAK,WACL,KAAK,iBACP;EACA,OAAO;CACT;CAEA,IAAY,iBAAyB;EACnC,MAAM,EAAE,mBAAmB,iBACzB,KAAK,WACL,KAAK,iBACP;EACA,OAAO;CACT;CAEA,SAAiB,QAAgB,KAAqB;EACpD,IAAI,WAAW,OACb,OAAO,cAAc,QAAQ,GAAG;EAElC,OAAO;CACT;CAEA,MAAM,KAAK,QAAgB,KAA+B;EACxD,OAAO,MAAMA,IAAiB,KAAK,WAAW,KAAK,SAAS,QAAQ,GAAG,CAAC;CAC1E;CAEA,MAAM,QAAQ,QAAgB,KAAa,MAAgC;EACzE,IAAI,CAAC,MACH;EAGF,IAAI,CAAC,KAAK,aAAa;GACrB,MAAMC,cACJ,KAAK,WACL,KAAK,SAAS,QAAQ,GAAG,GACzB,MACA,KAAK,cACP;GACA;EACF;EAEA,MAAM,eAAe,aAAa,KAAK,cAAc,KAAK;EAE1D,MAAM,EAAE,MAAM,MAAM,OAAO,QAAQ,KAAK,YAAY,UAClD,aAAa,IACf;EAEA,IAAI,KAAK;GACP,OAAO,KAAK,MACV;IAAE;IAAK;IAAQ,WAAW,KAAK;IAAW;GAAI,GAC9C,mFACF;GACA;EACF;EAEA,aAAa,OAAO;EAEpB,MAAMA,cACJ,KAAK,WACL,KAAK,SAAS,QAAQ,GAAG,GACzB;GAAE,GAAG;GAAM;EAAa,GACxB,KAAK,cACP;CACF;CAEA,MAAe,aACb,QACA,KACA,gBAAgB,OACiB;EACjC,MAAM,SAAS,MAAM,KAAK,IAAI,QAAQ,GAAG;EACzC,IAAI,CAAC,QACH,OAAO;EAGT,IAAI,eACF,OAAO,OAAO;EAIhB,MAAM,WADW,SAAS,QAAQ,OAAO,SACjB,CAAC,CAAC,KAAK,EAAE,SAAS,KAAK,eAAe,CAAC;EAE/D,IADY,SAAS,IACf,KAAK,UAAU;GACnB,eAAe,eAAe,GAAG;GACjC,OAAO;EACT;EAEA,eAAe,aAAa,GAAG;EAC/B,OAAO,OAAO;CAChB;CAEA,aAAgB,MAAgC;EAE9C,IAD+B,aAAa,IAAI,sBACvB,GACvB,OAAO;EAGT,IAAI,KAAK,yBAAyB;GAChC,MAAM,eAAe,KAAK,QAAQ;GAQlC,IAAI,EANF,SAAS,YAAY,KACrB,aACG,kBAAkB,CAAC,CACnB,MAAM,MAAM,SAAS,CAAC,CAAC,CACvB,SAAS,QAAQ,IAGpB,OAAO;EAEX;EAEA,IAAI,KAAK,4BAA4B,KAAK,eACxC,OAAO;EAGT,OAAO;CACT;CAEA,MAAe,mBACb,QACA,KACA,MAC0B;EAC1B,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,aAAa,IAAI,GACvC,OAAO;EAGT,OAAO,MAAM,MAAM,mBAAmB,QAAQ,KAAK,IAAI;CACzD;AACF"}
@@ -5,6 +5,7 @@ import { regEx } from "../regex.js";
5
5
  import { GlobalConfig } from "../../config/global.js";
6
6
  import { logger } from "../../logger/index.js";
7
7
  import { joinUrlParts, parseLinkHeader, parseUrl } from "../url.js";
8
+ import { isForbidden, isGone, isServerError, isUnauthorized, isUnprocessableContent } from "./status.js";
8
9
  import { find } from "../host-rules.js";
9
10
  import { ExternalHostError } from "../../types/errors/external-host-error.js";
10
11
  import { LooseArray } from "../schema-utils/index.js";
@@ -46,7 +47,7 @@ const GithubErrors = z.union([LooseArray(GithubError), GithubError.transform((er
46
47
  * Whether `err` is GitHub's response to creating a PR for a branch which already has one, usually because a different user created it so Renovate could not find it.
47
48
  */
48
49
  function isPrAlreadyExistsError(err) {
49
- return err?.response?.statusCode === 422 && GithubErrors.parse(err.response.body?.errors).some((e) => e.message?.startsWith("A pull request already exists"));
50
+ return isUnprocessableContent(err) && GithubErrors.parse(err.response?.body?.errors).some((e) => e.message?.startsWith("A pull request already exists"));
50
51
  }
51
52
  function handleGotError(err, url, opts) {
52
53
  const path = url.toString();
@@ -61,23 +62,23 @@ function handleGotError(err, url, opts) {
61
62
  logger.debug({ err }, "");
62
63
  return new ExternalHostError(err, "github");
63
64
  }
64
- if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) {
65
+ if (isServerError(err)) {
65
66
  logger.debug({ err }, "GitHub failure: 5xx");
66
67
  return new ExternalHostError(err, "github");
67
68
  }
68
- if (err.statusCode === 403 && message.startsWith("You have triggered an abuse detection mechanism")) {
69
+ if (isForbidden(err) && message.startsWith("You have triggered an abuse detection mechanism")) {
69
70
  logger.debug({ err }, "GitHub failure: abuse detection");
70
71
  return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);
71
72
  }
72
- if (err.statusCode === 403 && message.startsWith("You have exceeded a secondary rate limit")) {
73
+ if (isForbidden(err) && message.startsWith("You have exceeded a secondary rate limit")) {
73
74
  logger.warn({ err }, "GitHub failure: secondary rate limit");
74
75
  return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);
75
76
  }
76
- if (err.statusCode === 403 && message.includes("Upgrade to GitHub Pro")) {
77
+ if (isForbidden(err) && message.includes("Upgrade to GitHub Pro")) {
77
78
  logger.debug(`Endpoint: ${path}, needs paid GitHub plan`);
78
79
  return err;
79
80
  }
80
- if (err.statusCode === 403 && message.includes("rate limit exceeded")) {
81
+ if (isForbidden(err) && message.includes("rate limit exceeded")) {
81
82
  logger.debug({ err }, "GitHub failure: rate limit");
82
83
  const parsed = parseUrl(baseUrl);
83
84
  const rule = find({ url: baseUrl });
@@ -86,11 +87,11 @@ function handleGotError(err, url, opts) {
86
87
  else logger.once.warn({ host: parsed.host }, "Rate limit exceeded, as no hostRules set for this host");
87
88
  return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);
88
89
  }
89
- if (err.statusCode === 403 && message.startsWith("Resource not accessible by integration")) {
90
+ if (isForbidden(err) && message.startsWith("Resource not accessible by integration")) {
90
91
  logger.debug({ err }, "GitHub failure: Resource not accessible by integration");
91
92
  return new Error(PLATFORM_INTEGRATION_UNAUTHORIZED);
92
93
  }
93
- if (err.statusCode === 401) {
94
+ if (isUnauthorized(err)) {
94
95
  const hostname = parseUrl(url)?.hostname;
95
96
  // v8 ignore else -- TODO: add test #40625
96
97
  if (hostname === "github.com" || hostname === "api.github.com") logger.once.warn("github.com token 401 unauthorized");
@@ -104,7 +105,7 @@ function handleGotError(err, url, opts) {
104
105
  return new Error(PLATFORM_BAD_CREDENTIALS);
105
106
  }
106
107
  }
107
- if (err.statusCode === 422) {
108
+ if (isUnprocessableContent(err)) {
108
109
  if (message.includes("Review cannot be requested from pull request author")) return err;
109
110
  const errors = GithubErrors.parse(err.body?.errors);
110
111
  if (errors.some((e) => e.field === "milestone")) return err;
@@ -116,14 +117,17 @@ function handleGotError(err, url, opts) {
116
117
  logger.debug({ err }, "422 Error thrown from GitHub");
117
118
  return new ExternalHostError(err, "github");
118
119
  }
119
- if (err.statusCode === 410 && err.body?.message === "Issues are disabled for this repo") return err;
120
+ if (isGone(err) && err.body?.message === "Issues are disabled for this repo") return err;
120
121
  return err;
121
122
  }
123
+ function hasVendorAccept(acceptStrings) {
124
+ return acceptStrings.some((x) => x.startsWith("application/vnd.github.")) && acceptStrings.length >= 2;
125
+ }
122
126
  function constructAcceptString(input) {
123
127
  const defaultAccept = "application/vnd.github.v3+json";
124
128
  const acceptStrings = isString(input) ? input.split(regEx(/\s*,\s*/)) : [];
125
129
  // v8 ignore else -- TODO: add test #40625
126
- if (!acceptStrings.some((x) => x === defaultAccept) && (!acceptStrings.some((x) => x.startsWith("application/vnd.github.")) || acceptStrings.length < 2)) acceptStrings.push(defaultAccept);
130
+ if (!acceptStrings.some((x) => x === defaultAccept) && !hasVendorAccept(acceptStrings)) acceptStrings.push(defaultAccept);
127
131
  return acceptStrings.join(", ");
128
132
  }
129
133
  const MAX_GRAPHQL_PAGE_SIZE = 100;
@@ -1 +1 @@
1
- {"version":3,"file":"github.js","names":["hostRules.find","p.all"],"sources":["../../../lib/util/http/github.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyObject,\n isNullOrUndefined,\n isPlainObject,\n isString,\n} from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport { z } from 'zod/v4';\nimport { GlobalConfig } from '../../config/global.ts';\nimport {\n PLATFORM_BAD_CREDENTIALS,\n PLATFORM_INTEGRATION_UNAUTHORIZED,\n PLATFORM_RATE_LIMIT_EXCEEDED,\n REPOSITORY_CHANGED,\n} from '../../constants/error-messages.ts';\nimport { logger } from '../../logger/index.ts';\nimport { ExternalHostError } from '../../types/errors/external-host-error.ts';\nimport { getCache } from '../cache/repository/index.ts';\nimport { getEnv } from '../env.ts';\nimport * as hostRules from '../host-rules.ts';\nimport { maskToken } from '../mask.ts';\nimport { coerceObject } from '../object.ts';\nimport * as p from '../promises.ts';\nimport { range } from '../range.ts';\nimport { regEx } from '../regex.ts';\nimport { LooseArray } from '../schema-utils/index.ts';\nimport { joinUrlParts, parseLinkHeader, parseUrl } from '../url.ts';\nimport { findMatchingRule } from './host-rules.ts';\nimport {\n HttpBase,\n type InternalHttpOptions,\n type InternalJsonUnsafeOptions,\n} from './http.ts';\nimport type { GotLegacyError } from './legacy.ts';\nimport type {\n GraphqlOptions,\n HttpMethod,\n HttpOptions,\n HttpResponse,\n} from './types.ts';\n\nconst githubBaseUrl = 'https://api.github.com/';\nconst MAX_PAGINATION_PAGES = 100;\nlet baseUrl = githubBaseUrl;\nexport function setBaseUrl(url: string): void {\n baseUrl = url;\n}\n\nexport interface GithubBaseHttpOptions extends HttpOptions {\n repository?: string;\n}\n\nexport interface GithubHttpOptions extends GithubBaseHttpOptions {\n paginate?: boolean | string;\n paginationField?: string;\n pageLimit?: number;\n}\n\ninterface GithubGraphqlRepoData<T = unknown> {\n repository?: T;\n}\n\nexport type GithubGraphqlResponse<T = unknown> =\n | {\n data: T;\n errors?: never;\n }\n | {\n data?: never;\n errors: {\n type?: string;\n code?: string;\n message: string;\n }[];\n };\n\n/**\n * GitHub reports a spent GraphQL budget in two shapes: `RATE_LIMITED` from the\n * GraphQL API itself, and `graphql_rate_limit` when an app installation has\n * exhausted its allowance. Both mean \"come back later\", not \"unknown error\".\n */\nfunction isGraphqlRateLimited(\n errors: { type?: string; code?: string }[] | undefined,\n): boolean {\n return !!errors?.some(\n (err) => err.type === 'RATE_LIMITED' || err.code === 'graphql_rate_limit',\n );\n}\n\nconst GithubError = z.object({\n field: z.string().optional(),\n code: z.string().optional(),\n message: z.string().optional(),\n});\n\n/**\n * GitHub usually returns `errors` as an array, but not always - normalize it\n * into an array of error objects, dropping anything that doesn't fit.\n */\nconst GithubErrors = z\n .union([LooseArray(GithubError), GithubError.transform((error) => [error])])\n .catch([]);\n\n/**\n * Whether `err` is GitHub's response to creating a PR for a branch which already has one, usually because a different user created it so Renovate could not find it.\n */\nexport function isPrAlreadyExistsError(err: any): boolean {\n return (\n err?.response?.statusCode === 422 &&\n GithubErrors.parse(err.response.body?.errors).some((e) =>\n e.message?.startsWith('A pull request already exists'),\n )\n );\n}\n\nfunction handleGotError(\n err: GotLegacyError,\n url: string | URL,\n opts: GithubBaseHttpOptions,\n): Error {\n const path = url.toString();\n let message = err.message || '';\n const body = err.response?.body;\n if (isPlainObject(body) && 'message' in body) {\n message = String(body.message);\n }\n if (\n err.code === 'ERR_HTTP2_STREAM_ERROR' ||\n err.code === 'ENOTFOUND' ||\n err.code === 'ETIMEDOUT' ||\n err.code === 'EAI_AGAIN' ||\n err.code === 'ECONNRESET'\n ) {\n logger.debug({ err }, 'GitHub failure: RequestError');\n return new ExternalHostError(err, 'github');\n }\n if (err.name === 'ParseError') {\n logger.debug({ err }, '');\n return new ExternalHostError(err, 'github');\n }\n if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) {\n logger.debug({ err }, 'GitHub failure: 5xx');\n return new ExternalHostError(err, 'github');\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('You have triggered an abuse detection mechanism')\n ) {\n logger.debug({ err }, 'GitHub failure: abuse detection');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('You have exceeded a secondary rate limit')\n ) {\n logger.warn({ err }, 'GitHub failure: secondary rate limit');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) {\n logger.debug(`Endpoint: ${path}, needs paid GitHub plan`);\n return err;\n }\n if (err.statusCode === 403 && message.includes('rate limit exceeded')) {\n logger.debug({ err }, 'GitHub failure: rate limit');\n\n const parsed = parseUrl(baseUrl);\n\n const rule = hostRules.find({ url: baseUrl });\n if (rule.token || rule.password) {\n logger.once.warn(\n 'Rate limit exceeded for api.github.com, even though we are authenticated',\n );\n } else {\n if (parsed?.hostname === 'api.github.com') {\n logger.once.warn(\n {\n documentationUrl: `${GlobalConfig.get('productLinks').documentation}getting-started/running/#githubcom-token-for-changelogs-and-tools`,\n },\n 'Rate limit exceeded for api.github.com, as no hostRules set for this host. Please set a GITHUB_COM_TOKEN',\n );\n } else {\n logger.once.warn(\n { host: parsed!.host },\n 'Rate limit exceeded, as no hostRules set for this host',\n );\n }\n }\n\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('Resource not accessible by integration')\n ) {\n logger.debug(\n { err },\n 'GitHub failure: Resource not accessible by integration',\n );\n return new Error(PLATFORM_INTEGRATION_UNAUTHORIZED);\n }\n if (err.statusCode === 401) {\n // Warn once for github.com token if unauthorized\n const hostname = parseUrl(url)?.hostname;\n // v8 ignore else -- TODO: add test #40625\n if (hostname === 'github.com' || hostname === 'api.github.com') {\n logger.once.warn('github.com token 401 unauthorized');\n }\n if (message.includes('Bad credentials')) {\n const rateLimit = err.headers?.['x-ratelimit-limit'] ?? -1;\n logger.debug(\n {\n token: maskToken(opts.token),\n err,\n },\n 'GitHub failure: Bad credentials',\n );\n if (rateLimit === '60') {\n return new ExternalHostError(err, 'github');\n }\n return new Error(PLATFORM_BAD_CREDENTIALS);\n }\n }\n if (err.statusCode === 422) {\n if (\n message.includes('Review cannot be requested from pull request author')\n ) {\n return err;\n }\n\n const errors = GithubErrors.parse(err.body?.errors);\n if (errors.some((e) => e.field === 'milestone')) {\n return err;\n }\n if (errors.some((e) => e.code === 'invalid')) {\n logger.debug({ err }, 'Received invalid response - aborting');\n return new Error(REPOSITORY_CHANGED);\n }\n if (isPrAlreadyExistsError(err)) {\n return err;\n }\n logger.debug({ err }, '422 Error thrown from GitHub');\n return new ExternalHostError(err, 'github');\n }\n if (\n err.statusCode === 410 &&\n err.body?.message === 'Issues are disabled for this repo'\n ) {\n return err;\n }\n return err;\n}\n\ninterface GraphqlPaginatedContent<T = unknown> {\n nodes?: T[];\n edges?: T[];\n pageInfo: { hasNextPage: boolean; endCursor: string };\n}\n\nfunction constructAcceptString(input?: unknown): string {\n const defaultAccept = 'application/vnd.github.v3+json';\n const acceptStrings = isString(input) ? input.split(regEx(/\\s*,\\s*/)) : [];\n\n // TODO: regression of #6736\n // v8 ignore else -- TODO: add test #40625\n if (\n !acceptStrings.some((x) => x === defaultAccept) &&\n (!acceptStrings.some((x) => x.startsWith('application/vnd.github.')) ||\n acceptStrings.length < 2)\n ) {\n acceptStrings.push(defaultAccept);\n }\n return acceptStrings.join(', ');\n}\n\nconst MAX_GRAPHQL_PAGE_SIZE = 100;\n\ninterface GraphqlPageCacheItem {\n pageLastResizedAt: string;\n pageSize: number;\n}\n\nexport type GraphqlPageCache = Record<string, GraphqlPageCacheItem>;\n\nfunction getGraphqlPageSize(\n fieldName: string,\n defaultPageSize = MAX_GRAPHQL_PAGE_SIZE,\n): number {\n const cache = getCache();\n const graphqlPageCache = cache?.platform?.github\n ?.graphqlPageCache as GraphqlPageCache;\n const cachedRecord = graphqlPageCache?.[fieldName];\n\n if (graphqlPageCache && cachedRecord) {\n logger.debug(\n { fieldName, ...cachedRecord },\n 'GraphQL page size: found cached value',\n );\n\n const oldPageSize = cachedRecord.pageSize;\n\n const now = DateTime.local();\n const then = DateTime.fromISO(cachedRecord.pageLastResizedAt);\n const expiry = then.plus({ hours: 24 });\n if (now > expiry) {\n const newPageSize = Math.min(oldPageSize * 2, MAX_GRAPHQL_PAGE_SIZE);\n if (newPageSize < MAX_GRAPHQL_PAGE_SIZE) {\n const timestamp = now.toISO();\n\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp },\n 'GraphQL page size: expanding',\n );\n\n cachedRecord.pageLastResizedAt = timestamp;\n cachedRecord.pageSize = newPageSize;\n } else {\n logger.debug(\n { fieldName, oldPageSize, newPageSize },\n 'GraphQL page size: expanded to default page size',\n );\n\n delete graphqlPageCache[fieldName];\n }\n\n return newPageSize;\n }\n\n return oldPageSize;\n }\n\n return defaultPageSize;\n}\n\nfunction setGraphqlPageSize(fieldName: string, newPageSize: number): void {\n const oldPageSize = getGraphqlPageSize(fieldName);\n if (newPageSize !== oldPageSize) {\n const now = DateTime.local();\n const pageLastResizedAt = now.toISO();\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp: pageLastResizedAt },\n 'GraphQL page size: shrinking',\n );\n const cache = getCache();\n cache.platform ??= {};\n cache.platform.github ??= {};\n cache.platform.github.graphqlPageCache ??= {};\n const graphqlPageCache = cache.platform.github\n .graphqlPageCache as GraphqlPageCache;\n graphqlPageCache[fieldName] = {\n pageLastResizedAt,\n pageSize: newPageSize,\n };\n }\n}\n\nfunction replaceUrlBase(url: URL, baseUrl: string): URL {\n const relativeUrl = `${url.pathname}${url.search}`;\n return new URL(relativeUrl, baseUrl);\n}\n\nfunction resolvePaginationUrl(\n url: string,\n baseUrl: string | undefined,\n rebasePaginationLinks: boolean,\n): URL {\n const parsedUrl = new URL(url, baseUrl);\n const rebasePagination =\n !!baseUrl &&\n rebasePaginationLinks &&\n // Preserve github.com URLs for use cases like release notes\n parsedUrl.origin !== 'https://api.github.com';\n return rebasePagination ? replaceUrlBase(parsedUrl, baseUrl) : parsedUrl;\n}\n\nexport class GithubHttp extends HttpBase<GithubHttpOptions> {\n protected override get baseUrl(): string | undefined {\n return baseUrl;\n }\n\n constructor(hostType = 'github', options?: HttpOptions) {\n super(hostType, options);\n }\n\n protected override extraOptions(): readonly string[] {\n return super\n .extraOptions()\n .concat([\n 'pageLimit',\n 'paginate',\n 'paginationField',\n 'repository',\n ] as (keyof GithubHttpOptions)[]);\n }\n\n protected override processOptions(\n url: URL,\n opts: InternalHttpOptions & GithubBaseHttpOptions,\n ): void {\n if (!opts.token) {\n // create a mutable copy of `url`\n const authUrl = parseUrl(url.toString())!;\n\n if (opts.repository) {\n // set authUrl to https://api.github.com/repos/org/repo or https://gihub.domain.com/api/v3/repos/org/repo\n authUrl.hash = '';\n authUrl.search = '';\n authUrl.pathname = joinUrlParts(\n authUrl.pathname.startsWith('/api/v3') ? '/api/v3' : '',\n 'repos',\n `${opts.repository}`,\n );\n }\n\n let readOnly = opts.readOnly;\n const { method = 'get' } = opts;\n if (\n readOnly === undefined &&\n ['get', 'head'].includes(method.toLowerCase())\n ) {\n readOnly = true;\n }\n const { token } = findMatchingRule(authUrl.toString(), {\n hostType: this.hostType,\n readOnly,\n });\n opts.token = token;\n }\n\n const accept = constructAcceptString(opts.headers?.accept);\n\n opts.headers = {\n ...opts.headers,\n accept,\n };\n }\n\n protected override handleError(\n url: string | URL,\n opts: HttpOptions,\n err: GotLegacyError,\n ): never {\n throw handleGotError(err, url, opts);\n }\n\n protected override async requestJsonUnsafe<T>(\n method: HttpMethod,\n options: InternalJsonUnsafeOptions<GithubHttpOptions>,\n ): Promise<HttpResponse<T>> {\n const httpOptions = coerceObject(options.httpOptions);\n const resolvedUrl = this.resolveUrl(options.url, httpOptions);\n const opts = {\n ...options,\n url: resolvedUrl,\n };\n\n const result = await super.requestJsonUnsafe<T>(method, opts);\n if (httpOptions.paginate) {\n delete httpOptions.cacheProvider;\n httpOptions.memCache = false;\n // Check if result is paginated\n const pageLimit = httpOptions.pageLimit ?? 10;\n const linkHeader = parseLinkHeader(result?.headers?.link);\n const next = linkHeader?.next;\n const env = getEnv();\n if (next?.url) {\n const baseUrl = httpOptions.baseUrl ?? this.baseUrl;\n const rebasePaginationLinks = !!env.RENOVATE_X_REBASE_PAGINATION_LINKS;\n const firstPageUrl = resolvePaginationUrl(\n next.url,\n baseUrl,\n rebasePaginationLinks,\n );\n // Don't follow a cross-origin request, unless we've been explicitly requested to do so with `RENOVATE_X_REBASE_PAGINATION_LINKS`\n if (firstPageUrl.origin === resolvedUrl.origin) {\n let pages: HttpResponse<T>[];\n if (linkHeader?.last?.page) {\n logger.debug('Using GitHub offset-based pagination');\n let lastPage = parseInt(linkHeader.last.page, 10);\n // v8 ignore else -- TODO: add test #40625\n if (!env.RENOVATE_PAGINATE_ALL && httpOptions.paginate !== 'all') {\n lastPage = Math.min(pageLimit, lastPage);\n }\n const queue = [...range(2, lastPage)].map(\n (pageNumber) => (): Promise<HttpResponse<T>> => {\n // copy before modifying searchParams\n const nextUrl = parseUrl(firstPageUrl.toString())!;\n nextUrl.searchParams.set('page', String(pageNumber));\n return super.requestJsonUnsafe<T>(method, {\n ...opts,\n url: nextUrl,\n });\n },\n );\n pages = await p.all(queue);\n } else {\n logger.debug('Using GitHub cursor-based pagination');\n pages = [];\n const paginateAll =\n !!env.RENOVATE_PAGINATE_ALL || httpOptions.paginate === 'all';\n const cursorPageLimit = paginateAll\n ? MAX_PAGINATION_PAGES\n : pageLimit;\n let nextUrl: URL | null = firstPageUrl;\n let pageNumber = 2;\n for (; nextUrl && pageNumber <= cursorPageLimit; pageNumber += 1) {\n if (nextUrl.origin !== resolvedUrl.origin) {\n logger.once.warn(\n {\n requestOrigin: resolvedUrl.origin,\n paginationOrigin: nextUrl.origin,\n },\n 'Ignoring cross-origin GitHub pagination link. Set RENOVATE_X_REBASE_PAGINATION_LINKS if this is a self-hosted instance that returns a different origin in pagination links.',\n );\n break;\n }\n const nextPage: HttpResponse<T> =\n await super.requestJsonUnsafe<T>(method, {\n ...opts,\n url: nextUrl,\n });\n pages.push(nextPage);\n const nextLink = parseLinkHeader(nextPage.headers.link)?.next;\n nextUrl = nextLink?.url\n ? resolvePaginationUrl(\n nextLink.url,\n baseUrl,\n rebasePaginationLinks,\n )\n : null;\n }\n if (paginateAll && nextUrl && pageNumber > cursorPageLimit) {\n logger.warn(\n { maxPages: MAX_PAGINATION_PAGES },\n 'GitHub cursor pagination limit reached',\n );\n }\n }\n // v8 ignore else -- TODO: add test #40625\n if (httpOptions.paginationField && isPlainObject(result.body)) {\n const paginatedResult = result.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(paginatedResult)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isPlainObject(nextPage.body)) {\n const nextPageResults =\n nextPage.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPageResults)) {\n paginatedResult.push(...nextPageResults);\n }\n }\n }\n }\n } else if (isArray<T>(result.body)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPage.body)) {\n result.body.push(...nextPage.body);\n }\n }\n }\n } else {\n // make sure that users are aware if there are any (potentially malicious, or misconfigured) pagination links being returned\n logger.once.warn(\n {\n requestOrigin: resolvedUrl.origin,\n paginationOrigin: firstPageUrl.origin,\n },\n 'Ignoring cross-origin GitHub pagination link. Set RENOVATE_X_REBASE_PAGINATION_LINKS if this is a self-hosted instance that returns a different origin in pagination links.',\n );\n }\n }\n }\n return result;\n }\n\n public async requestGraphql<T = unknown>(\n query: string,\n options: GraphqlOptions = {},\n ): Promise<GithubGraphqlResponse<T> | null> {\n const path = 'graphql';\n\n const { paginate, count = MAX_GRAPHQL_PAGE_SIZE, cursor = null } = options;\n let { variables } = options;\n if (paginate) {\n variables = {\n ...variables,\n count,\n cursor,\n };\n }\n const body = variables ? { query, variables } : { query };\n\n const opts: GithubBaseHttpOptions = {\n baseUrl: baseUrl.replace('/v3/', '/'), // GHE uses unversioned graphql path\n body,\n headers: { accept: options?.acceptHeader },\n readOnly: options.readOnly,\n };\n if (options.token) {\n opts.token = options.token;\n }\n logger.trace(`Performing Github GraphQL request`);\n\n let response: GithubGraphqlResponse<T> | null;\n try {\n const res = await this.postJson<GithubGraphqlResponse<T>>(path, opts);\n response = res.body;\n } catch (err) {\n logger.debug({ err, query, options }, 'Unexpected GraphQL Error');\n if (err instanceof ExternalHostError && count && count > 10) {\n logger.info('Reducing pagination count to workaround graphql errors');\n return null;\n }\n throw handleGotError(err, path, opts);\n }\n\n if (isGraphqlRateLimited(response?.errors)) {\n throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n\n return response;\n }\n\n async queryRepoField<T = Record<string, unknown>>(\n query: string,\n fieldName: string,\n options: GraphqlOptions = {},\n ): Promise<T[]> {\n const result: T[] = [];\n\n const { paginate = true } = options;\n\n let optimalCount: null | number = null;\n let count = getGraphqlPageSize(\n fieldName,\n options.count ?? MAX_GRAPHQL_PAGE_SIZE,\n );\n let limit = options.limit ?? 1000;\n let cursor: string | null = null;\n\n let isIterating = true;\n while (isIterating) {\n const res = await this.requestGraphql<GithubGraphqlRepoData<T>>(query, {\n ...options,\n count: Math.min(count, limit),\n cursor,\n paginate,\n });\n const repositoryData = res?.data?.repository;\n if (\n isNonEmptyObject(repositoryData) &&\n !isNullOrUndefined(repositoryData[fieldName])\n ) {\n optimalCount = count;\n\n const {\n nodes = [],\n edges = [],\n pageInfo,\n } = repositoryData[fieldName] as GraphqlPaginatedContent<T>;\n result.push(...nodes);\n result.push(...edges);\n\n limit = Math.max(0, limit - nodes.length - edges.length);\n\n if (limit === 0) {\n isIterating = false;\n } else if (paginate && pageInfo) {\n const { hasNextPage, endCursor } = pageInfo;\n if (hasNextPage && endCursor) {\n cursor = endCursor;\n } else {\n isIterating = false;\n }\n }\n } else {\n count = Math.floor(count / 2);\n if (count === 0) {\n logger.warn({ query, options, res }, 'Error fetching GraphQL nodes');\n isIterating = false;\n }\n }\n\n if (!paginate) {\n isIterating = false;\n }\n }\n\n if (optimalCount && optimalCount < MAX_GRAPHQL_PAGE_SIZE) {\n setGraphqlPageSize(fieldName, optimalCount);\n }\n\n return result;\n }\n\n /**\n * Get the raw text file from a URL.\n * Only use this method to fetch text files.\n *\n * @param url Full API URL, contents path or path inside the repository to the file\n * @param options\n *\n * @example url = 'https://api.github.com/repos/renovatebot/renovate/contents/package.json'\n * @example url = 'renovatebot/renovate/contents/package.json'\n * @example url = 'package.json' & options.repository = 'renovatebot/renovate'\n */\n public async getRawTextFile(\n url: string,\n options: InternalHttpOptions & GithubBaseHttpOptions = {},\n ): Promise<HttpResponse> {\n const newOptions: InternalHttpOptions & GithubBaseHttpOptions = {\n ...options,\n headers: {\n accept: 'application/vnd.github.raw+json',\n },\n };\n\n let newURL = url;\n const httpRegex = regEx(/^https?:\\/\\//);\n if (options.repository && !httpRegex.test(options.repository)) {\n newURL = joinUrlParts(options.repository, 'contents', url);\n }\n\n return await this.getText(newURL, newOptions);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"github.js","names":["hostRules.find","p.all"],"sources":["../../../lib/util/http/github.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyObject,\n isNullOrUndefined,\n isPlainObject,\n isString,\n} from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport { z } from 'zod/v4';\nimport { GlobalConfig } from '../../config/global.ts';\nimport {\n PLATFORM_BAD_CREDENTIALS,\n PLATFORM_INTEGRATION_UNAUTHORIZED,\n PLATFORM_RATE_LIMIT_EXCEEDED,\n REPOSITORY_CHANGED,\n} from '../../constants/error-messages.ts';\nimport { logger } from '../../logger/index.ts';\nimport { ExternalHostError } from '../../types/errors/external-host-error.ts';\nimport { getCache } from '../cache/repository/index.ts';\nimport { getEnv } from '../env.ts';\nimport * as hostRules from '../host-rules.ts';\nimport { maskToken } from '../mask.ts';\nimport { coerceObject } from '../object.ts';\nimport * as p from '../promises.ts';\nimport { range } from '../range.ts';\nimport { regEx } from '../regex.ts';\nimport { LooseArray } from '../schema-utils/index.ts';\nimport { joinUrlParts, parseLinkHeader, parseUrl } from '../url.ts';\nimport { findMatchingRule } from './host-rules.ts';\nimport {\n HttpBase,\n type InternalHttpOptions,\n type InternalJsonUnsafeOptions,\n} from './http.ts';\nimport type { GotLegacyError } from './legacy.ts';\nimport {\n isForbidden,\n isGone,\n isServerError,\n isUnauthorized,\n isUnprocessableContent,\n} from './status.ts';\nimport type {\n GraphqlOptions,\n HttpMethod,\n HttpOptions,\n HttpResponse,\n} from './types.ts';\n\nconst githubBaseUrl = 'https://api.github.com/';\nconst MAX_PAGINATION_PAGES = 100;\nlet baseUrl = githubBaseUrl;\nexport function setBaseUrl(url: string): void {\n baseUrl = url;\n}\n\nexport interface GithubBaseHttpOptions extends HttpOptions {\n repository?: string;\n}\n\nexport interface GithubHttpOptions extends GithubBaseHttpOptions {\n paginate?: boolean | string;\n paginationField?: string;\n pageLimit?: number;\n}\n\ninterface GithubGraphqlRepoData<T = unknown> {\n repository?: T;\n}\n\nexport type GithubGraphqlResponse<T = unknown> =\n | {\n data: T;\n errors?: never;\n }\n | {\n data?: never;\n errors: {\n type?: string;\n code?: string;\n message: string;\n }[];\n };\n\n/**\n * GitHub reports a spent GraphQL budget in two shapes: `RATE_LIMITED` from the\n * GraphQL API itself, and `graphql_rate_limit` when an app installation has\n * exhausted its allowance. Both mean \"come back later\", not \"unknown error\".\n */\nfunction isGraphqlRateLimited(\n errors: { type?: string; code?: string }[] | undefined,\n): boolean {\n return !!errors?.some(\n (err) => err.type === 'RATE_LIMITED' || err.code === 'graphql_rate_limit',\n );\n}\n\nconst GithubError = z.object({\n field: z.string().optional(),\n code: z.string().optional(),\n message: z.string().optional(),\n});\n\n/**\n * GitHub usually returns `errors` as an array, but not always - normalize it\n * into an array of error objects, dropping anything that doesn't fit.\n */\nconst GithubErrors = z\n .union([LooseArray(GithubError), GithubError.transform((error) => [error])])\n .catch([]);\n\n/**\n * Whether `err` is GitHub's response to creating a PR for a branch which already has one, usually because a different user created it so Renovate could not find it.\n */\nexport function isPrAlreadyExistsError(err: any): boolean {\n return (\n isUnprocessableContent(err) &&\n GithubErrors.parse(err.response?.body?.errors).some((e) =>\n e.message?.startsWith('A pull request already exists'),\n )\n );\n}\n\nfunction handleGotError(\n err: GotLegacyError,\n url: string | URL,\n opts: GithubBaseHttpOptions,\n): Error {\n const path = url.toString();\n let message = err.message || '';\n const body = err.response?.body;\n if (isPlainObject(body) && 'message' in body) {\n message = String(body.message);\n }\n if (\n err.code === 'ERR_HTTP2_STREAM_ERROR' ||\n err.code === 'ENOTFOUND' ||\n err.code === 'ETIMEDOUT' ||\n err.code === 'EAI_AGAIN' ||\n err.code === 'ECONNRESET'\n ) {\n logger.debug({ err }, 'GitHub failure: RequestError');\n return new ExternalHostError(err, 'github');\n }\n if (err.name === 'ParseError') {\n logger.debug({ err }, '');\n return new ExternalHostError(err, 'github');\n }\n if (isServerError(err)) {\n logger.debug({ err }, 'GitHub failure: 5xx');\n return new ExternalHostError(err, 'github');\n }\n if (\n isForbidden(err) &&\n message.startsWith('You have triggered an abuse detection mechanism')\n ) {\n logger.debug({ err }, 'GitHub failure: abuse detection');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n isForbidden(err) &&\n message.startsWith('You have exceeded a secondary rate limit')\n ) {\n logger.warn({ err }, 'GitHub failure: secondary rate limit');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (isForbidden(err) && message.includes('Upgrade to GitHub Pro')) {\n logger.debug(`Endpoint: ${path}, needs paid GitHub plan`);\n return err;\n }\n if (isForbidden(err) && message.includes('rate limit exceeded')) {\n logger.debug({ err }, 'GitHub failure: rate limit');\n\n const parsed = parseUrl(baseUrl);\n\n const rule = hostRules.find({ url: baseUrl });\n if (rule.token || rule.password) {\n logger.once.warn(\n 'Rate limit exceeded for api.github.com, even though we are authenticated',\n );\n } else {\n if (parsed?.hostname === 'api.github.com') {\n logger.once.warn(\n {\n documentationUrl: `${GlobalConfig.get('productLinks').documentation}getting-started/running/#githubcom-token-for-changelogs-and-tools`,\n },\n 'Rate limit exceeded for api.github.com, as no hostRules set for this host. Please set a GITHUB_COM_TOKEN',\n );\n } else {\n logger.once.warn(\n { host: parsed!.host },\n 'Rate limit exceeded, as no hostRules set for this host',\n );\n }\n }\n\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n isForbidden(err) &&\n message.startsWith('Resource not accessible by integration')\n ) {\n logger.debug(\n { err },\n 'GitHub failure: Resource not accessible by integration',\n );\n return new Error(PLATFORM_INTEGRATION_UNAUTHORIZED);\n }\n if (isUnauthorized(err)) {\n // Warn once for github.com token if unauthorized\n const hostname = parseUrl(url)?.hostname;\n // v8 ignore else -- TODO: add test #40625\n if (hostname === 'github.com' || hostname === 'api.github.com') {\n logger.once.warn('github.com token 401 unauthorized');\n }\n if (message.includes('Bad credentials')) {\n const rateLimit = err.headers?.['x-ratelimit-limit'] ?? -1;\n logger.debug(\n {\n token: maskToken(opts.token),\n err,\n },\n 'GitHub failure: Bad credentials',\n );\n if (rateLimit === '60') {\n return new ExternalHostError(err, 'github');\n }\n return new Error(PLATFORM_BAD_CREDENTIALS);\n }\n }\n if (isUnprocessableContent(err)) {\n if (\n message.includes('Review cannot be requested from pull request author')\n ) {\n return err;\n }\n\n const errors = GithubErrors.parse(err.body?.errors);\n if (errors.some((e) => e.field === 'milestone')) {\n return err;\n }\n if (errors.some((e) => e.code === 'invalid')) {\n logger.debug({ err }, 'Received invalid response - aborting');\n return new Error(REPOSITORY_CHANGED);\n }\n if (isPrAlreadyExistsError(err)) {\n return err;\n }\n logger.debug({ err }, '422 Error thrown from GitHub');\n return new ExternalHostError(err, 'github');\n }\n if (\n isGone(err) &&\n err.body?.message === 'Issues are disabled for this repo'\n ) {\n return err;\n }\n return err;\n}\n\ninterface GraphqlPaginatedContent<T = unknown> {\n nodes?: T[];\n edges?: T[];\n pageInfo: { hasNextPage: boolean; endCursor: string };\n}\n\nfunction hasVendorAccept(acceptStrings: string[]): boolean {\n return (\n acceptStrings.some((x) => x.startsWith('application/vnd.github.')) &&\n acceptStrings.length >= 2\n );\n}\n\nfunction constructAcceptString(input?: unknown): string {\n const defaultAccept = 'application/vnd.github.v3+json';\n const acceptStrings = isString(input) ? input.split(regEx(/\\s*,\\s*/)) : [];\n\n // TODO: regression of #6736\n // v8 ignore else -- TODO: add test #40625\n if (\n !acceptStrings.some((x) => x === defaultAccept) &&\n !hasVendorAccept(acceptStrings)\n ) {\n acceptStrings.push(defaultAccept);\n }\n return acceptStrings.join(', ');\n}\n\nconst MAX_GRAPHQL_PAGE_SIZE = 100;\n\ninterface GraphqlPageCacheItem {\n pageLastResizedAt: string;\n pageSize: number;\n}\n\nexport type GraphqlPageCache = Record<string, GraphqlPageCacheItem>;\n\nfunction getGraphqlPageSize(\n fieldName: string,\n defaultPageSize = MAX_GRAPHQL_PAGE_SIZE,\n): number {\n const cache = getCache();\n const graphqlPageCache = cache?.platform?.github\n ?.graphqlPageCache as GraphqlPageCache;\n const cachedRecord = graphqlPageCache?.[fieldName];\n\n if (graphqlPageCache && cachedRecord) {\n logger.debug(\n { fieldName, ...cachedRecord },\n 'GraphQL page size: found cached value',\n );\n\n const oldPageSize = cachedRecord.pageSize;\n\n const now = DateTime.local();\n const then = DateTime.fromISO(cachedRecord.pageLastResizedAt);\n const expiry = then.plus({ hours: 24 });\n if (now > expiry) {\n const newPageSize = Math.min(oldPageSize * 2, MAX_GRAPHQL_PAGE_SIZE);\n if (newPageSize < MAX_GRAPHQL_PAGE_SIZE) {\n const timestamp = now.toISO();\n\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp },\n 'GraphQL page size: expanding',\n );\n\n cachedRecord.pageLastResizedAt = timestamp;\n cachedRecord.pageSize = newPageSize;\n } else {\n logger.debug(\n { fieldName, oldPageSize, newPageSize },\n 'GraphQL page size: expanded to default page size',\n );\n\n delete graphqlPageCache[fieldName];\n }\n\n return newPageSize;\n }\n\n return oldPageSize;\n }\n\n return defaultPageSize;\n}\n\nfunction setGraphqlPageSize(fieldName: string, newPageSize: number): void {\n const oldPageSize = getGraphqlPageSize(fieldName);\n if (newPageSize !== oldPageSize) {\n const now = DateTime.local();\n const pageLastResizedAt = now.toISO();\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp: pageLastResizedAt },\n 'GraphQL page size: shrinking',\n );\n const cache = getCache();\n cache.platform ??= {};\n cache.platform.github ??= {};\n cache.platform.github.graphqlPageCache ??= {};\n const graphqlPageCache = cache.platform.github\n .graphqlPageCache as GraphqlPageCache;\n graphqlPageCache[fieldName] = {\n pageLastResizedAt,\n pageSize: newPageSize,\n };\n }\n}\n\nfunction replaceUrlBase(url: URL, baseUrl: string): URL {\n const relativeUrl = `${url.pathname}${url.search}`;\n return new URL(relativeUrl, baseUrl);\n}\n\nfunction resolvePaginationUrl(\n url: string,\n baseUrl: string | undefined,\n rebasePaginationLinks: boolean,\n): URL {\n const parsedUrl = new URL(url, baseUrl);\n const rebasePagination =\n !!baseUrl &&\n rebasePaginationLinks &&\n // Preserve github.com URLs for use cases like release notes\n parsedUrl.origin !== 'https://api.github.com';\n return rebasePagination ? replaceUrlBase(parsedUrl, baseUrl) : parsedUrl;\n}\n\nexport class GithubHttp extends HttpBase<GithubHttpOptions> {\n protected override get baseUrl(): string | undefined {\n return baseUrl;\n }\n\n constructor(hostType = 'github', options?: HttpOptions) {\n super(hostType, options);\n }\n\n protected override extraOptions(): readonly string[] {\n return super\n .extraOptions()\n .concat([\n 'pageLimit',\n 'paginate',\n 'paginationField',\n 'repository',\n ] as (keyof GithubHttpOptions)[]);\n }\n\n protected override processOptions(\n url: URL,\n opts: InternalHttpOptions & GithubBaseHttpOptions,\n ): void {\n if (!opts.token) {\n // create a mutable copy of `url`\n const authUrl = parseUrl(url.toString())!;\n\n if (opts.repository) {\n // set authUrl to https://api.github.com/repos/org/repo or https://gihub.domain.com/api/v3/repos/org/repo\n authUrl.hash = '';\n authUrl.search = '';\n authUrl.pathname = joinUrlParts(\n authUrl.pathname.startsWith('/api/v3') ? '/api/v3' : '',\n 'repos',\n `${opts.repository}`,\n );\n }\n\n let readOnly = opts.readOnly;\n const { method = 'get' } = opts;\n if (\n readOnly === undefined &&\n ['get', 'head'].includes(method.toLowerCase())\n ) {\n readOnly = true;\n }\n const { token } = findMatchingRule(authUrl.toString(), {\n hostType: this.hostType,\n readOnly,\n });\n opts.token = token;\n }\n\n const accept = constructAcceptString(opts.headers?.accept);\n\n opts.headers = {\n ...opts.headers,\n accept,\n };\n }\n\n protected override handleError(\n url: string | URL,\n opts: HttpOptions,\n err: GotLegacyError,\n ): never {\n throw handleGotError(err, url, opts);\n }\n\n protected override async requestJsonUnsafe<T>(\n method: HttpMethod,\n options: InternalJsonUnsafeOptions<GithubHttpOptions>,\n ): Promise<HttpResponse<T>> {\n const httpOptions = coerceObject(options.httpOptions);\n const resolvedUrl = this.resolveUrl(options.url, httpOptions);\n const opts = {\n ...options,\n url: resolvedUrl,\n };\n\n const result = await super.requestJsonUnsafe<T>(method, opts);\n if (httpOptions.paginate) {\n delete httpOptions.cacheProvider;\n httpOptions.memCache = false;\n // Check if result is paginated\n const pageLimit = httpOptions.pageLimit ?? 10;\n const linkHeader = parseLinkHeader(result?.headers?.link);\n const next = linkHeader?.next;\n const env = getEnv();\n if (next?.url) {\n const baseUrl = httpOptions.baseUrl ?? this.baseUrl;\n const rebasePaginationLinks = !!env.RENOVATE_X_REBASE_PAGINATION_LINKS;\n const firstPageUrl = resolvePaginationUrl(\n next.url,\n baseUrl,\n rebasePaginationLinks,\n );\n // Don't follow a cross-origin request, unless we've been explicitly requested to do so with `RENOVATE_X_REBASE_PAGINATION_LINKS`\n if (firstPageUrl.origin === resolvedUrl.origin) {\n let pages: HttpResponse<T>[];\n if (linkHeader?.last?.page) {\n logger.debug('Using GitHub offset-based pagination');\n let lastPage = parseInt(linkHeader.last.page, 10);\n // v8 ignore else -- TODO: add test #40625\n if (!env.RENOVATE_PAGINATE_ALL && httpOptions.paginate !== 'all') {\n lastPage = Math.min(pageLimit, lastPage);\n }\n const queue = [...range(2, lastPage)].map(\n (pageNumber) => (): Promise<HttpResponse<T>> => {\n // copy before modifying searchParams\n const nextUrl = parseUrl(firstPageUrl.toString())!;\n nextUrl.searchParams.set('page', String(pageNumber));\n return super.requestJsonUnsafe<T>(method, {\n ...opts,\n url: nextUrl,\n });\n },\n );\n pages = await p.all(queue);\n } else {\n logger.debug('Using GitHub cursor-based pagination');\n pages = [];\n const paginateAll =\n !!env.RENOVATE_PAGINATE_ALL || httpOptions.paginate === 'all';\n const cursorPageLimit = paginateAll\n ? MAX_PAGINATION_PAGES\n : pageLimit;\n let nextUrl: URL | null = firstPageUrl;\n let pageNumber = 2;\n for (; nextUrl && pageNumber <= cursorPageLimit; pageNumber += 1) {\n if (nextUrl.origin !== resolvedUrl.origin) {\n logger.once.warn(\n {\n requestOrigin: resolvedUrl.origin,\n paginationOrigin: nextUrl.origin,\n },\n 'Ignoring cross-origin GitHub pagination link. Set RENOVATE_X_REBASE_PAGINATION_LINKS if this is a self-hosted instance that returns a different origin in pagination links.',\n );\n break;\n }\n const nextPage: HttpResponse<T> =\n await super.requestJsonUnsafe<T>(method, {\n ...opts,\n url: nextUrl,\n });\n pages.push(nextPage);\n const nextLink = parseLinkHeader(nextPage.headers.link)?.next;\n nextUrl = nextLink?.url\n ? resolvePaginationUrl(\n nextLink.url,\n baseUrl,\n rebasePaginationLinks,\n )\n : null;\n }\n if (paginateAll && nextUrl && pageNumber > cursorPageLimit) {\n logger.warn(\n { maxPages: MAX_PAGINATION_PAGES },\n 'GitHub cursor pagination limit reached',\n );\n }\n }\n // v8 ignore else -- TODO: add test #40625\n if (httpOptions.paginationField && isPlainObject(result.body)) {\n const paginatedResult = result.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(paginatedResult)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isPlainObject(nextPage.body)) {\n const nextPageResults =\n nextPage.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPageResults)) {\n paginatedResult.push(...nextPageResults);\n }\n }\n }\n }\n } else if (isArray<T>(result.body)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPage.body)) {\n result.body.push(...nextPage.body);\n }\n }\n }\n } else {\n // make sure that users are aware if there are any (potentially malicious, or misconfigured) pagination links being returned\n logger.once.warn(\n {\n requestOrigin: resolvedUrl.origin,\n paginationOrigin: firstPageUrl.origin,\n },\n 'Ignoring cross-origin GitHub pagination link. Set RENOVATE_X_REBASE_PAGINATION_LINKS if this is a self-hosted instance that returns a different origin in pagination links.',\n );\n }\n }\n }\n return result;\n }\n\n public async requestGraphql<T = unknown>(\n query: string,\n options: GraphqlOptions = {},\n ): Promise<GithubGraphqlResponse<T> | null> {\n const path = 'graphql';\n\n const { paginate, count = MAX_GRAPHQL_PAGE_SIZE, cursor = null } = options;\n let { variables } = options;\n if (paginate) {\n variables = {\n ...variables,\n count,\n cursor,\n };\n }\n const body = variables ? { query, variables } : { query };\n\n const opts: GithubBaseHttpOptions = {\n baseUrl: baseUrl.replace('/v3/', '/'), // GHE uses unversioned graphql path\n body,\n headers: { accept: options?.acceptHeader },\n readOnly: options.readOnly,\n };\n if (options.token) {\n opts.token = options.token;\n }\n logger.trace(`Performing Github GraphQL request`);\n\n let response: GithubGraphqlResponse<T> | null;\n try {\n const res = await this.postJson<GithubGraphqlResponse<T>>(path, opts);\n response = res.body;\n } catch (err) {\n logger.debug({ err, query, options }, 'Unexpected GraphQL Error');\n if (err instanceof ExternalHostError && count && count > 10) {\n logger.info('Reducing pagination count to workaround graphql errors');\n return null;\n }\n throw handleGotError(err, path, opts);\n }\n\n if (isGraphqlRateLimited(response?.errors)) {\n throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n\n return response;\n }\n\n async queryRepoField<T = Record<string, unknown>>(\n query: string,\n fieldName: string,\n options: GraphqlOptions = {},\n ): Promise<T[]> {\n const result: T[] = [];\n\n const { paginate = true } = options;\n\n let optimalCount: null | number = null;\n let count = getGraphqlPageSize(\n fieldName,\n options.count ?? MAX_GRAPHQL_PAGE_SIZE,\n );\n let limit = options.limit ?? 1000;\n let cursor: string | null = null;\n\n let isIterating = true;\n while (isIterating) {\n const res = await this.requestGraphql<GithubGraphqlRepoData<T>>(query, {\n ...options,\n count: Math.min(count, limit),\n cursor,\n paginate,\n });\n const repositoryData = res?.data?.repository;\n if (\n isNonEmptyObject(repositoryData) &&\n !isNullOrUndefined(repositoryData[fieldName])\n ) {\n optimalCount = count;\n\n const {\n nodes = [],\n edges = [],\n pageInfo,\n } = repositoryData[fieldName] as GraphqlPaginatedContent<T>;\n result.push(...nodes);\n result.push(...edges);\n\n limit = Math.max(0, limit - nodes.length - edges.length);\n\n if (limit === 0) {\n isIterating = false;\n } else if (paginate && pageInfo) {\n const { hasNextPage, endCursor } = pageInfo;\n if (hasNextPage && endCursor) {\n cursor = endCursor;\n } else {\n isIterating = false;\n }\n }\n } else {\n count = Math.floor(count / 2);\n if (count === 0) {\n logger.warn({ query, options, res }, 'Error fetching GraphQL nodes');\n isIterating = false;\n }\n }\n\n if (!paginate) {\n isIterating = false;\n }\n }\n\n if (optimalCount && optimalCount < MAX_GRAPHQL_PAGE_SIZE) {\n setGraphqlPageSize(fieldName, optimalCount);\n }\n\n return result;\n }\n\n /**\n * Get the raw text file from a URL.\n * Only use this method to fetch text files.\n *\n * @param url Full API URL, contents path or path inside the repository to the file\n * @param options\n *\n * @example url = 'https://api.github.com/repos/renovatebot/renovate/contents/package.json'\n * @example url = 'renovatebot/renovate/contents/package.json'\n * @example url = 'package.json' & options.repository = 'renovatebot/renovate'\n */\n public async getRawTextFile(\n url: string,\n options: InternalHttpOptions & GithubBaseHttpOptions = {},\n ): Promise<HttpResponse> {\n const newOptions: InternalHttpOptions & GithubBaseHttpOptions = {\n ...options,\n headers: {\n accept: 'application/vnd.github.raw+json',\n },\n };\n\n let newURL = url;\n const httpRegex = regEx(/^https?:\\/\\//);\n if (options.repository && !httpRegex.test(options.repository)) {\n newURL = joinUrlParts(options.repository, 'contents', url);\n }\n\n return await this.getText(newURL, newOptions);\n }\n}\n"],"mappings":"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"}