Skip to content

Commit 1ca7d26

Browse files
torarvidrarkins
andauthored
feat: export UV_EXTRA_INDEX_URL before doing 'uv lock' (#31840)
Co-authored-by: Rhys Arkins <[email protected]>
1 parent 839ade1 commit 1ca7d26

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

lib/modules/manager/pep621/processors/uv.spec.ts

+64-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { join } from 'upath';
22
import { mockExecAll } from '../../../../../test/exec-util';
3-
import { fs, mockedFunction } from '../../../../../test/util';
3+
import { fs, hostRules, mockedFunction } from '../../../../../test/util';
44
import { GlobalConfig } from '../../../../config/global';
55
import type { RepoGlobalConfig } from '../../../../config/types';
66
import { getPkgReleases as _getPkgReleases } from '../../../datasource';
@@ -268,6 +268,69 @@ describe('modules/manager/pep621/processors/uv', () => {
268268
]);
269269
});
270270

271+
it('performs update on private package registry', async () => {
272+
const execSnapshots = mockExecAll();
273+
GlobalConfig.set(adminConfig);
274+
hostRules.add({
275+
matchHost: 'https://example.com',
276+
username: 'user',
277+
password: 'pass',
278+
});
279+
fs.getSiblingFileName.mockReturnValueOnce('uv.lock');
280+
fs.readLocalFile.mockResolvedValueOnce('test content');
281+
fs.readLocalFile.mockResolvedValueOnce('changed test content');
282+
// python
283+
getPkgReleases.mockResolvedValueOnce({
284+
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
285+
});
286+
// uv
287+
getPkgReleases.mockResolvedValueOnce({
288+
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
289+
});
290+
291+
const updatedDeps = [
292+
{
293+
packageName: 'dep1',
294+
depType: depTypes.dependencies,
295+
registryUrls: ['https://foobar.com'],
296+
},
297+
{
298+
packageName: 'dep2',
299+
depType: depTypes.dependencies,
300+
registryUrls: ['https://example.com'],
301+
},
302+
];
303+
const result = await processor.updateArtifacts(
304+
{
305+
packageFileName: 'pyproject.toml',
306+
newPackageFileContent: '',
307+
config: {},
308+
updatedDeps,
309+
},
310+
{},
311+
);
312+
expect(result).toEqual([
313+
{
314+
file: {
315+
contents: 'changed test content',
316+
path: 'uv.lock',
317+
type: 'addition',
318+
},
319+
},
320+
]);
321+
expect(execSnapshots).toMatchObject([
322+
{
323+
cmd: 'uv lock --upgrade-package dep1 --upgrade-package dep2',
324+
options: {
325+
env: {
326+
UV_EXTRA_INDEX_URL:
327+
'https://foobar.com/ https://user:[email protected]/',
328+
},
329+
},
330+
},
331+
]);
332+
});
333+
271334
it('return update on lockfileMaintenance', async () => {
272335
const execSnapshots = mockExecAll();
273336
GlobalConfig.set(adminConfig);

lib/modules/manager/pep621/processors/uv.ts

+38
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import is from '@sindresorhus/is';
22
import { quote } from 'shlex';
33
import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
44
import { logger } from '../../../../logger';
5+
import type { HostRule } from '../../../../types';
56
import { exec } from '../../../../util/exec';
67
import type { ExecOptions, ToolConstraint } from '../../../../util/exec/types';
78
import { getSiblingFileName, readLocalFile } from '../../../../util/fs';
9+
import { find } from '../../../../util/host-rules';
810
import { Result } from '../../../../util/result';
11+
import { parseUrl } from '../../../../util/url';
12+
import { PypiDatasource } from '../../../datasource/pypi';
913
import type {
1014
PackageDependency,
1115
UpdateArtifact,
@@ -115,8 +119,12 @@ export class UvProcessor implements PyProjectProcessor {
115119
constraint: config.constraints?.uv,
116120
};
117121

122+
const extraEnv = {
123+
...getUvExtraIndexUrl(updateArtifact.updatedDeps),
124+
};
118125
const execOptions: ExecOptions = {
119126
cwdFile: packageFileName,
127+
extraEnv,
120128
docker: {},
121129
userConfiguredEnv: config.env,
122130
toolConstraints: [pythonConstraint, uvConstraint],
@@ -191,3 +199,33 @@ function generateCMD(updatedDeps: Upgrade[]): string {
191199

192200
return `${uvUpdateCMD} ${deps.map((dep) => `--upgrade-package ${quote(dep)}`).join(' ')}`;
193201
}
202+
203+
function getMatchingHostRule(url: string | undefined): HostRule {
204+
return find({ hostType: PypiDatasource.id, url });
205+
}
206+
207+
function getUvExtraIndexUrl(deps: Upgrade[]): NodeJS.ProcessEnv {
208+
const registryUrls = new Set(deps.map((dep) => dep.registryUrls).flat());
209+
const extraIndexUrls: string[] = [];
210+
211+
for (const registryUrl of registryUrls) {
212+
const parsedUrl = parseUrl(registryUrl);
213+
if (!parsedUrl) {
214+
continue;
215+
}
216+
217+
const rule = getMatchingHostRule(parsedUrl.toString());
218+
if (rule.username) {
219+
parsedUrl.username = rule.username;
220+
}
221+
if (rule.password) {
222+
parsedUrl.password = rule.password;
223+
}
224+
225+
extraIndexUrls.push(parsedUrl.toString());
226+
}
227+
228+
return {
229+
UV_EXTRA_INDEX_URL: extraIndexUrls.join(' '),
230+
};
231+
}

0 commit comments

Comments
 (0)