-
Notifications
You must be signed in to change notification settings - Fork 690
Add support for custom PyPI repository configuration #1260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f054be5
24f18a9
a272ba3
fc9c7d0
da6c752
d20d604
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -422,3 +422,79 @@ export function getDownloadFileName(downloadUrl: string): string | undefined { | |||||||||||||||
| ? path.join(tempDir, path.basename(downloadUrl)) | ||||||||||||||||
| : undefined; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Configure pip to use a custom PyPI repository | ||||||||||||||||
| * Creates a pip.conf (Linux/macOS) or pip.ini (Windows) file with repository and credentials | ||||||||||||||||
| * @param pypiUrl The custom PyPI repository URL | ||||||||||||||||
| * @param username The username for authentication (optional) | ||||||||||||||||
| * @param password The password or token for authentication (optional) | ||||||||||||||||
| */ | ||||||||||||||||
| export async function configurePipRepository( | ||||||||||||||||
| pypiUrl: string, | ||||||||||||||||
| username?: string, | ||||||||||||||||
| password?: string | ||||||||||||||||
| ): Promise<void> { | ||||||||||||||||
|
Comment on lines
+433
to
+437
|
||||||||||||||||
| if (!pypiUrl) { | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| core.info(`Configuring pip to use custom PyPI repository: ${pypiUrl}`); | ||||||||||||||||
|
|
||||||||||||||||
| // Determine the pip config file location and name based on OS | ||||||||||||||||
| const homeDir = process.env.HOME || process.env.USERPROFILE || ''; | ||||||||||||||||
| const configDir = IS_WINDOWS | ||||||||||||||||
| ? path.join(homeDir, 'pip') | ||||||||||||||||
| : path.join(homeDir, '.pip'); | ||||||||||||||||
| const configFile = IS_WINDOWS ? 'pip.ini' : 'pip.conf'; | ||||||||||||||||
| const configPath = path.join(configDir, configFile); | ||||||||||||||||
|
Comment on lines
+445
to
+450
|
||||||||||||||||
|
|
||||||||||||||||
| // Create the config directory if it doesn't exist | ||||||||||||||||
| if (!fs.existsSync(configDir)) { | ||||||||||||||||
| fs.mkdirSync(configDir, {recursive: true}); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Build the index URL with credentials if provided | ||||||||||||||||
| let indexUrl = pypiUrl; | ||||||||||||||||
| if (username && password) { | ||||||||||||||||
| // Parse the URL to inject credentials | ||||||||||||||||
| try { | ||||||||||||||||
| const url = new URL(pypiUrl); | ||||||||||||||||
| url.username = encodeURIComponent(username); | ||||||||||||||||
| url.password = encodeURIComponent(password); | ||||||||||||||||
| indexUrl = url.toString(); | ||||||||||||||||
heckerdj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| } catch (error) { | ||||||||||||||||
| core.warning( | ||||||||||||||||
| `Failed to parse PyPI URL: ${pypiUrl}. Using URL without credentials.` | ||||||||||||||||
| ); | ||||||||||||||||
| indexUrl = pypiUrl; | ||||||||||||||||
| } | ||||||||||||||||
| } else if (username || password) { | ||||||||||||||||
| core.warning( | ||||||||||||||||
| 'Both pypi-username and pypi-password must be provided for authentication. Configuring without credentials.' | ||||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Create the pip config content | ||||||||||||||||
| const configContent = `[global] | ||||||||||||||||
| index-url = ${indexUrl} | ||||||||||||||||
| `; | ||||||||||||||||
|
|
||||||||||||||||
| // Write the config file | ||||||||||||||||
| try { | ||||||||||||||||
| fs.writeFileSync(configPath, configContent, {encoding: 'utf8'}); | ||||||||||||||||
|
||||||||||||||||
| core.info(`Successfully created pip config file at: ${configPath}`); | ||||||||||||||||
|
|
||||||||||||||||
| // Mask credentials in logs if they were used | ||||||||||||||||
| if (username) { | ||||||||||||||||
| core.setSecret(username); | ||||||||||||||||
| } | ||||||||||||||||
| if (password) { | ||||||||||||||||
| core.setSecret(password); | ||||||||||||||||
| } | ||||||||||||||||
heckerdj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| } catch (error) { | ||||||||||||||||
| core.setFailed( | ||||||||||||||||
| `Failed to create pip config file at ${configPath}: ${error}` | ||||||||||||||||
|
Comment on lines
+496
to
+497
|
||||||||||||||||
| core.setFailed( | |
| `Failed to create pip config file at ${configPath}: ${error}` | |
| const errorMessage = | |
| error instanceof Error ? error.message : String(error); | |
| core.setFailed( | |
| `Failed to create pip config file at ${configPath}: ${errorMessage}` |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When core.setFailed() is called, it doesn't throw an error or stop execution. The function continues to execute and returns normally. This means that if the config file write fails, the workflow step will be marked as failed but subsequent operations (like pip install) may still attempt to run. Consider throwing an error after setFailed() or restructuring the error handling to ensure the promise rejects.
| ); | |
| ); | |
| throw error instanceof Error | |
| ? error | |
| : new Error( | |
| `Failed to create pip config file at ${configPath}: ${error}` | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no test coverage for the error handling path when URL parsing fails or when file writing fails. Consider adding tests that verify the behavior when an invalid URL is provided or when the file system operations fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback