renovate 44.100.0 → 44.101.1
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.
- package/dist/modules/datasource/pypi/common.js +2 -1
- package/dist/modules/datasource/pypi/common.js.map +1 -1
- package/dist/modules/datasource/pypi/host-rules.js +43 -0
- package/dist/modules/datasource/pypi/host-rules.js.map +1 -0
- package/dist/modules/datasource/pypi/index.js +18 -19
- package/dist/modules/datasource/pypi/index.js.map +1 -1
- package/dist/modules/datasource/util.js +7 -1
- package/dist/modules/datasource/util.js.map +1 -1
- package/dist/modules/manager/bun/artifacts.js +25 -22
- package/dist/modules/manager/bun/artifacts.js.map +1 -1
- package/dist/modules/manager/deno/artifacts.js +38 -35
- package/dist/modules/manager/deno/artifacts.js.map +1 -1
- package/dist/modules/manager/devbox/artifacts.js +6 -6
- package/dist/modules/manager/devbox/artifacts.js.map +1 -1
- package/dist/modules/manager/fingerprint.generated.js +7 -7
- package/dist/modules/manager/fingerprint.generated.js.map +1 -1
- package/dist/modules/manager/helmv3/common.js +1 -1
- package/dist/modules/manager/helmv3/common.js.map +1 -1
- package/dist/modules/manager/npm/artifacts.js +20 -22
- package/dist/modules/manager/npm/artifacts.js.map +1 -1
- package/dist/modules/manager/npm/post-update/index.js +113 -116
- package/dist/modules/manager/npm/post-update/index.js.map +1 -1
- package/dist/modules/manager/npm/utils.js +15 -1
- package/dist/modules/manager/npm/utils.js.map +1 -1
- package/dist/modules/manager/pep621/processors/uv.js +3 -20
- package/dist/modules/manager/pep621/processors/uv.js.map +1 -1
- package/dist/modules/manager/pip-compile/artifacts.js +4 -3
- package/dist/modules/manager/pip-compile/artifacts.js.map +1 -1
- package/dist/modules/manager/pip-compile/common.js +10 -17
- package/dist/modules/manager/pip-compile/common.js.map +1 -1
- package/dist/modules/manager/pipenv/artifacts.js +9 -26
- package/dist/modules/manager/pipenv/artifacts.js.map +1 -1
- package/dist/modules/manager/poetry/artifacts.js +3 -26
- package/dist/modules/manager/poetry/artifacts.js.map +1 -1
- package/dist/modules/manager/swift/artifacts.js +12 -0
- package/dist/modules/manager/swift/artifacts.js.map +1 -1
- package/dist/modules/manager/types.d.ts +6 -2
- package/dist/modules/manager/util.js +9 -2
- package/dist/modules/manager/util.js.map +1 -1
- package/dist/util/git/index.d.ts +7 -1
- package/dist/util/git/index.js +9 -1
- package/dist/util/git/index.js.map +1 -1
- package/dist/util/host-rules.js +2 -2
- package/dist/util/host-rules.js.map +1 -1
- package/dist/util/url.d.ts +10 -1
- package/dist/util/url.js +10 -1
- package/dist/util/url.js.map +1 -1
- package/dist/workers/repository/process/lookup/bucket.js +23 -4
- package/dist/workers/repository/process/lookup/bucket.js.map +1 -1
- package/dist/workers/repository/process/lookup/current.js +28 -1
- package/dist/workers/repository/process/lookup/current.js.map +1 -1
- package/dist/workers/repository/process/lookup/digest.js +99 -0
- package/dist/workers/repository/process/lookup/digest.js.map +1 -0
- package/dist/workers/repository/process/lookup/filter-checks.js +28 -7
- package/dist/workers/repository/process/lookup/filter-checks.js.map +1 -1
- package/dist/workers/repository/process/lookup/generate.js +2 -2
- package/dist/workers/repository/process/lookup/generate.js.map +1 -1
- package/dist/workers/repository/process/lookup/index.js +24 -161
- package/dist/workers/repository/process/lookup/index.js.map +1 -1
- package/dist/workers/repository/process/lookup/range-strategy.js +18 -0
- package/dist/workers/repository/process/lookup/range-strategy.js.map +1 -0
- package/dist/workers/repository/process/lookup/update-type.js +14 -5
- package/dist/workers/repository/process/lookup/update-type.js.map +1 -1
- package/dist/workers/repository/process/lookup/utils.js +13 -2
- package/dist/workers/repository/process/lookup/utils.js.map +1 -1
- package/dist/workers/repository/process/lookup/version-compatibility.js +51 -0
- package/dist/workers/repository/process/lookup/version-compatibility.js.map +1 -0
- package/dist/workers/repository/process/lookup/vulnerability.js +53 -0
- package/dist/workers/repository/process/lookup/vulnerability.js.map +1 -0
- package/package.json +2 -2
- package/renovate-schema.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { regEx } from "../../../util/regex.js";
|
|
2
2
|
//#region lib/modules/datasource/pypi/common.ts
|
|
3
|
+
const pypiDatasourceId = "pypi";
|
|
3
4
|
const githubRepoPattern = regEx(/^https?:\/\/github\.com\/(?<owner>[^/]+)\/[^/]+$/);
|
|
4
5
|
function isGitHubRepo(url) {
|
|
5
6
|
const m = url.match(githubRepoPattern);
|
|
@@ -9,6 +10,6 @@ function normalizePythonDepName(name) {
|
|
|
9
10
|
return name.replace(regEx(/[-_.]+/g), "-").toLowerCase();
|
|
10
11
|
}
|
|
11
12
|
//#endregion
|
|
12
|
-
export { isGitHubRepo, normalizePythonDepName };
|
|
13
|
+
export { isGitHubRepo, normalizePythonDepName, pypiDatasourceId };
|
|
13
14
|
|
|
14
15
|
//# sourceMappingURL=common.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","names":[],"sources":["../../../../lib/modules/datasource/pypi/common.ts"],"sourcesContent":["import { regEx } from '../../../util/regex.ts';\n\nconst githubRepoPattern = regEx(\n /^https?:\\/\\/github\\.com\\/(?<owner>[^/]+)\\/[^/]+$/,\n);\n\nexport function isGitHubRepo(url: string): boolean {\n const m = url.match(githubRepoPattern);\n return !!m && m.groups!.owner !== 'sponsors';\n}\n\n// https://packaging.python.org/en/latest/specifications/name-normalization/\nexport function normalizePythonDepName(name: string): string {\n return name.replace(regEx(/[-_.]+/g), '-').toLowerCase();\n}\n"],"mappings":";;AAEA,MAAM,oBAAoB,MACxB,kDACF;AAEA,SAAgB,aAAa,KAAsB;CACjD,MAAM,IAAI,IAAI,MAAM,iBAAiB;CACrC,OAAO,CAAC,CAAC,KAAK,EAAE,OAAQ,UAAU;AACpC;AAGA,SAAgB,uBAAuB,MAAsB;CAC3D,OAAO,KAAK,QAAQ,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,YAAY;AACzD"}
|
|
1
|
+
{"version":3,"file":"common.js","names":[],"sources":["../../../../lib/modules/datasource/pypi/common.ts"],"sourcesContent":["import { regEx } from '../../../util/regex.ts';\n\nexport const pypiDatasourceId = 'pypi';\n\nconst githubRepoPattern = regEx(\n /^https?:\\/\\/github\\.com\\/(?<owner>[^/]+)\\/[^/]+$/,\n);\n\nexport function isGitHubRepo(url: string): boolean {\n const m = url.match(githubRepoPattern);\n return !!m && m.groups!.owner !== 'sponsors';\n}\n\n// https://packaging.python.org/en/latest/specifications/name-normalization/\nexport function normalizePythonDepName(name: string): string {\n return name.replace(regEx(/[-_.]+/g), '-').toLowerCase();\n}\n"],"mappings":";;AAEA,MAAa,mBAAmB;AAEhC,MAAM,oBAAoB,MACxB,kDACF;AAEA,SAAgB,aAAa,KAAsB;CACjD,MAAM,IAAI,IAAI,MAAM,iBAAiB;CACrC,OAAO,CAAC,CAAC,KAAK,EAAE,OAAQ,UAAU;AACpC;AAGA,SAAgB,uBAAuB,MAAsB;CAC3D,OAAO,KAAK,QAAQ,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,YAAY;AACzD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { logger } from "../../../logger/index.js";
|
|
2
|
+
import { parseUrl } from "../../../util/url.js";
|
|
3
|
+
import { find } from "../../../util/host-rules.js";
|
|
4
|
+
import { getGoogleAuthHostRule, isGoogleArtifactRegistry } from "../util.js";
|
|
5
|
+
import { pypiDatasourceId } from "./common.js";
|
|
6
|
+
//#region lib/modules/datasource/pypi/host-rules.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the credentials for a Python package index URL.
|
|
9
|
+
*
|
|
10
|
+
* The URL's own `user:password@` part - which is often a placeholder such as
|
|
11
|
+
* `${USER}:${PASS}@` - does not stop it matching a `matchHost` without
|
|
12
|
+
* credentials, as `find()` ignores userinfo when matching. Google Artifact
|
|
13
|
+
* Registry is only asked for a token when no host rule supplies credentials,
|
|
14
|
+
* so an explicitly configured username/password always wins.
|
|
15
|
+
*/
|
|
16
|
+
async function findPypiIndexCredentials(indexUrl) {
|
|
17
|
+
const parsedUrl = parseUrl(indexUrl);
|
|
18
|
+
if (!parsedUrl) {
|
|
19
|
+
logger.once.debug(`Failed to parse index URL ${indexUrl}`);
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
const { username, password } = find({
|
|
23
|
+
hostType: pypiDatasourceId,
|
|
24
|
+
url: parsedUrl.toString()
|
|
25
|
+
});
|
|
26
|
+
if (username ?? password) return {
|
|
27
|
+
username,
|
|
28
|
+
password
|
|
29
|
+
};
|
|
30
|
+
if (isGoogleArtifactRegistry(parsedUrl.hostname)) {
|
|
31
|
+
const googleHostRule = await getGoogleAuthHostRule();
|
|
32
|
+
if (googleHostRule) return {
|
|
33
|
+
username: googleHostRule.username,
|
|
34
|
+
password: googleHostRule.password
|
|
35
|
+
};
|
|
36
|
+
logger.once.debug(`Could not get Google access token (url=${parsedUrl.toString()})`);
|
|
37
|
+
}
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { findPypiIndexCredentials };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=host-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-rules.js","names":[],"sources":["../../../../lib/modules/datasource/pypi/host-rules.ts"],"sourcesContent":["import { logger } from '../../../logger/index.ts';\nimport { find } from '../../../util/host-rules.ts';\nimport { parseUrl } from '../../../util/url.ts';\nimport { getGoogleAuthHostRule, isGoogleArtifactRegistry } from '../util.ts';\nimport { pypiDatasourceId } from './common.ts';\nimport type { PypiIndexCredentials } from './types.ts';\n\n/**\n * Resolves the credentials for a Python package index URL.\n *\n * The URL's own `user:password@` part - which is often a placeholder such as\n * `${USER}:${PASS}@` - does not stop it matching a `matchHost` without\n * credentials, as `find()` ignores userinfo when matching. Google Artifact\n * Registry is only asked for a token when no host rule supplies credentials,\n * so an explicitly configured username/password always wins.\n */\nexport async function findPypiIndexCredentials(\n indexUrl: string | undefined,\n): Promise<PypiIndexCredentials> {\n const parsedUrl = parseUrl(indexUrl);\n if (!parsedUrl) {\n logger.once.debug(`Failed to parse index URL ${indexUrl}`);\n return {};\n }\n\n const { username, password } = find({\n hostType: pypiDatasourceId,\n url: parsedUrl.toString(),\n });\n if (username ?? password) {\n return { username, password };\n }\n\n if (isGoogleArtifactRegistry(parsedUrl.hostname)) {\n const googleHostRule = await getGoogleAuthHostRule();\n if (googleHostRule) {\n return {\n username: googleHostRule.username,\n password: googleHostRule.password,\n };\n }\n logger.once.debug(\n `Could not get Google access token (url=${parsedUrl.toString()})`,\n );\n }\n\n return {};\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,eAAsB,yBACpB,UAC+B;CAC/B,MAAM,YAAY,SAAS,QAAQ;CACnC,IAAI,CAAC,WAAW;EACd,OAAO,KAAK,MAAM,6BAA6B,UAAU;EACzD,OAAO,CAAC;CACV;CAEA,MAAM,EAAE,UAAU,aAAa,KAAK;EAClC,UAAU;EACV,KAAK,UAAU,SAAS;CAC1B,CAAC;CACD,IAAI,YAAY,UACd,OAAO;EAAE;EAAU;CAAS;CAG9B,IAAI,yBAAyB,UAAU,QAAQ,GAAG;EAChD,MAAM,iBAAiB,MAAM,sBAAsB;EACnD,IAAI,gBACF,OAAO;GACL,UAAU,eAAe;GACzB,UAAU,eAAe;EAC3B;EAEF,OAAO,KAAK,MACV,0CAA0C,UAAU,SAAS,EAAE,EACjE;CACF;CAEA,OAAO,CAAC;AACV"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getEnv } from "../../../util/env.js";
|
|
2
2
|
import { regEx } from "../../../util/regex.js";
|
|
3
3
|
import { coerceArray, deduplicateArray } from "../../../util/array.js";
|
|
4
|
+
import { toBase64 } from "../../../util/string.js";
|
|
5
|
+
import { addSecretForSanitizing } from "../../../util/sanitize.js";
|
|
4
6
|
import { logger } from "../../../logger/index.js";
|
|
5
7
|
import { ensureTrailingSlash, parseUrl } from "../../../util/url.js";
|
|
6
8
|
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
|
|
@@ -11,14 +13,15 @@ import "../../../util/http/index.js";
|
|
|
11
13
|
import { asTimestamp } from "../../../util/timestamp.js";
|
|
12
14
|
import { Datasource } from "../datasource.js";
|
|
13
15
|
import { parse } from "../../../util/html.js";
|
|
14
|
-
import {
|
|
15
|
-
import { isGitHubRepo, normalizePythonDepName } from "./common.js";
|
|
16
|
+
import { isGoogleArtifactRegistry } from "../util.js";
|
|
17
|
+
import { isGitHubRepo, normalizePythonDepName, pypiDatasourceId } from "./common.js";
|
|
18
|
+
import { findPypiIndexCredentials } from "./host-rules.js";
|
|
16
19
|
import { PypiResponse, PypiSimpleResponse } from "./schema.js";
|
|
17
20
|
import { isNonEmptyString } from "@sindresorhus/is";
|
|
18
21
|
import changelogFilenameRegex from "changelog-filename-regex";
|
|
19
22
|
//#region lib/modules/datasource/pypi/index.ts
|
|
20
23
|
var PypiDatasource = class PypiDatasource extends Datasource {
|
|
21
|
-
static id =
|
|
24
|
+
static id = pypiDatasourceId;
|
|
22
25
|
constructor() {
|
|
23
26
|
super(PypiDatasource.id);
|
|
24
27
|
}
|
|
@@ -76,25 +79,21 @@ var PypiDatasource = class PypiDatasource extends Datasource {
|
|
|
76
79
|
lookupUrl
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
|
-
if (parsedUrl.hostname
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
lookupUrl: sanitizedLookupUrl
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
logger.once.debug({ lookupUrl }, "Could not get Google access token");
|
|
89
|
-
return {
|
|
90
|
-
headers: {},
|
|
91
|
-
lookupUrl
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
82
|
+
if (!isGoogleArtifactRegistry(parsedUrl.hostname)) return {
|
|
83
|
+
headers: {},
|
|
84
|
+
lookupUrl
|
|
85
|
+
};
|
|
86
|
+
const { username, password } = await findPypiIndexCredentials(lookupUrl);
|
|
87
|
+
if (!username && !password) return {
|
|
95
88
|
headers: {},
|
|
96
89
|
lookupUrl
|
|
97
90
|
};
|
|
91
|
+
const auth = toBase64(`${username ?? ""}:${password ?? ""}`);
|
|
92
|
+
addSecretForSanitizing(auth);
|
|
93
|
+
return {
|
|
94
|
+
headers: { authorization: `Basic ${auth}` },
|
|
95
|
+
lookupUrl: this.sanitizeLookupUrl(lookupUrl, parsedUrl)
|
|
96
|
+
};
|
|
98
97
|
}
|
|
99
98
|
async getDependency(packageName, hostUrl) {
|
|
100
99
|
const lookupUrl = new URL(`${normalizePythonDepName(packageName)}/json`, hostUrl).href;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["pep440.id","HttpError"],"sources":["../../../../lib/modules/datasource/pypi/index.ts"],"sourcesContent":["import { isNonEmptyString } from '@sindresorhus/is';\nimport changelogFilenameRegex from 'changelog-filename-regex';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { coerceArray, deduplicateArray } from '../../../util/array.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport { parse } from '../../../util/html.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport type {\n HttpResponse,\n OutgoingHttpHeaders,\n} from '../../../util/http/types.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { Json } from '../../../util/schema-utils/index.ts';\nimport type { Timestamp } from '../../../util/timestamp.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport { ensureTrailingSlash, parseUrl } from '../../../util/url.ts';\nimport * as pep440 from '../../versioning/pep440/index.ts';\nimport { Datasource } from '../datasource.ts';\nimport type { GetReleasesConfig, Release, ReleaseResult } from '../types.ts';\nimport { getGoogleAuthToken } from '../util.ts';\nimport { isGitHubRepo, normalizePythonDepName } from './common.ts';\nimport type { PypiRelease } from './schema.ts';\nimport { PypiResponse, PypiSimpleResponse } from './schema.ts';\nimport type { Releases } from './types.ts';\n\nexport class PypiDatasource extends Datasource {\n static readonly id = 'pypi';\n\n constructor() {\n super(PypiDatasource.id);\n }\n\n override readonly caching = true;\n\n override readonly customRegistrySupport = true;\n\n static readonly defaultURL =\n getEnv().PIP_INDEX_URL ?? 'https://pypi.org/pypi/';\n override readonly defaultRegistryUrls = [PypiDatasource.defaultURL];\n\n override readonly defaultVersioning = pep440.id;\n\n override readonly registryStrategy = 'merge';\n\n override readonly releaseTimestampSupport = true;\n override readonly releaseTimestampNote =\n 'The release timestamp is determined from the earliest `upload_time` field of the files of a version. When using the Simple API, timestamps are available if the server supports the JSON-based Simple API (PEP 691).';\n override readonly sourceUrlSupport = 'release';\n override readonly sourceUrlNote =\n 'The source URL is determined from the `homepage` field if it is a github repository, else we use the `project_urls` field.';\n\n async getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n let dependency: ReleaseResult | null = null;\n // TODO: null check (#22198)\n const hostUrl = ensureTrailingSlash(\n registryUrl!.replace('https://pypi.org/simple', 'https://pypi.org/pypi'),\n );\n const normalizedLookupName = normalizePythonDepName(packageName);\n\n // not all simple indexes use this identifier, but most do\n if (hostUrl.endsWith('/simple/') || hostUrl.endsWith('/+simple/')) {\n logger.trace(\n { packageName, hostUrl },\n 'Looking up pypi simple dependency',\n );\n dependency = await this.getSimpleDependency(\n normalizedLookupName,\n hostUrl,\n );\n } else {\n logger.trace({ packageName, hostUrl }, 'Looking up pypi api dependency');\n try {\n // we need to resolve early here so we can catch any 404s and fallback to a simple lookup\n dependency = await this.getDependency(normalizedLookupName, hostUrl);\n } catch (err) {\n // error contacting json-style api -- attempt to fallback to a simple-style api\n logger.trace(\n { packageName, hostUrl, err },\n 'Looking up pypi simple dependency via fallback',\n );\n dependency = await this.getSimpleDependency(\n normalizedLookupName,\n hostUrl,\n );\n }\n }\n return dependency;\n }\n\n private sanitizeLookupUrl(lookupUrl: string, parsedUrl: URL): string {\n if (!parsedUrl.username && !parsedUrl.password) {\n return lookupUrl;\n }\n\n parsedUrl.username = '';\n parsedUrl.password = '';\n return parsedUrl.toString();\n }\n\n private async getAuthHeaders(\n lookupUrl: string,\n ): Promise<{ headers: OutgoingHttpHeaders; lookupUrl: string }> {\n const parsedUrl = parseUrl(lookupUrl);\n // v8 ignore if -- TODO: refactor to cover this branch through public behavior again\n if (!parsedUrl) {\n logger.once.debug({ lookupUrl }, 'Failed to parse URL');\n return { headers: {}, lookupUrl };\n }\n if (parsedUrl.hostname.endsWith('.pkg.dev')) {\n const auth = await getGoogleAuthToken();\n if (auth) {\n const sanitizedLookupUrl = this.sanitizeLookupUrl(lookupUrl, parsedUrl);\n return {\n headers: { authorization: `Basic ${auth}` },\n lookupUrl: sanitizedLookupUrl,\n };\n }\n logger.once.debug({ lookupUrl }, 'Could not get Google access token');\n return { headers: {}, lookupUrl };\n }\n return { headers: {}, lookupUrl };\n }\n\n private async getDependency(\n packageName: string,\n hostUrl: string,\n ): Promise<ReleaseResult | null> {\n const lookupUrl = new URL(\n `${normalizePythonDepName(packageName)}/json`,\n hostUrl,\n ).href;\n const dependency: ReleaseResult = { releases: [] };\n logger.trace({ lookupUrl }, 'Pypi api got lookup');\n const { headers, lookupUrl: sanitizedUrl } =\n await this.getAuthHeaders(lookupUrl);\n const rep = await this.http.getJson(\n sanitizedUrl,\n { headers },\n PypiResponse,\n );\n const dep = rep?.body;\n if (rep.authorization) {\n dependency.isPrivate = true;\n }\n logger.trace({ lookupUrl }, 'Got pypi api result');\n\n if (dep.info?.home_page) {\n dependency.homepage = dep.info.home_page;\n if (isGitHubRepo(dep.info.home_page)) {\n dependency.sourceUrl = dep.info.home_page.replace(\n 'http://',\n 'https://',\n );\n }\n }\n\n if (dep.info?.project_urls) {\n for (const [name, projectUrl] of Object.entries(dep.info.project_urls)) {\n const lower = name.toLowerCase();\n\n if (\n projectUrl &&\n !dependency.sourceUrl &&\n (lower.startsWith('repo') ||\n lower === 'code' ||\n lower === 'source' ||\n isGitHubRepo(projectUrl))\n ) {\n dependency.sourceUrl = projectUrl;\n }\n\n if (\n !dependency.changelogUrl &&\n ([\n 'changelog',\n 'change log',\n 'changes',\n 'release notes',\n 'news',\n \"what's new\",\n ].includes(lower) ||\n changelogFilenameRegex.exec(lower))\n ) {\n // from https://github.com/pypa/warehouse/blob/418c7511dc367fb410c71be139545d0134ccb0df/warehouse/templates/packaging/detail.html#L24\n dependency.changelogUrl = projectUrl;\n }\n }\n }\n\n if (dep.releases) {\n dependency.releases = PypiDatasource.toReleases(dep.releases);\n }\n return dependency;\n }\n\n private static getEarliestTimestamp(\n releases: PypiRelease[],\n ): Timestamp | null {\n let earliest: Timestamp | null = null;\n for (const { upload_time } of releases) {\n const timestamp = asTimestamp(upload_time);\n // `asTimestamp` normalizes to UTC ISO 8601, so comparing the strings compares the instants\n if (timestamp && (!earliest || timestamp < earliest)) {\n earliest = timestamp;\n }\n }\n return earliest;\n }\n\n private static extractVersionFromLinkText(\n text: string,\n packageName: string,\n ): string | null {\n // source packages\n const lcText = text.toLowerCase();\n const normalizedSrcText = normalizePythonDepName(text);\n const srcPrefix = `${packageName}-`;\n\n // source distribution format: `{name}-{version}.tar.gz` (https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-file-name)\n // binary distribution: `{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl` (https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-name-convention)\n // officially both `name` and `distribution` should be normalized and then the - replaced with _, but in reality this is not the case\n // We therefore normalize the name we have (replacing `_-.` with -) and then check if the text starts with the normalized name\n\n if (!normalizedSrcText.startsWith(srcPrefix)) {\n return null;\n }\n\n // strip off the prefix using the prefix length as we may have normalized the srcPrefix/packageName\n // We assume that neither the version nor the suffix contains multiple `-` like `0.1.2---rc1.tar.gz`\n // and use the difference in length to strip off the prefix in case the name contains double `--` characters\n const normalizedLengthDiff = lcText.length - normalizedSrcText.length;\n const res = lcText.slice(srcPrefix.length + normalizedLengthDiff);\n\n // source distribution\n const srcSuffixes = ['.tar.gz', '.tar.bz2', '.tar.xz', '.zip', '.tgz'];\n const srcSuffix = srcSuffixes.find((suffix) => lcText.endsWith(suffix));\n if (srcSuffix) {\n // strip off the suffix using character length\n return res.slice(0, -srcSuffix.length);\n }\n\n // binary distribution\n // for binary distributions the version is the first part after the removed distribution name\n const wheelSuffix = '.whl';\n if (lcText.endsWith(wheelSuffix) && lcText.split('-').length > 2) {\n return res.split('-')[0];\n }\n return null;\n }\n\n private static cleanSimpleHtml(html: string): string {\n return (\n html\n .replace(regEx(/<\\/?pre>/), '')\n // Certain simple repositories like artifactory don't escape > and <\n .replace(\n regEx(/data-requires-python=\"(?<before>[^\"]*?)>(?<after>[^\"]*?)\"/g),\n 'data-requires-python=\"$<before>>$<after>\"',\n )\n .replace(\n regEx(/data-requires-python=\"(?<before>[^\"]*?)<(?<after>[^\"]*?)\"/g),\n 'data-requires-python=\"$<before><$<after>\"',\n )\n );\n }\n\n private static getSimpleReleasesFromHtml(\n html: string,\n packageName: string,\n ): Releases {\n const root = parse(PypiDatasource.cleanSimpleHtml(html));\n const links = root.querySelectorAll('a');\n const releases: Releases = {};\n for (const link of Array.from(links)) {\n const version = PypiDatasource.extractVersionFromLinkText(\n link.text?.trim(),\n packageName,\n );\n if (version) {\n const release: PypiRelease = {\n yanked: link.hasAttribute('data-yanked'),\n };\n const requiresPython = link.getAttribute('data-requires-python');\n if (requiresPython) {\n release.requires_python = requiresPython;\n }\n (releases[version] ??= []).push(release);\n }\n }\n return releases;\n }\n\n private static getSimpleReleasesFromJson(\n json: string,\n packageName: string,\n ): Releases | null {\n const releases: Releases = {};\n const parsed = Json.pipe(PypiSimpleResponse).safeParse(json);\n if (!parsed.success) {\n logger.once.warn(\n { packageName, err: parsed.error },\n 'Failed to parse JSON-based Simple API response',\n );\n // Distinguish a malformed response from a package with genuinely no files, so the caller doesn't mistake an error for an empty result.\n return null;\n }\n for (const file of parsed.data.files) {\n const version = PypiDatasource.extractVersionFromLinkText(\n file.filename,\n packageName,\n );\n if (version) {\n (releases[version] ??= []).push(file);\n }\n }\n return releases;\n }\n\n private async getSimpleDependency(\n packageName: string,\n hostUrl: string,\n ): Promise<ReleaseResult | null> {\n const lookupUrl = new URL(\n ensureTrailingSlash(normalizePythonDepName(packageName)),\n hostUrl,\n ).href;\n const dependency: ReleaseResult = { releases: [] };\n const { headers: authHeaders, lookupUrl: sanitizedUrl } =\n await this.getAuthHeaders(lookupUrl);\n const headers: OutgoingHttpHeaders = {\n ...authHeaders,\n // Request the JSON serialization (PEP 691), falling back to the legacy HTML serialization (PEP 503) via content negotiation.\n // https://github.com/pypa/pip/blob/6b0011b49a068c62f65389bf4cea7af5d28cb002/src/pip/_internal/index/collector.py#L128-L134\n accept:\n 'application/vnd.pypi.simple.v1+json, application/vnd.pypi.simple.v1+html; q=0.1, text/html; q=0.01',\n };\n let response: HttpResponse;\n try {\n response = await this.http.getText(sanitizedUrl, { headers });\n } catch (err) {\n // An `abortOnError` host rule makes `Http` wrap the original error, so unwrap it before looking at the status code.\n const httpErr = err instanceof ExternalHostError ? err.err : err;\n const statusCode =\n httpErr instanceof HttpError ? httpErr.response?.statusCode : undefined;\n // A registry which cannot serve any of the negotiated types may pick a content type of its own, answer `406`, or answer `300`; the spec mandates none of them, but `406` is the common choice.\n // https://packaging.python.org/en/latest/specifications/simple-repository-api/#version-format-selection\n // Retry once without the negotiated `accept` header to preserve the pre-PEP-691 behaviour of a plain request.\n if (statusCode !== 406) {\n throw err;\n }\n\n logger.trace(\n { packageName, hostUrl, statusCode },\n 'Registry rejected negotiated Accept header, retrying without it',\n );\n response = await this.http.getText(sanitizedUrl, {\n headers: authHeaders,\n });\n }\n const dep = response?.body;\n if (!dep) {\n logger.trace({ dependency: packageName }, 'pip package not found');\n return null;\n }\n if (response.authorization) {\n dependency.isPrivate = true;\n }\n\n // Dispatch on the response content-type: JSON serialization (PEP 691) or the legacy HTML serialization (PEP 503). Matched loosely (any `json` media type) rather than the exact vendor type, as some registries/proxies relabel or strip the vendor-specific media type while still returning JSON.\n const contentType = response.headers['content-type'];\n const isJson = !!contentType && contentType.includes('json');\n const looksLikeJson = dep.trimStart().startsWith('{');\n\n if (isJson) {\n if (looksLikeJson) {\n const releases = PypiDatasource.getSimpleReleasesFromJson(\n dep,\n packageName,\n );\n // A parse failure is distinct from a package with genuinely no files, so don't report an empty release list as if it were a real (if empty) result, which would be cached as such.\n if (!releases) {\n return null;\n }\n dependency.releases = PypiDatasource.toReleases(releases);\n return dependency;\n }\n\n // The registry mislabeled a non-JSON body as JSON, so fall through to the HTML parser instead of reporting the package as not found.\n logger.debug(\n { packageName, hostUrl, contentType },\n 'Parsing Simple API response as HTML, as it is labeled as JSON but does not look like it',\n );\n }\n\n const htmlReleases = PypiDatasource.getSimpleReleasesFromHtml(\n dep,\n packageName,\n );\n dependency.releases = PypiDatasource.toReleases(htmlReleases);\n if (dependency.releases.length > 0 || !looksLikeJson) {\n return dependency;\n }\n\n // The registry honored the `Accept` header but omitted or mislabeled the `content-type`, so a JSON body went through the HTML parser and found nothing. Parse it as JSON instead of silently reporting no releases.\n logger.debug(\n { packageName, hostUrl, contentType },\n 'Retrying Simple API response as JSON, as it is not labeled as JSON but looks like it',\n );\n const jsonReleases = PypiDatasource.getSimpleReleasesFromJson(\n dep,\n packageName,\n );\n if (!jsonReleases) {\n return null;\n }\n dependency.releases = PypiDatasource.toReleases(jsonReleases);\n return dependency;\n }\n\n /**\n * The files of a version are not ordered by upload time, so use the earliest one: that is when the version was first published, which is what `minimumReleaseAge` needs.\n */\n private static toReleases(releases: Releases): Release[] {\n const versions = Object.keys(releases);\n return versions.map((version) => {\n const versionReleases = coerceArray(releases[version]);\n const isDeprecated = versionReleases.some(({ yanked }) => yanked);\n const result: Release = { version };\n const releaseTimestamp =\n PypiDatasource.getEarliestTimestamp(versionReleases);\n if (releaseTimestamp) {\n result.releaseTimestamp = releaseTimestamp;\n }\n if (isDeprecated) {\n result.isDeprecated = isDeprecated;\n }\n const pythonConstraints = versionReleases.map(\n ({ requires_python }) => requires_python,\n );\n // A file without `requires_python` can be installed on any Python version, so the version as a whole is unconstrained\n const isUnconstrained = pythonConstraints.some(\n (constraint) => !isNonEmptyString(constraint),\n );\n // There may be multiple releases with different requires_python, so we return all in an array.\n // Report no constraints at all for an unconstrained version, instead of only those of its other files, which would drop the version under `constraintsFiltering=strict`.\n result.constraints = {\n python: isUnconstrained\n ? []\n : deduplicateArray(pythonConstraints.filter(isNonEmptyString)),\n };\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["pep440.id","HttpError"],"sources":["../../../../lib/modules/datasource/pypi/index.ts"],"sourcesContent":["import { isNonEmptyString } from '@sindresorhus/is';\nimport changelogFilenameRegex from 'changelog-filename-regex';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { coerceArray, deduplicateArray } from '../../../util/array.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport { parse } from '../../../util/html.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport type {\n HttpResponse,\n OutgoingHttpHeaders,\n} from '../../../util/http/types.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { addSecretForSanitizing } from '../../../util/sanitize.ts';\nimport { Json } from '../../../util/schema-utils/index.ts';\nimport { toBase64 } from '../../../util/string.ts';\nimport type { Timestamp } from '../../../util/timestamp.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport { ensureTrailingSlash, parseUrl } from '../../../util/url.ts';\nimport * as pep440 from '../../versioning/pep440/index.ts';\nimport { Datasource } from '../datasource.ts';\nimport type { GetReleasesConfig, Release, ReleaseResult } from '../types.ts';\nimport { isGoogleArtifactRegistry } from '../util.ts';\nimport {\n isGitHubRepo,\n normalizePythonDepName,\n pypiDatasourceId,\n} from './common.ts';\nimport { findPypiIndexCredentials } from './host-rules.ts';\nimport type { PypiRelease } from './schema.ts';\nimport { PypiResponse, PypiSimpleResponse } from './schema.ts';\nimport type { Releases } from './types.ts';\n\nexport class PypiDatasource extends Datasource {\n static readonly id = pypiDatasourceId;\n\n constructor() {\n super(PypiDatasource.id);\n }\n\n override readonly caching = true;\n\n override readonly customRegistrySupport = true;\n\n static readonly defaultURL =\n getEnv().PIP_INDEX_URL ?? 'https://pypi.org/pypi/';\n override readonly defaultRegistryUrls = [PypiDatasource.defaultURL];\n\n override readonly defaultVersioning = pep440.id;\n\n override readonly registryStrategy = 'merge';\n\n override readonly releaseTimestampSupport = true;\n override readonly releaseTimestampNote =\n 'The release timestamp is determined from the earliest `upload_time` field of the files of a version. When using the Simple API, timestamps are available if the server supports the JSON-based Simple API (PEP 691).';\n override readonly sourceUrlSupport = 'release';\n override readonly sourceUrlNote =\n 'The source URL is determined from the `homepage` field if it is a github repository, else we use the `project_urls` field.';\n\n async getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n let dependency: ReleaseResult | null = null;\n // TODO: null check (#22198)\n const hostUrl = ensureTrailingSlash(\n registryUrl!.replace('https://pypi.org/simple', 'https://pypi.org/pypi'),\n );\n const normalizedLookupName = normalizePythonDepName(packageName);\n\n // not all simple indexes use this identifier, but most do\n if (hostUrl.endsWith('/simple/') || hostUrl.endsWith('/+simple/')) {\n logger.trace(\n { packageName, hostUrl },\n 'Looking up pypi simple dependency',\n );\n dependency = await this.getSimpleDependency(\n normalizedLookupName,\n hostUrl,\n );\n } else {\n logger.trace({ packageName, hostUrl }, 'Looking up pypi api dependency');\n try {\n // we need to resolve early here so we can catch any 404s and fallback to a simple lookup\n dependency = await this.getDependency(normalizedLookupName, hostUrl);\n } catch (err) {\n // error contacting json-style api -- attempt to fallback to a simple-style api\n logger.trace(\n { packageName, hostUrl, err },\n 'Looking up pypi simple dependency via fallback',\n );\n dependency = await this.getSimpleDependency(\n normalizedLookupName,\n hostUrl,\n );\n }\n }\n return dependency;\n }\n\n private sanitizeLookupUrl(lookupUrl: string, parsedUrl: URL): string {\n if (!parsedUrl.username && !parsedUrl.password) {\n return lookupUrl;\n }\n\n parsedUrl.username = '';\n parsedUrl.password = '';\n return parsedUrl.toString();\n }\n\n private async getAuthHeaders(\n lookupUrl: string,\n ): Promise<{ headers: OutgoingHttpHeaders; lookupUrl: string }> {\n const parsedUrl = parseUrl(lookupUrl);\n // v8 ignore if -- TODO: refactor to cover this branch through public behavior again\n if (!parsedUrl) {\n logger.once.debug({ lookupUrl }, 'Failed to parse URL');\n return { headers: {}, lookupUrl };\n }\n // Only Google Artifact Registry needs a header of our own: for every other host `applyHostRule` already turns the matching host rule into request auth\n if (!isGoogleArtifactRegistry(parsedUrl.hostname)) {\n return { headers: {}, lookupUrl };\n }\n\n const { username, password } = await findPypiIndexCredentials(lookupUrl);\n if (!username && !password) {\n return { headers: {}, lookupUrl };\n }\n\n const auth = toBase64(`${username ?? ''}:${password ?? ''}`);\n addSecretForSanitizing(auth);\n return {\n headers: { authorization: `Basic ${auth}` },\n lookupUrl: this.sanitizeLookupUrl(lookupUrl, parsedUrl),\n };\n }\n\n private async getDependency(\n packageName: string,\n hostUrl: string,\n ): Promise<ReleaseResult | null> {\n const lookupUrl = new URL(\n `${normalizePythonDepName(packageName)}/json`,\n hostUrl,\n ).href;\n const dependency: ReleaseResult = { releases: [] };\n logger.trace({ lookupUrl }, 'Pypi api got lookup');\n const { headers, lookupUrl: sanitizedUrl } =\n await this.getAuthHeaders(lookupUrl);\n const rep = await this.http.getJson(\n sanitizedUrl,\n { headers },\n PypiResponse,\n );\n const dep = rep?.body;\n if (rep.authorization) {\n dependency.isPrivate = true;\n }\n logger.trace({ lookupUrl }, 'Got pypi api result');\n\n if (dep.info?.home_page) {\n dependency.homepage = dep.info.home_page;\n if (isGitHubRepo(dep.info.home_page)) {\n dependency.sourceUrl = dep.info.home_page.replace(\n 'http://',\n 'https://',\n );\n }\n }\n\n if (dep.info?.project_urls) {\n for (const [name, projectUrl] of Object.entries(dep.info.project_urls)) {\n const lower = name.toLowerCase();\n\n if (\n projectUrl &&\n !dependency.sourceUrl &&\n (lower.startsWith('repo') ||\n lower === 'code' ||\n lower === 'source' ||\n isGitHubRepo(projectUrl))\n ) {\n dependency.sourceUrl = projectUrl;\n }\n\n if (\n !dependency.changelogUrl &&\n ([\n 'changelog',\n 'change log',\n 'changes',\n 'release notes',\n 'news',\n \"what's new\",\n ].includes(lower) ||\n changelogFilenameRegex.exec(lower))\n ) {\n // from https://github.com/pypa/warehouse/blob/418c7511dc367fb410c71be139545d0134ccb0df/warehouse/templates/packaging/detail.html#L24\n dependency.changelogUrl = projectUrl;\n }\n }\n }\n\n if (dep.releases) {\n dependency.releases = PypiDatasource.toReleases(dep.releases);\n }\n return dependency;\n }\n\n private static getEarliestTimestamp(\n releases: PypiRelease[],\n ): Timestamp | null {\n let earliest: Timestamp | null = null;\n for (const { upload_time } of releases) {\n const timestamp = asTimestamp(upload_time);\n // `asTimestamp` normalizes to UTC ISO 8601, so comparing the strings compares the instants\n if (timestamp && (!earliest || timestamp < earliest)) {\n earliest = timestamp;\n }\n }\n return earliest;\n }\n\n private static extractVersionFromLinkText(\n text: string,\n packageName: string,\n ): string | null {\n // source packages\n const lcText = text.toLowerCase();\n const normalizedSrcText = normalizePythonDepName(text);\n const srcPrefix = `${packageName}-`;\n\n // source distribution format: `{name}-{version}.tar.gz` (https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-file-name)\n // binary distribution: `{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl` (https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-name-convention)\n // officially both `name` and `distribution` should be normalized and then the - replaced with _, but in reality this is not the case\n // We therefore normalize the name we have (replacing `_-.` with -) and then check if the text starts with the normalized name\n\n if (!normalizedSrcText.startsWith(srcPrefix)) {\n return null;\n }\n\n // strip off the prefix using the prefix length as we may have normalized the srcPrefix/packageName\n // We assume that neither the version nor the suffix contains multiple `-` like `0.1.2---rc1.tar.gz`\n // and use the difference in length to strip off the prefix in case the name contains double `--` characters\n const normalizedLengthDiff = lcText.length - normalizedSrcText.length;\n const res = lcText.slice(srcPrefix.length + normalizedLengthDiff);\n\n // source distribution\n const srcSuffixes = ['.tar.gz', '.tar.bz2', '.tar.xz', '.zip', '.tgz'];\n const srcSuffix = srcSuffixes.find((suffix) => lcText.endsWith(suffix));\n if (srcSuffix) {\n // strip off the suffix using character length\n return res.slice(0, -srcSuffix.length);\n }\n\n // binary distribution\n // for binary distributions the version is the first part after the removed distribution name\n const wheelSuffix = '.whl';\n if (lcText.endsWith(wheelSuffix) && lcText.split('-').length > 2) {\n return res.split('-')[0];\n }\n return null;\n }\n\n private static cleanSimpleHtml(html: string): string {\n return (\n html\n .replace(regEx(/<\\/?pre>/), '')\n // Certain simple repositories like artifactory don't escape > and <\n .replace(\n regEx(/data-requires-python=\"(?<before>[^\"]*?)>(?<after>[^\"]*?)\"/g),\n 'data-requires-python=\"$<before>>$<after>\"',\n )\n .replace(\n regEx(/data-requires-python=\"(?<before>[^\"]*?)<(?<after>[^\"]*?)\"/g),\n 'data-requires-python=\"$<before><$<after>\"',\n )\n );\n }\n\n private static getSimpleReleasesFromHtml(\n html: string,\n packageName: string,\n ): Releases {\n const root = parse(PypiDatasource.cleanSimpleHtml(html));\n const links = root.querySelectorAll('a');\n const releases: Releases = {};\n for (const link of Array.from(links)) {\n const version = PypiDatasource.extractVersionFromLinkText(\n link.text?.trim(),\n packageName,\n );\n if (version) {\n const release: PypiRelease = {\n yanked: link.hasAttribute('data-yanked'),\n };\n const requiresPython = link.getAttribute('data-requires-python');\n if (requiresPython) {\n release.requires_python = requiresPython;\n }\n (releases[version] ??= []).push(release);\n }\n }\n return releases;\n }\n\n private static getSimpleReleasesFromJson(\n json: string,\n packageName: string,\n ): Releases | null {\n const releases: Releases = {};\n const parsed = Json.pipe(PypiSimpleResponse).safeParse(json);\n if (!parsed.success) {\n logger.once.warn(\n { packageName, err: parsed.error },\n 'Failed to parse JSON-based Simple API response',\n );\n // Distinguish a malformed response from a package with genuinely no files, so the caller doesn't mistake an error for an empty result.\n return null;\n }\n for (const file of parsed.data.files) {\n const version = PypiDatasource.extractVersionFromLinkText(\n file.filename,\n packageName,\n );\n if (version) {\n (releases[version] ??= []).push(file);\n }\n }\n return releases;\n }\n\n private async getSimpleDependency(\n packageName: string,\n hostUrl: string,\n ): Promise<ReleaseResult | null> {\n const lookupUrl = new URL(\n ensureTrailingSlash(normalizePythonDepName(packageName)),\n hostUrl,\n ).href;\n const dependency: ReleaseResult = { releases: [] };\n const { headers: authHeaders, lookupUrl: sanitizedUrl } =\n await this.getAuthHeaders(lookupUrl);\n const headers: OutgoingHttpHeaders = {\n ...authHeaders,\n // Request the JSON serialization (PEP 691), falling back to the legacy HTML serialization (PEP 503) via content negotiation.\n // https://github.com/pypa/pip/blob/6b0011b49a068c62f65389bf4cea7af5d28cb002/src/pip/_internal/index/collector.py#L128-L134\n accept:\n 'application/vnd.pypi.simple.v1+json, application/vnd.pypi.simple.v1+html; q=0.1, text/html; q=0.01',\n };\n let response: HttpResponse;\n try {\n response = await this.http.getText(sanitizedUrl, { headers });\n } catch (err) {\n // An `abortOnError` host rule makes `Http` wrap the original error, so unwrap it before looking at the status code.\n const httpErr = err instanceof ExternalHostError ? err.err : err;\n const statusCode =\n httpErr instanceof HttpError ? httpErr.response?.statusCode : undefined;\n // A registry which cannot serve any of the negotiated types may pick a content type of its own, answer `406`, or answer `300`; the spec mandates none of them, but `406` is the common choice.\n // https://packaging.python.org/en/latest/specifications/simple-repository-api/#version-format-selection\n // Retry once without the negotiated `accept` header to preserve the pre-PEP-691 behaviour of a plain request.\n if (statusCode !== 406) {\n throw err;\n }\n\n logger.trace(\n { packageName, hostUrl, statusCode },\n 'Registry rejected negotiated Accept header, retrying without it',\n );\n response = await this.http.getText(sanitizedUrl, {\n headers: authHeaders,\n });\n }\n const dep = response?.body;\n if (!dep) {\n logger.trace({ dependency: packageName }, 'pip package not found');\n return null;\n }\n if (response.authorization) {\n dependency.isPrivate = true;\n }\n\n // Dispatch on the response content-type: JSON serialization (PEP 691) or the legacy HTML serialization (PEP 503). Matched loosely (any `json` media type) rather than the exact vendor type, as some registries/proxies relabel or strip the vendor-specific media type while still returning JSON.\n const contentType = response.headers['content-type'];\n const isJson = !!contentType && contentType.includes('json');\n const looksLikeJson = dep.trimStart().startsWith('{');\n\n if (isJson) {\n if (looksLikeJson) {\n const releases = PypiDatasource.getSimpleReleasesFromJson(\n dep,\n packageName,\n );\n // A parse failure is distinct from a package with genuinely no files, so don't report an empty release list as if it were a real (if empty) result, which would be cached as such.\n if (!releases) {\n return null;\n }\n dependency.releases = PypiDatasource.toReleases(releases);\n return dependency;\n }\n\n // The registry mislabeled a non-JSON body as JSON, so fall through to the HTML parser instead of reporting the package as not found.\n logger.debug(\n { packageName, hostUrl, contentType },\n 'Parsing Simple API response as HTML, as it is labeled as JSON but does not look like it',\n );\n }\n\n const htmlReleases = PypiDatasource.getSimpleReleasesFromHtml(\n dep,\n packageName,\n );\n dependency.releases = PypiDatasource.toReleases(htmlReleases);\n if (dependency.releases.length > 0 || !looksLikeJson) {\n return dependency;\n }\n\n // The registry honored the `Accept` header but omitted or mislabeled the `content-type`, so a JSON body went through the HTML parser and found nothing. Parse it as JSON instead of silently reporting no releases.\n logger.debug(\n { packageName, hostUrl, contentType },\n 'Retrying Simple API response as JSON, as it is not labeled as JSON but looks like it',\n );\n const jsonReleases = PypiDatasource.getSimpleReleasesFromJson(\n dep,\n packageName,\n );\n if (!jsonReleases) {\n return null;\n }\n dependency.releases = PypiDatasource.toReleases(jsonReleases);\n return dependency;\n }\n\n /**\n * The files of a version are not ordered by upload time, so use the earliest one: that is when the version was first published, which is what `minimumReleaseAge` needs.\n */\n private static toReleases(releases: Releases): Release[] {\n const versions = Object.keys(releases);\n return versions.map((version) => {\n const versionReleases = coerceArray(releases[version]);\n const isDeprecated = versionReleases.some(({ yanked }) => yanked);\n const result: Release = { version };\n const releaseTimestamp =\n PypiDatasource.getEarliestTimestamp(versionReleases);\n if (releaseTimestamp) {\n result.releaseTimestamp = releaseTimestamp;\n }\n if (isDeprecated) {\n result.isDeprecated = isDeprecated;\n }\n const pythonConstraints = versionReleases.map(\n ({ requires_python }) => requires_python,\n );\n // A file without `requires_python` can be installed on any Python version, so the version as a whole is unconstrained\n const isUnconstrained = pythonConstraints.some(\n (constraint) => !isNonEmptyString(constraint),\n );\n // There may be multiple releases with different requires_python, so we return all in an array.\n // Report no constraints at all for an unconstrained version, instead of only those of its other files, which would drop the version under `constraintsFiltering=strict`.\n result.constraints = {\n python: isUnconstrained\n ? []\n : deduplicateArray(pythonConstraints.filter(isNonEmptyString)),\n };\n return result;\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,MAAa,uBAAuB,WAAW;CAC7C,OAAgB,KAAK;CAErB,cAAc;EACZ,MAAM,eAAe,EAAE;CACzB;CAEA,UAA4B;CAE5B,wBAA0C;CAE1C,OAAgB,aACd,OAAO,CAAC,CAAC,iBAAiB;CAC5B,sBAAwC,CAAC,eAAe,UAAU;CAElE,oBAAsCA;CAEtC,mBAAqC;CAErC,0BAA4C;CAC5C,uBACE;CACF,mBAAqC;CACrC,gBACE;CAEF,MAAM,YAAY,EAChB,aACA,eACmD;EACnD,IAAI,aAAmC;EAEvC,MAAM,UAAU,oBACd,YAAa,QAAQ,2BAA2B,uBAAuB,CACzE;EACA,MAAM,uBAAuB,uBAAuB,WAAW;EAG/D,IAAI,QAAQ,SAAS,UAAU,KAAK,QAAQ,SAAS,WAAW,GAAG;GACjE,OAAO,MACL;IAAE;IAAa;GAAQ,GACvB,mCACF;GACA,aAAa,MAAM,KAAK,oBACtB,sBACA,OACF;EACF,OAAO;GACL,OAAO,MAAM;IAAE;IAAa;GAAQ,GAAG,gCAAgC;GACvE,IAAI;IAEF,aAAa,MAAM,KAAK,cAAc,sBAAsB,OAAO;GACrE,SAAS,KAAK;IAEZ,OAAO,MACL;KAAE;KAAa;KAAS;IAAI,GAC5B,gDACF;IACA,aAAa,MAAM,KAAK,oBACtB,sBACA,OACF;GACF;EACF;EACA,OAAO;CACT;CAEA,kBAA0B,WAAmB,WAAwB;EACnE,IAAI,CAAC,UAAU,YAAY,CAAC,UAAU,UACpC,OAAO;EAGT,UAAU,WAAW;EACrB,UAAU,WAAW;EACrB,OAAO,UAAU,SAAS;CAC5B;CAEA,MAAc,eACZ,WAC8D;EAC9D,MAAM,YAAY,SAAS,SAAS;;EAEpC,IAAI,CAAC,WAAW;GACd,OAAO,KAAK,MAAM,EAAE,UAAU,GAAG,qBAAqB;GACtD,OAAO;IAAE,SAAS,CAAC;IAAG;GAAU;EAClC;EAEA,IAAI,CAAC,yBAAyB,UAAU,QAAQ,GAC9C,OAAO;GAAE,SAAS,CAAC;GAAG;EAAU;EAGlC,MAAM,EAAE,UAAU,aAAa,MAAM,yBAAyB,SAAS;EACvE,IAAI,CAAC,YAAY,CAAC,UAChB,OAAO;GAAE,SAAS,CAAC;GAAG;EAAU;EAGlC,MAAM,OAAO,SAAS,GAAG,YAAY,GAAG,GAAG,YAAY,IAAI;EAC3D,uBAAuB,IAAI;EAC3B,OAAO;GACL,SAAS,EAAE,eAAe,SAAS,OAAO;GAC1C,WAAW,KAAK,kBAAkB,WAAW,SAAS;EACxD;CACF;CAEA,MAAc,cACZ,aACA,SAC+B;EAC/B,MAAM,YAAY,IAAI,IACpB,GAAG,uBAAuB,WAAW,EAAE,QACvC,OACF,CAAC,CAAC;EACF,MAAM,aAA4B,EAAE,UAAU,CAAC,EAAE;EACjD,OAAO,MAAM,EAAE,UAAU,GAAG,qBAAqB;EACjD,MAAM,EAAE,SAAS,WAAW,iBAC1B,MAAM,KAAK,eAAe,SAAS;EACrC,MAAM,MAAM,MAAM,KAAK,KAAK,QAC1B,cACA,EAAE,QAAQ,GACV,YACF;EACA,MAAM,MAAM,KAAK;EACjB,IAAI,IAAI,eACN,WAAW,YAAY;EAEzB,OAAO,MAAM,EAAE,UAAU,GAAG,qBAAqB;EAEjD,IAAI,IAAI,MAAM,WAAW;GACvB,WAAW,WAAW,IAAI,KAAK;GAC/B,IAAI,aAAa,IAAI,KAAK,SAAS,GACjC,WAAW,YAAY,IAAI,KAAK,UAAU,QACxC,WACA,UACF;EAEJ;EAEA,IAAI,IAAI,MAAM,cACZ,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,IAAI,KAAK,YAAY,GAAG;GACtE,MAAM,QAAQ,KAAK,YAAY;GAE/B,IACE,cACA,CAAC,WAAW,cACX,MAAM,WAAW,MAAM,KACtB,UAAU,UACV,UAAU,YACV,aAAa,UAAU,IAEzB,WAAW,YAAY;GAGzB,IACE,CAAC,WAAW,iBACX;IACC;IACA;IACA;IACA;IACA;IACA;GACF,CAAC,CAAC,SAAS,KAAK,KACd,uBAAuB,KAAK,KAAK,IAGnC,WAAW,eAAe;EAE9B;EAGF,IAAI,IAAI,UACN,WAAW,WAAW,eAAe,WAAW,IAAI,QAAQ;EAE9D,OAAO;CACT;CAEA,OAAe,qBACb,UACkB;EAClB,IAAI,WAA6B;EACjC,KAAK,MAAM,EAAE,iBAAiB,UAAU;GACtC,MAAM,YAAY,YAAY,WAAW;GAEzC,IAAI,cAAc,CAAC,YAAY,YAAY,WACzC,WAAW;EAEf;EACA,OAAO;CACT;CAEA,OAAe,2BACb,MACA,aACe;EAEf,MAAM,SAAS,KAAK,YAAY;EAChC,MAAM,oBAAoB,uBAAuB,IAAI;EACrD,MAAM,YAAY,GAAG,YAAY;EAOjC,IAAI,CAAC,kBAAkB,WAAW,SAAS,GACzC,OAAO;EAMT,MAAM,uBAAuB,OAAO,SAAS,kBAAkB;EAC/D,MAAM,MAAM,OAAO,MAAM,UAAU,SAAS,oBAAoB;EAIhE,MAAM,YAAY;GADG;GAAW;GAAY;GAAW;GAAQ;EACnC,CAAC,CAAC,MAAM,WAAW,OAAO,SAAS,MAAM,CAAC;EACtE,IAAI,WAEF,OAAO,IAAI,MAAM,GAAG,CAAC,UAAU,MAAM;EAMvC,IAAI,OAAO,SAAS,MAAW,KAAK,OAAO,MAAM,GAAG,CAAC,CAAC,SAAS,GAC7D,OAAO,IAAI,MAAM,GAAG,CAAC,CAAC;EAExB,OAAO;CACT;CAEA,OAAe,gBAAgB,MAAsB;EACnD,OACE,KACG,QAAQ,MAAM,UAAU,GAAG,EAAE,CAAC,CAE9B,QACC,MAAM,4DAA4D,GAClE,gDACF,CAAC,CACA,QACC,MAAM,4DAA4D,GAClE,gDACF;CAEN;CAEA,OAAe,0BACb,MACA,aACU;EAEV,MAAM,QADO,MAAM,eAAe,gBAAgB,IAAI,CACrC,CAAC,CAAC,iBAAiB,GAAG;EACvC,MAAM,WAAqB,CAAC;EAC5B,KAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,GAAG;GACpC,MAAM,UAAU,eAAe,2BAC7B,KAAK,MAAM,KAAK,GAChB,WACF;GACA,IAAI,SAAS;IACX,MAAM,UAAuB,EAC3B,QAAQ,KAAK,aAAa,aAAa,EACzC;IACA,MAAM,iBAAiB,KAAK,aAAa,sBAAsB;IAC/D,IAAI,gBACF,QAAQ,kBAAkB;IAE5B,CAAC,SAAS,aAAa,CAAC,EAAA,CAAG,KAAK,OAAO;GACzC;EACF;EACA,OAAO;CACT;CAEA,OAAe,0BACb,MACA,aACiB;EACjB,MAAM,WAAqB,CAAC;EAC5B,MAAM,SAAS,KAAK,KAAK,kBAAkB,CAAC,CAAC,UAAU,IAAI;EAC3D,IAAI,CAAC,OAAO,SAAS;GACnB,OAAO,KAAK,KACV;IAAE;IAAa,KAAK,OAAO;GAAM,GACjC,gDACF;GAEA,OAAO;EACT;EACA,KAAK,MAAM,QAAQ,OAAO,KAAK,OAAO;GACpC,MAAM,UAAU,eAAe,2BAC7B,KAAK,UACL,WACF;GACA,IAAI,SACF,CAAC,SAAS,aAAa,CAAC,EAAA,CAAG,KAAK,IAAI;EAExC;EACA,OAAO;CACT;CAEA,MAAc,oBACZ,aACA,SAC+B;EAC/B,MAAM,YAAY,IAAI,IACpB,oBAAoB,uBAAuB,WAAW,CAAC,GACvD,OACF,CAAC,CAAC;EACF,MAAM,aAA4B,EAAE,UAAU,CAAC,EAAE;EACjD,MAAM,EAAE,SAAS,aAAa,WAAW,iBACvC,MAAM,KAAK,eAAe,SAAS;EACrC,MAAM,UAA+B;GACnC,GAAG;GAGH,QACE;EACJ;EACA,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,KAAK,KAAK,QAAQ,cAAc,EAAE,QAAQ,CAAC;EAC9D,SAAS,KAAK;GAEZ,MAAM,UAAU,eAAe,oBAAoB,IAAI,MAAM;GAC7D,MAAM,aACJ,mBAAmBC,eAAY,QAAQ,UAAU,aAAa,KAAA;GAIhE,IAAI,eAAe,KACjB,MAAM;GAGR,OAAO,MACL;IAAE;IAAa;IAAS;GAAW,GACnC,iEACF;GACA,WAAW,MAAM,KAAK,KAAK,QAAQ,cAAc,EAC/C,SAAS,YACX,CAAC;EACH;EACA,MAAM,MAAM,UAAU;EACtB,IAAI,CAAC,KAAK;GACR,OAAO,MAAM,EAAE,YAAY,YAAY,GAAG,uBAAuB;GACjE,OAAO;EACT;EACA,IAAI,SAAS,eACX,WAAW,YAAY;EAIzB,MAAM,cAAc,SAAS,QAAQ;EACrC,MAAM,SAAS,CAAC,CAAC,eAAe,YAAY,SAAS,MAAM;EAC3D,MAAM,gBAAgB,IAAI,UAAU,CAAC,CAAC,WAAW,GAAG;EAEpD,IAAI,QAAQ;GACV,IAAI,eAAe;IACjB,MAAM,WAAW,eAAe,0BAC9B,KACA,WACF;IAEA,IAAI,CAAC,UACH,OAAO;IAET,WAAW,WAAW,eAAe,WAAW,QAAQ;IACxD,OAAO;GACT;GAGA,OAAO,MACL;IAAE;IAAa;IAAS;GAAY,GACpC,yFACF;EACF;EAEA,MAAM,eAAe,eAAe,0BAClC,KACA,WACF;EACA,WAAW,WAAW,eAAe,WAAW,YAAY;EAC5D,IAAI,WAAW,SAAS,SAAS,KAAK,CAAC,eACrC,OAAO;EAIT,OAAO,MACL;GAAE;GAAa;GAAS;EAAY,GACpC,sFACF;EACA,MAAM,eAAe,eAAe,0BAClC,KACA,WACF;EACA,IAAI,CAAC,cACH,OAAO;EAET,WAAW,WAAW,eAAe,WAAW,YAAY;EAC5D,OAAO;CACT;;;;CAKA,OAAe,WAAW,UAA+B;EAEvD,OADiB,OAAO,KAAK,QACf,CAAC,CAAC,KAAK,YAAY;GAC/B,MAAM,kBAAkB,YAAY,SAAS,QAAQ;GACrD,MAAM,eAAe,gBAAgB,MAAM,EAAE,aAAa,MAAM;GAChE,MAAM,SAAkB,EAAE,QAAQ;GAClC,MAAM,mBACJ,eAAe,qBAAqB,eAAe;GACrD,IAAI,kBACF,OAAO,mBAAmB;GAE5B,IAAI,cACF,OAAO,eAAe;GAExB,MAAM,oBAAoB,gBAAgB,KACvC,EAAE,sBAAsB,eAC3B;GAOA,OAAO,cAAc,EACnB,QANsB,kBAAkB,MACvC,eAAe,CAAC,iBAAiB,UAAU,CAKtB,IAClB,CAAC,IACD,iBAAiB,kBAAkB,OAAO,gBAAgB,CAAC,EACjE;GACA,OAAO;EACT,CAAC;CACH;AACF"}
|
|
@@ -51,6 +51,12 @@ function resolvePaginationUrl(baseUrl, nextUrl, allowCrossOrigin) {
|
|
|
51
51
|
function isArtifactoryServer(res) {
|
|
52
52
|
return isString(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether the host is a Google Artifact Registry endpoint, which authenticates with a short-lived Google access token instead of static credentials.
|
|
56
|
+
*/
|
|
57
|
+
function isGoogleArtifactRegistry(hostname) {
|
|
58
|
+
return hostname.endsWith(".pkg.dev");
|
|
59
|
+
}
|
|
54
60
|
async function getGoogleAuthHostRule() {
|
|
55
61
|
try {
|
|
56
62
|
const accessToken = await new GoogleAuth({ scopes: "https://www.googleapis.com/auth/cloud-platform" }).getAccessToken();
|
|
@@ -78,6 +84,6 @@ async function getGoogleAuthToken() {
|
|
|
78
84
|
return null;
|
|
79
85
|
}
|
|
80
86
|
//#endregion
|
|
81
|
-
export { getGoogleAuthHostRule, getGoogleAuthToken, isArtifactoryServer, isCrossOriginPaginationAllowed, resolvePaginationUrl };
|
|
87
|
+
export { getGoogleAuthHostRule, getGoogleAuthToken, isArtifactoryServer, isCrossOriginPaginationAllowed, isGoogleArtifactRegistry, resolvePaginationUrl };
|
|
82
88
|
|
|
83
89
|
//# sourceMappingURL=util.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","names":[],"sources":["../../../lib/modules/datasource/util.ts"],"sourcesContent":["import { isString } from '@sindresorhus/is';\nimport { GoogleAuth } from 'google-auth-library';\nimport { logger } from '../../logger/index.ts';\nimport type { HostRule } from '../../types/index.ts';\nimport { getEnv } from '../../util/env.ts';\nimport type { HttpResponse } from '../../util/http/types.ts';\nimport { addSecretForSanitizing } from '../../util/sanitize.ts';\nimport { parseUrl, resolveSameOriginUrl } from '../../util/url.ts';\n\nconst JFROG_ARTIFACTORY_RES_HEADER = 'x-jfrog-version';\n\n/**\n * Experimental, per-datasource env var that opts a datasource into following\n * server-provided pagination links across origins. Each datasource has its own\n * flag so relaxing the restriction for one registry cannot weaken the others.\n */\nconst crossOriginPaginationEnv: Record<string, string> = {\n docker: 'RENOVATE_X_DOCKER_PAGINATION_ALLOW_CROSS_ORIGIN',\n nuget: 'RENOVATE_X_NUGET_PAGINATION_ALLOW_CROSS_ORIGIN',\n};\n\n/**\n * Whether a datasource is opted in to following server-provided pagination\n * links across origins, via its experimental `RENOVATE_X_<DATASOURCE>_PAGINATION_ALLOW_CROSS_ORIGIN`\n * env var.\n *\n * By default such links are restricted to the same origin as the request so a\n * malicious or compromised registry cannot attempt to redirect our requests in an attempt for Server Side Request Forgery.\n */\nexport function isCrossOriginPaginationAllowed(datasource: string): boolean {\n const envVar = crossOriginPaginationEnv[datasource];\n return envVar ? !!getEnv()[envVar] : false;\n}\n\n/**\n * Resolves a server-provided pagination `next` URL against `baseUrl`, returning\n * the URL to follow or `null` if it must not be followed.\n *\n * When `allowCrossOrigin` is false (the default; see\n * {@link isCrossOriginPaginationAllowed}) the link is dropped unless it stays on\n * the same origin. When true, the link is followed regardless of origin.\n */\nexport function resolvePaginationUrl(\n baseUrl: string,\n nextUrl: string,\n allowCrossOrigin: boolean,\n): string | null {\n if (!allowCrossOrigin) {\n return resolveSameOriginUrl(baseUrl, nextUrl);\n }\n try {\n const resolved = new URL(nextUrl, baseUrl).href;\n if (parseUrl(baseUrl)?.origin !== parseUrl(resolved)?.origin) {\n logger.once.warn(\n { baseUrl, nextUrl },\n 'Following cross-origin pagination link',\n );\n }\n return resolved;\n } catch {\n return null;\n }\n}\n\nexport function isArtifactoryServer<T = unknown>(\n res: HttpResponse<T> | undefined,\n): boolean {\n return isString(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);\n}\n\nexport async function getGoogleAuthHostRule(): Promise<HostRule | null> {\n try {\n const googleAuth: GoogleAuth = new GoogleAuth({\n scopes: 'https://www.googleapis.com/auth/cloud-platform',\n });\n const accessToken = await googleAuth.getAccessToken();\n if (accessToken) {\n // sanitize token\n addSecretForSanitizing(accessToken);\n return {\n username: 'oauth2accesstoken',\n password: accessToken,\n };\n }\n logger.warn(\n 'Could not retrieve access token using google-auth-library getAccessToken',\n );\n } catch (err) {\n if (err.message?.includes('Could not load the default credentials')) {\n return null;\n }\n throw err;\n }\n return null;\n}\n\nexport async function getGoogleAuthToken(): Promise<string | null> {\n const rule = await getGoogleAuthHostRule();\n if (rule) {\n const token = Buffer.from(`${rule.username}:${rule.password}`).toString(\n 'base64',\n );\n addSecretForSanitizing(token);\n return token;\n }\n return null;\n}\n"],"mappings":";;;;;;;AASA,MAAM,+BAA+B;;;;;;AAOrC,MAAM,2BAAmD;CACvD,QAAQ;CACR,OAAO;AACT;;;;;;;;;AAUA,SAAgB,+BAA+B,YAA6B;CAC1E,MAAM,SAAS,yBAAyB;CACxC,OAAO,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU;AACvC;;;;;;;;;AAUA,SAAgB,qBACd,SACA,SACA,kBACe;CACf,IAAI,CAAC,kBACH,OAAO,qBAAqB,SAAS,OAAO;CAE9C,IAAI;EACF,MAAM,WAAW,IAAI,IAAI,SAAS,OAAO,CAAC,CAAC;EAC3C,IAAI,SAAS,OAAO,CAAC,EAAE,WAAW,SAAS,QAAQ,CAAC,EAAE,QACpD,OAAO,KAAK,KACV;GAAE;GAAS;EAAQ,GACnB,wCACF;EAEF,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,oBACd,KACS;CACT,OAAO,SAAS,KAAK,QAAQ,6BAA6B;AAC5D;AAEA,eAAsB,wBAAkD;CACtE,IAAI;EAIF,MAAM,cAAc,MAAM,IAHS,WAAW,EAC5C,QAAQ,iDACV,CACmC,CAAC,CAAC,eAAe;EACpD,IAAI,aAAa;GAEf,uBAAuB,WAAW;GAClC,OAAO;IACL,UAAU;IACV,UAAU;GACZ;EACF;EACA,OAAO,KACL,0EACF;CACF,SAAS,KAAK;EACZ,IAAI,IAAI,SAAS,SAAS,wCAAwC,GAChE,OAAO;EAET,MAAM;CACR;CACA,OAAO;AACT;AAEA,eAAsB,qBAA6C;CACjE,MAAM,OAAO,MAAM,sBAAsB;CACzC,IAAI,MAAM;EACR,MAAM,QAAQ,OAAO,KAAK,GAAG,KAAK,SAAS,GAAG,KAAK,UAAU,CAAC,CAAC,SAC7D,QACF;EACA,uBAAuB,KAAK;EAC5B,OAAO;CACT;CACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"util.js","names":[],"sources":["../../../lib/modules/datasource/util.ts"],"sourcesContent":["import { isString } from '@sindresorhus/is';\nimport { GoogleAuth } from 'google-auth-library';\nimport { logger } from '../../logger/index.ts';\nimport type { HostRule } from '../../types/index.ts';\nimport { getEnv } from '../../util/env.ts';\nimport type { HttpResponse } from '../../util/http/types.ts';\nimport { addSecretForSanitizing } from '../../util/sanitize.ts';\nimport { parseUrl, resolveSameOriginUrl } from '../../util/url.ts';\n\nconst JFROG_ARTIFACTORY_RES_HEADER = 'x-jfrog-version';\n\n/**\n * Experimental, per-datasource env var that opts a datasource into following\n * server-provided pagination links across origins. Each datasource has its own\n * flag so relaxing the restriction for one registry cannot weaken the others.\n */\nconst crossOriginPaginationEnv: Record<string, string> = {\n docker: 'RENOVATE_X_DOCKER_PAGINATION_ALLOW_CROSS_ORIGIN',\n nuget: 'RENOVATE_X_NUGET_PAGINATION_ALLOW_CROSS_ORIGIN',\n};\n\n/**\n * Whether a datasource is opted in to following server-provided pagination\n * links across origins, via its experimental `RENOVATE_X_<DATASOURCE>_PAGINATION_ALLOW_CROSS_ORIGIN`\n * env var.\n *\n * By default such links are restricted to the same origin as the request so a\n * malicious or compromised registry cannot attempt to redirect our requests in an attempt for Server Side Request Forgery.\n */\nexport function isCrossOriginPaginationAllowed(datasource: string): boolean {\n const envVar = crossOriginPaginationEnv[datasource];\n return envVar ? !!getEnv()[envVar] : false;\n}\n\n/**\n * Resolves a server-provided pagination `next` URL against `baseUrl`, returning\n * the URL to follow or `null` if it must not be followed.\n *\n * When `allowCrossOrigin` is false (the default; see\n * {@link isCrossOriginPaginationAllowed}) the link is dropped unless it stays on\n * the same origin. When true, the link is followed regardless of origin.\n */\nexport function resolvePaginationUrl(\n baseUrl: string,\n nextUrl: string,\n allowCrossOrigin: boolean,\n): string | null {\n if (!allowCrossOrigin) {\n return resolveSameOriginUrl(baseUrl, nextUrl);\n }\n try {\n const resolved = new URL(nextUrl, baseUrl).href;\n if (parseUrl(baseUrl)?.origin !== parseUrl(resolved)?.origin) {\n logger.once.warn(\n { baseUrl, nextUrl },\n 'Following cross-origin pagination link',\n );\n }\n return resolved;\n } catch {\n return null;\n }\n}\n\nexport function isArtifactoryServer<T = unknown>(\n res: HttpResponse<T> | undefined,\n): boolean {\n return isString(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);\n}\n\n/**\n * Whether the host is a Google Artifact Registry endpoint, which authenticates with a short-lived Google access token instead of static credentials.\n */\nexport function isGoogleArtifactRegistry(hostname: string): boolean {\n return hostname.endsWith('.pkg.dev');\n}\n\nexport async function getGoogleAuthHostRule(): Promise<HostRule | null> {\n try {\n const googleAuth: GoogleAuth = new GoogleAuth({\n scopes: 'https://www.googleapis.com/auth/cloud-platform',\n });\n const accessToken = await googleAuth.getAccessToken();\n if (accessToken) {\n // sanitize token\n addSecretForSanitizing(accessToken);\n return {\n username: 'oauth2accesstoken',\n password: accessToken,\n };\n }\n logger.warn(\n 'Could not retrieve access token using google-auth-library getAccessToken',\n );\n } catch (err) {\n if (err.message?.includes('Could not load the default credentials')) {\n return null;\n }\n throw err;\n }\n return null;\n}\n\nexport async function getGoogleAuthToken(): Promise<string | null> {\n const rule = await getGoogleAuthHostRule();\n if (rule) {\n const token = Buffer.from(`${rule.username}:${rule.password}`).toString(\n 'base64',\n );\n addSecretForSanitizing(token);\n return token;\n }\n return null;\n}\n"],"mappings":";;;;;;;AASA,MAAM,+BAA+B;;;;;;AAOrC,MAAM,2BAAmD;CACvD,QAAQ;CACR,OAAO;AACT;;;;;;;;;AAUA,SAAgB,+BAA+B,YAA6B;CAC1E,MAAM,SAAS,yBAAyB;CACxC,OAAO,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU;AACvC;;;;;;;;;AAUA,SAAgB,qBACd,SACA,SACA,kBACe;CACf,IAAI,CAAC,kBACH,OAAO,qBAAqB,SAAS,OAAO;CAE9C,IAAI;EACF,MAAM,WAAW,IAAI,IAAI,SAAS,OAAO,CAAC,CAAC;EAC3C,IAAI,SAAS,OAAO,CAAC,EAAE,WAAW,SAAS,QAAQ,CAAC,EAAE,QACpD,OAAO,KAAK,KACV;GAAE;GAAS;EAAQ,GACnB,wCACF;EAEF,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,oBACd,KACS;CACT,OAAO,SAAS,KAAK,QAAQ,6BAA6B;AAC5D;;;;AAKA,SAAgB,yBAAyB,UAA2B;CAClE,OAAO,SAAS,SAAS,UAAU;AACrC;AAEA,eAAsB,wBAAkD;CACtE,IAAI;EAIF,MAAM,cAAc,MAAM,IAHS,WAAW,EAC5C,QAAQ,iDACV,CACmC,CAAC,CAAC,eAAe;EACpD,IAAI,aAAa;GAEf,uBAAuB,WAAW;GAClC,OAAO;IACL,UAAU;IACV,UAAU;GACZ;EACF;EACA,OAAO,KACL,0EACF;CACF,SAAS,KAAK;EACZ,IAAI,IAAI,SAAS,SAAS,wCAAwC,GAChE,OAAO;EAET,MAAM;CACR;CACA,OAAO;AACT;AAEA,eAAsB,qBAA6C;CACjE,MAAM,OAAO,MAAM,sBAAsB;CACzC,IAAI,MAAM;EACR,MAAM,QAAQ,OAAO,KAAK,GAAG,KAAK,SAAS,GAAG,KAAK,UAAU,CAAC,CAAC,SAC7D,QACF;EACA,uBAAuB,KAAK;EAC5B,OAAO;CACT;CACA,OAAO;AACT"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "../../../constants/error-messages.js";
|
|
2
2
|
import { GlobalConfig } from "../../../config/global.js";
|
|
3
3
|
import { logger } from "../../../logger/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readLocalFile } from "../../../util/fs/index.js";
|
|
5
5
|
import { exec } from "../../../util/exec/index.js";
|
|
6
|
-
import { artifactErrorResult,
|
|
6
|
+
import { artifactErrorResult, resolveToolConstraint, updateLockFile } from "../util.js";
|
|
7
7
|
import { processHostRules } from "../npm/post-update/rules.js";
|
|
8
|
-
import {
|
|
8
|
+
import { withNpmrcHostRules } from "../npm/utils.js";
|
|
9
9
|
import { isEmptyArray } from "@sindresorhus/is";
|
|
10
10
|
import upath from "upath";
|
|
11
11
|
//#region lib/modules/manager/bun/artifacts.ts
|
|
@@ -28,27 +28,30 @@ async function updateArtifacts(updateArtifact) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
const pkgFileDir = upath.dirname(packageFileName);
|
|
31
|
-
const npmrcContent = await getNpmrcContent(pkgFileDir);
|
|
32
31
|
const { additionalNpmrcContent } = processHostRules();
|
|
33
|
-
await updateNpmrcContent(pkgFileDir, npmrcContent, additionalNpmrcContent);
|
|
34
32
|
try {
|
|
35
|
-
await
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
return await withNpmrcHostRules(pkgFileDir, additionalNpmrcContent, async () => {
|
|
34
|
+
let cmd = "bun install";
|
|
35
|
+
if (!GlobalConfig.get("allowScripts") || config.ignoreScripts) cmd += " --ignore-scripts";
|
|
36
|
+
const execOptions = {
|
|
37
|
+
cwdFile: lockFileName,
|
|
38
|
+
docker: {},
|
|
39
|
+
toolConstraints: [{
|
|
40
|
+
toolName: "bun",
|
|
41
|
+
constraint: await resolveToolConstraint(config, "bun")
|
|
42
|
+
}]
|
|
43
|
+
};
|
|
44
|
+
return await updateLockFile({
|
|
45
|
+
lockFileName,
|
|
46
|
+
existingLockFileContent: oldLockFileContent,
|
|
47
|
+
packageFile: {
|
|
48
|
+
path: packageFileName,
|
|
49
|
+
contents: newPackageFileContent
|
|
50
|
+
},
|
|
51
|
+
deleteLockFile: isLockFileMaintenance,
|
|
52
|
+
run: () => exec(cmd, execOptions)
|
|
53
|
+
});
|
|
54
|
+
});
|
|
52
55
|
} catch (err) {
|
|
53
56
|
if (err.message === "temporary-error") throw err;
|
|
54
57
|
logger.warn({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","names":[],"sources":["../../../../lib/modules/manager/bun/artifacts.ts"],"sourcesContent":["import { isEmptyArray } from '@sindresorhus/is';\nimport upath from 'upath';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport { TEMPORARY_ERROR } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { exec } from '../../../util/exec/index.ts';\nimport type { ExecOptions } from '../../../util/exec/types.ts';\nimport {
|
|
1
|
+
{"version":3,"file":"artifacts.js","names":[],"sources":["../../../../lib/modules/manager/bun/artifacts.ts"],"sourcesContent":["import { isEmptyArray } from '@sindresorhus/is';\nimport upath from 'upath';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport { TEMPORARY_ERROR } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { exec } from '../../../util/exec/index.ts';\nimport type { ExecOptions } from '../../../util/exec/types.ts';\nimport { readLocalFile } from '../../../util/fs/index.ts';\nimport { processHostRules } from '../npm/post-update/rules.ts';\nimport { withNpmrcHostRules } from '../npm/utils.ts';\nimport type { UpdateArtifact, UpdateArtifactsResult } from '../types.ts';\nimport {\n artifactErrorResult,\n resolveToolConstraint,\n updateLockFile,\n} from '../util.ts';\n\nexport async function updateArtifacts(\n updateArtifact: UpdateArtifact,\n): Promise<UpdateArtifactsResult[] | null> {\n const { packageFileName, updatedDeps, newPackageFileContent, config } =\n updateArtifact;\n logger.debug(`bun.updateArtifacts(${packageFileName})`);\n const { isLockFileMaintenance } = config;\n\n if (isEmptyArray(updatedDeps) && !isLockFileMaintenance) {\n logger.debug('No updated bun deps - returning null');\n return null;\n }\n\n // Find the first bun dependency in order to handle mixed manager updates,\n // eventually falling back to the first lock file from config.\n const lockFileName =\n updatedDeps.find((dep) => dep.manager === 'bun')?.lockFiles?.[0] ??\n config.lockFiles?.[0];\n\n if (!lockFileName) {\n logger.debug(`bun: No lock file found`);\n return null;\n }\n\n const oldLockFileContent = await readLocalFile(lockFileName);\n if (!oldLockFileContent) {\n logger.debug(`No ${lockFileName} found`);\n return null;\n }\n\n const pkgFileDir = upath.dirname(packageFileName);\n const { additionalNpmrcContent } = processHostRules();\n\n try {\n return await withNpmrcHostRules(\n pkgFileDir,\n additionalNpmrcContent,\n async () => {\n let cmd = 'bun install';\n\n if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) {\n cmd += ' --ignore-scripts';\n }\n\n const execOptions: ExecOptions = {\n cwdFile: lockFileName,\n docker: {},\n toolConstraints: [\n {\n toolName: 'bun',\n constraint: await resolveToolConstraint(config, 'bun'),\n },\n ],\n };\n\n return await updateLockFile({\n lockFileName,\n existingLockFileContent: oldLockFileContent,\n packageFile: {\n path: packageFileName,\n contents: newPackageFileContent,\n },\n deleteLockFile: isLockFileMaintenance,\n run: () => exec(cmd, execOptions),\n });\n },\n );\n } catch (err) {\n if (err.message === TEMPORARY_ERROR) {\n throw err;\n }\n logger.warn({ lockfile: lockFileName, err }, `Failed to update lock file`);\n return artifactErrorResult(lockFileName, err);\n }\n}\n"],"mappings":";;;;;;;;;;;AAiBA,eAAsB,gBACpB,gBACyC;CACzC,MAAM,EAAE,iBAAiB,aAAa,uBAAuB,WAC3D;CACF,OAAO,MAAM,uBAAuB,gBAAgB,EAAE;CACtD,MAAM,EAAE,0BAA0B;CAElC,IAAI,aAAa,WAAW,KAAK,CAAC,uBAAuB;EACvD,OAAO,MAAM,sCAAsC;EACnD,OAAO;CACT;CAIA,MAAM,eACJ,YAAY,MAAM,QAAQ,IAAI,YAAY,KAAK,CAAC,EAAE,YAAY,MAC9D,OAAO,YAAY;CAErB,IAAI,CAAC,cAAc;EACjB,OAAO,MAAM,yBAAyB;EACtC,OAAO;CACT;CAEA,MAAM,qBAAqB,MAAM,cAAc,YAAY;CAC3D,IAAI,CAAC,oBAAoB;EACvB,OAAO,MAAM,MAAM,aAAa,OAAO;EACvC,OAAO;CACT;CAEA,MAAM,aAAa,MAAM,QAAQ,eAAe;CAChD,MAAM,EAAE,2BAA2B,iBAAiB;CAEpD,IAAI;EACF,OAAO,MAAM,mBACX,YACA,wBACA,YAAY;GACV,IAAI,MAAM;GAEV,IAAI,CAAC,aAAa,IAAI,cAAc,KAAK,OAAO,eAC9C,OAAO;GAGT,MAAM,cAA2B;IAC/B,SAAS;IACT,QAAQ,CAAC;IACT,iBAAiB,CACf;KACE,UAAU;KACV,YAAY,MAAM,sBAAsB,QAAQ,KAAK;IACvD,CACF;GACF;GAEA,OAAO,MAAM,eAAe;IAC1B;IACA,yBAAyB;IACzB,aAAa;KACX,MAAM;KACN,UAAU;IACZ;IACA,gBAAgB;IAChB,WAAW,KAAK,KAAK,WAAW;GAClC,CAAC;EACH,CACF;CACF,SAAS,KAAK;EACZ,IAAI,IAAI,YAAA,mBACN,MAAM;EAER,OAAO,KAAK;GAAE,UAAU;GAAc;EAAI,GAAG,4BAA4B;EACzE,OAAO,oBAAoB,cAAc,GAAG;CAC9C;AACF"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "../../../constants/error-messages.js";
|
|
2
2
|
import { logger } from "../../../logger/index.js";
|
|
3
3
|
import { findAll } from "../../../util/host-rules.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readLocalFile } from "../../../util/fs/index.js";
|
|
5
5
|
import { exec } from "../../../util/exec/index.js";
|
|
6
|
-
import { artifactError, artifactErrorResult,
|
|
6
|
+
import { artifactError, artifactErrorResult, resolveToolConstraint, updateLockFile } from "../util.js";
|
|
7
7
|
import { processHostRules } from "../npm/post-update/rules.js";
|
|
8
|
-
import {
|
|
8
|
+
import { withNpmrcHostRules } from "../npm/utils.js";
|
|
9
9
|
import { isEmptyArray } from "@sindresorhus/is";
|
|
10
10
|
import { quote } from "shlex";
|
|
11
11
|
import upath from "upath";
|
|
@@ -13,7 +13,7 @@ import upath from "upath";
|
|
|
13
13
|
async function updateArtifacts(updateArtifact) {
|
|
14
14
|
const { packageFileName, updatedDeps, newPackageFileContent, config } = updateArtifact;
|
|
15
15
|
logger.debug(`deno.updateArtifacts(${packageFileName})`);
|
|
16
|
-
const isLockFileMaintenance = config
|
|
16
|
+
const { isLockFileMaintenance } = config;
|
|
17
17
|
if (isEmptyArray(updatedDeps) && !isLockFileMaintenance) {
|
|
18
18
|
logger.debug("No updated deno deps - returning null");
|
|
19
19
|
return null;
|
|
@@ -38,38 +38,41 @@ async function updateArtifacts(updateArtifact) {
|
|
|
38
38
|
}
|
|
39
39
|
const pkgFileDir = upath.dirname(packageFileName);
|
|
40
40
|
const { additionalNpmrcContent } = processHostRules();
|
|
41
|
-
const npmrcContent = await getNpmrcContent(pkgFileDir);
|
|
42
|
-
await updateNpmrcContent(pkgFileDir, npmrcContent, additionalNpmrcContent);
|
|
43
41
|
try {
|
|
44
|
-
await
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
42
|
+
return await withNpmrcHostRules(pkgFileDir, additionalNpmrcContent, async () => {
|
|
43
|
+
const execOptions = {
|
|
44
|
+
cwdFile: updatedDeps.find((dep) => dep.managerData?.importMapReferrer)?.managerData?.importMapReferrer ?? packageFileName,
|
|
45
|
+
docker: {},
|
|
46
|
+
toolConstraints: [{
|
|
47
|
+
toolName: "deno",
|
|
48
|
+
constraint: await resolveToolConstraint(config, "deno")
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
let command = "deno install --frozen=false";
|
|
52
|
+
const defaultImportHosts = [
|
|
53
|
+
"deno.land:443",
|
|
54
|
+
"esm.sh:443",
|
|
55
|
+
"jsr.io:443",
|
|
56
|
+
"cdn.jsdelivr.net:443",
|
|
57
|
+
"raw.githubusercontent.com:443",
|
|
58
|
+
"gist.githubusercontent.com:443"
|
|
59
|
+
];
|
|
60
|
+
const additionalImportHosts = findAll({ hostType: "npm" }).filter((rule) => rule.resolvedHost).map((rule) => rule.resolvedHost);
|
|
61
|
+
if (additionalImportHosts.length > 0) {
|
|
62
|
+
const importHosts = [.../* @__PURE__ */ new Set([...defaultImportHosts, ...additionalImportHosts])].join(",");
|
|
63
|
+
command += ` --allow-import=${quote(importHosts)}`;
|
|
64
|
+
}
|
|
65
|
+
return await updateLockFile({
|
|
66
|
+
lockFileName,
|
|
67
|
+
existingLockFileContent: oldLockFileContent,
|
|
68
|
+
packageFile: {
|
|
69
|
+
path: packageFileName,
|
|
70
|
+
contents: newPackageFileContent
|
|
71
|
+
},
|
|
72
|
+
deleteLockFile: isLockFileMaintenance,
|
|
73
|
+
run: () => exec(command, execOptions)
|
|
74
|
+
});
|
|
75
|
+
});
|
|
73
76
|
} catch (err) {
|
|
74
77
|
if (err.message === "temporary-error") throw err;
|
|
75
78
|
logger.warn({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","names":["hostRules\n
|
|
1
|
+
{"version":3,"file":"artifacts.js","names":["hostRules\n .findAll"],"sources":["../../../../lib/modules/manager/deno/artifacts.ts"],"sourcesContent":["import { isEmptyArray } from '@sindresorhus/is';\nimport { quote } from 'shlex';\nimport upath from 'upath';\nimport { TEMPORARY_ERROR } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { exec } from '../../../util/exec/index.ts';\nimport type { ExecOptions } from '../../../util/exec/types.ts';\nimport { readLocalFile } from '../../../util/fs/index.ts';\nimport * as hostRules from '../../../util/host-rules.ts';\nimport { processHostRules } from '../npm/post-update/rules.ts';\nimport { withNpmrcHostRules } from '../npm/utils.ts';\nimport type { UpdateArtifact, UpdateArtifactsResult } from '../types.ts';\nimport {\n artifactError,\n artifactErrorResult,\n resolveToolConstraint,\n updateLockFile,\n} from '../util.ts';\nimport type { DenoManagerData } from './types.ts';\n\nexport async function updateArtifacts(\n updateArtifact: UpdateArtifact<DenoManagerData>,\n): Promise<UpdateArtifactsResult[] | null> {\n const { packageFileName, updatedDeps, newPackageFileContent, config } =\n updateArtifact;\n logger.debug(`deno.updateArtifacts(${packageFileName})`);\n const { isLockFileMaintenance } = config;\n\n if (isEmptyArray(updatedDeps) && !isLockFileMaintenance) {\n logger.debug('No updated deno deps - returning null');\n return null;\n }\n\n // falling back for lockFileMaintenance\n const lockFileName = updatedDeps[0]?.lockFiles?.[0] ?? config.lockFiles?.[0];\n\n if (!lockFileName) {\n logger.debug('No lock file found. Skipping artifact update.');\n return null;\n }\n\n const oldLockFileContent = await readLocalFile(lockFileName);\n if (!oldLockFileContent) {\n logger.debug(`Failed to read ${lockFileName}. Skipping artifact update.`);\n return [artifactError(lockFileName, `Failed to read \"${lockFileName}\"`)];\n }\n\n for (const updateDep of updatedDeps) {\n if (\n updateDep.depType === 'tasks' ||\n updateDep.depType === 'tasks.command'\n ) {\n logger.warn(\n {\n depType: updateDep.depType,\n depName: updateDep.depName,\n lockFileName,\n },\n \"Dependency can't be updated with a lock file\",\n );\n return [\n artifactError(\n lockFileName,\n `depType: \"${updateDep.depType}\", depName: \"${updateDep.depName}\" can't be updated with a lock file: \"${lockFileName}\"`,\n ),\n ];\n }\n }\n\n const pkgFileDir = upath.dirname(packageFileName);\n const { additionalNpmrcContent } = processHostRules();\n\n try {\n return await withNpmrcHostRules(\n pkgFileDir,\n additionalNpmrcContent,\n async () => {\n // run from its referred deno.json/deno.jsonc location if import map is used\n const importMapReferrerDep = updatedDeps.find(\n (dep) => dep.managerData?.importMapReferrer,\n );\n const cwdFile =\n importMapReferrerDep?.managerData?.importMapReferrer ??\n packageFileName;\n\n const execOptions: ExecOptions = {\n cwdFile,\n docker: {},\n toolConstraints: [\n {\n toolName: 'deno',\n constraint: await resolveToolConstraint(config, 'deno'),\n },\n ],\n };\n\n // \"deno install\" don't execute lifecycle scripts of package.json by default\n // https://docs.deno.com/runtime/reference/cli/install/#native-node.js-addons\n // deno.json(c) could have the `lock.frozen` field\n // we should always override the `frozen` flag due to if it would be specified true\n let command = 'deno install --frozen=false';\n\n // defaults as per https://docs.deno.com/runtime/fundamentals/security/#importing-from-the-web\n const defaultImportHosts = [\n 'deno.land:443',\n 'esm.sh:443',\n 'jsr.io:443',\n 'cdn.jsdelivr.net:443',\n 'raw.githubusercontent.com:443',\n 'gist.githubusercontent.com:443',\n ];\n const additionalImportHosts = hostRules\n .findAll({ hostType: 'npm' })\n .filter((rule) => rule.resolvedHost)\n .map((rule) => rule.resolvedHost);\n\n if (additionalImportHosts.length > 0) {\n // combine default and additional import hosts, removing duplicates\n const importHosts = [\n ...new Set([...defaultImportHosts, ...additionalImportHosts]),\n ].join(',');\n\n command += ` --allow-import=${quote(importHosts)}`;\n }\n\n // TODO: appending `--lockfile-only` is better to reduce disk usage\n // https://docs.deno.com/runtime/reference/cli/install/#options-lockfile-only\n return await updateLockFile({\n lockFileName,\n existingLockFileContent: oldLockFileContent,\n packageFile: {\n path: packageFileName,\n contents: newPackageFileContent,\n },\n deleteLockFile: isLockFileMaintenance,\n run: () => exec(command, execOptions),\n });\n },\n );\n } catch (err) {\n if (err.message === TEMPORARY_ERROR) {\n throw err;\n }\n logger.warn({ lockfile: lockFileName, err }, `Failed to update lock file`);\n return artifactErrorResult(lockFileName, err);\n }\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,eAAsB,gBACpB,gBACyC;CACzC,MAAM,EAAE,iBAAiB,aAAa,uBAAuB,WAC3D;CACF,OAAO,MAAM,wBAAwB,gBAAgB,EAAE;CACvD,MAAM,EAAE,0BAA0B;CAElC,IAAI,aAAa,WAAW,KAAK,CAAC,uBAAuB;EACvD,OAAO,MAAM,uCAAuC;EACpD,OAAO;CACT;CAGA,MAAM,eAAe,YAAY,EAAE,EAAE,YAAY,MAAM,OAAO,YAAY;CAE1E,IAAI,CAAC,cAAc;EACjB,OAAO,MAAM,+CAA+C;EAC5D,OAAO;CACT;CAEA,MAAM,qBAAqB,MAAM,cAAc,YAAY;CAC3D,IAAI,CAAC,oBAAoB;EACvB,OAAO,MAAM,kBAAkB,aAAa,4BAA4B;EACxE,OAAO,CAAC,cAAc,cAAc,mBAAmB,aAAa,EAAE,CAAC;CACzE;CAEA,KAAK,MAAM,aAAa,aACtB,IACE,UAAU,YAAY,WACtB,UAAU,YAAY,iBACtB;EACA,OAAO,KACL;GACE,SAAS,UAAU;GACnB,SAAS,UAAU;GACnB;EACF,GACA,8CACF;EACA,OAAO,CACL,cACE,cACA,aAAa,UAAU,QAAQ,eAAe,UAAU,QAAQ,wCAAwC,aAAa,EACvH,CACF;CACF;CAGF,MAAM,aAAa,MAAM,QAAQ,eAAe;CAChD,MAAM,EAAE,2BAA2B,iBAAiB;CAEpD,IAAI;EACF,OAAO,MAAM,mBACX,YACA,wBACA,YAAY;GASV,MAAM,cAA2B;IAC/B,SAR2B,YAAY,MACtC,QAAQ,IAAI,aAAa,iBAGP,CAAC,EAAE,aAAa,qBACnC;IAIA,QAAQ,CAAC;IACT,iBAAiB,CACf;KACE,UAAU;KACV,YAAY,MAAM,sBAAsB,QAAQ,MAAM;IACxD,CACF;GACF;GAMA,IAAI,UAAU;GAGd,MAAM,qBAAqB;IACzB;IACA;IACA;IACA;IACA;IACA;GACF;GACA,MAAM,wBAAwBA,QACnB,EAAE,UAAU,MAAM,CAAC,CAAC,CAC5B,QAAQ,SAAS,KAAK,YAAY,CAAC,CACnC,KAAK,SAAS,KAAK,YAAY;GAElC,IAAI,sBAAsB,SAAS,GAAG;IAEpC,MAAM,cAAc,CAClB,mBAAG,IAAI,IAAI,CAAC,GAAG,oBAAoB,GAAG,qBAAqB,CAAC,CAC9D,CAAC,CAAC,KAAK,GAAG;IAEV,WAAW,mBAAmB,MAAM,WAAW;GACjD;GAIA,OAAO,MAAM,eAAe;IAC1B;IACA,yBAAyB;IACzB,aAAa;KACX,MAAM;KACN,UAAU;IACZ;IACA,gBAAgB;IAChB,WAAW,KAAK,SAAS,WAAW;GACtC,CAAC;EACH,CACF;CACF,SAAS,KAAK;EACZ,IAAI,IAAI,YAAA,mBACN,MAAM;EAER,OAAO,KAAK;GAAE,UAAU;GAAc;EAAI,GAAG,4BAA4B;EACzE,OAAO,oBAAoB,cAAc,GAAG;CAC9C;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from "../../../logger/index.js";
|
|
2
2
|
import { getSiblingFileName, readLocalFile } from "../../../util/fs/index.js";
|
|
3
3
|
import { exec } from "../../../util/exec/index.js";
|
|
4
|
-
import { artifactErrorResult,
|
|
4
|
+
import { artifactErrorResult, resolveToolConstraint, updateLockFile } from "../util.js";
|
|
5
5
|
import { isNonEmptyArray } from "@sindresorhus/is";
|
|
6
6
|
import { quote } from "shlex";
|
|
7
7
|
import semver from "semver";
|
|
@@ -47,11 +47,11 @@ async function updateArtifacts({ config, packageFileName, updatedDeps }) {
|
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
try {
|
|
50
|
-
await
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
return await updateLockFile({
|
|
51
|
+
lockFileName,
|
|
52
|
+
existingLockFileContent: oldLockFileContent,
|
|
53
|
+
run: () => exec(cmd, execOptions)
|
|
54
|
+
});
|
|
55
55
|
} catch (err) {
|
|
56
56
|
logger.warn({ err }, "Error updating devbox.lock");
|
|
57
57
|
return artifactErrorResult(lockFileName, err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","names":[],"sources":["../../../../lib/modules/manager/devbox/artifacts.ts"],"sourcesContent":["import { isNonEmptyArray } from '@sindresorhus/is';\nimport semver from 'semver';\nimport { quote } from 'shlex';\nimport { logger } from '../../../logger/index.ts';\nimport { exec } from '../../../util/exec/index.ts';\nimport type { ExecOptions } from '../../../util/exec/types.ts';\nimport { getSiblingFileName, readLocalFile } from '../../../util/fs/index.ts';\nimport type { UpdateArtifact, UpdateArtifactsResult } from '../types.ts';\nimport {\n artifactErrorResult,\n
|
|
1
|
+
{"version":3,"file":"artifacts.js","names":[],"sources":["../../../../lib/modules/manager/devbox/artifacts.ts"],"sourcesContent":["import { isNonEmptyArray } from '@sindresorhus/is';\nimport semver from 'semver';\nimport { quote } from 'shlex';\nimport { logger } from '../../../logger/index.ts';\nimport { exec } from '../../../util/exec/index.ts';\nimport type { ExecOptions } from '../../../util/exec/types.ts';\nimport { getSiblingFileName, readLocalFile } from '../../../util/fs/index.ts';\nimport type { UpdateArtifact, UpdateArtifactsResult } from '../types.ts';\nimport {\n artifactErrorResult,\n resolveToolConstraint,\n updateLockFile,\n} from '../util.ts';\n\nexport async function updateArtifacts({\n config,\n packageFileName,\n updatedDeps,\n}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {\n const lockFileName = getSiblingFileName(packageFileName, 'devbox.lock');\n const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');\n if (!existingLockFileContent) {\n logger.debug('No devbox.lock found');\n return null;\n }\n\n const nixConstraint = await resolveToolConstraint(config, 'nix');\n const devboxConstraint = await resolveToolConstraint(config, 'devbox');\n\n const supportsNoInstall = devboxConstraint\n ? semver.intersects(devboxConstraint, '>=0.14.0')\n : true;\n\n const execOptions: ExecOptions = {\n cwdFile: packageFileName,\n toolConstraints: [\n // we are required to install nix because devbox spawns nix commands internally\n // https://github.com/renovatebot/renovate/discussions/35382\n // https://github.com/jetify-com/devbox/issues/2585\n {\n toolName: 'nix',\n constraint: nixConstraint,\n },\n {\n toolName: 'devbox',\n constraint: devboxConstraint,\n },\n ],\n docker: {},\n };\n\n const cmd: string[] = [];\n if (config.isLockFileMaintenance) {\n cmd.push(\n supportsNoInstall ? 'devbox update --no-install' : 'devbox update',\n );\n } else if (isNonEmptyArray(updatedDeps)) {\n if (supportsNoInstall) {\n const updateCommands: string[] = updatedDeps\n .map(\n (dep) =>\n dep.depName && `devbox update ${quote(dep.depName)} --no-install`,\n )\n .filter((dep): dep is string => Boolean(dep));\n if (updateCommands.length) {\n cmd.push(...updateCommands);\n } else {\n logger.trace('No updated devbox packages - returning null');\n return null;\n }\n } else {\n cmd.push('devbox install');\n }\n } else {\n logger.trace('No updated devbox packages - returning null');\n return null;\n }\n\n const oldLockFileContent = await readLocalFile(lockFileName);\n if (!oldLockFileContent) {\n logger.trace(`No ${lockFileName} found`);\n return null;\n }\n\n try {\n return await updateLockFile({\n lockFileName,\n existingLockFileContent: oldLockFileContent,\n run: () => exec(cmd, execOptions),\n });\n } catch (err) {\n logger.warn({ err }, 'Error updating devbox.lock');\n return artifactErrorResult(lockFileName, err);\n }\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,gBAAgB,EACpC,QACA,iBACA,eAC0D;CAC1D,MAAM,eAAe,mBAAmB,iBAAiB,aAAa;CAEtE,IAAI,CAAC,MADiC,cAAc,cAAc,MAAM,GAC1C;EAC5B,OAAO,MAAM,sBAAsB;EACnC,OAAO;CACT;CAEA,MAAM,gBAAgB,MAAM,sBAAsB,QAAQ,KAAK;CAC/D,MAAM,mBAAmB,MAAM,sBAAsB,QAAQ,QAAQ;CAErE,MAAM,oBAAoB,mBACtB,OAAO,WAAW,kBAAkB,UAAU,IAC9C;CAEJ,MAAM,cAA2B;EAC/B,SAAS;EACT,iBAAiB,CAIf;GACE,UAAU;GACV,YAAY;EACd,GACA;GACE,UAAU;GACV,YAAY;EACd,CACF;EACA,QAAQ,CAAC;CACX;CAEA,MAAM,MAAgB,CAAC;CACvB,IAAI,OAAO,uBACT,IAAI,KACF,oBAAoB,+BAA+B,eACrD;MACK,IAAI,gBAAgB,WAAW,GAAG;EACvC,IAAI,mBAAmB;GACrB,MAAM,iBAA2B,YAC9B,KACE,QACC,IAAI,WAAW,iBAAiB,MAAM,IAAI,OAAO,EAAE,cACvD,CAAC,CACA,QAAQ,QAAuB,QAAQ,GAAG,CAAC;GAC9C,IAAI,eAAe,QACjB,IAAI,KAAK,GAAG,cAAc;QACrB;IACL,OAAO,MAAM,6CAA6C;IAC1D,OAAO;GACT;EACF,OACE,IAAI,KAAK,gBAAgB;CAE7B,OAAO;EACL,OAAO,MAAM,6CAA6C;EAC1D,OAAO;CACT;CAEA,MAAM,qBAAqB,MAAM,cAAc,YAAY;CAC3D,IAAI,CAAC,oBAAoB;EACvB,OAAO,MAAM,MAAM,aAAa,OAAO;EACvC,OAAO;CACT;CAEA,IAAI;EACF,OAAO,MAAM,eAAe;GAC1B;GACA,yBAAyB;GACzB,WAAW,KAAK,KAAK,WAAW;EAClC,CAAC;CACH,SAAS,KAAK;EACZ,OAAO,KAAK,EAAE,IAAI,GAAG,4BAA4B;EACjD,OAAO,oBAAoB,cAAc,GAAG;CAC9C;AACF"}
|