GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Add support for custom PyPI repository configuration by heckerdj · Pull Request #1260 · actions/setup-python

Conversation

@heckerdj

@heckerdj heckerdj commented Dec 16, 2025

Copy link
Copy Markdown

Description

This PR adds the ability to configure pip to use a custom PyPI repository with authentication credentials through the setup-python action. This addresses the need for users working in enterprise environments where:

  • Public PyPI is blocked by firewall
  • Internal repositories (Nexus, Artifactory, etc.) are used for security-scanned packages
  • Custom package indices need to be configured

Changes

  • action.yml: Added three new optional inputs:

    • pypi-url: URL of the custom PyPI repository
    • pypi-username: Username for authentication
    • pypi-password: Password or token for authentication
  • src/utils.ts: Implemented configurePipRepository() function that:

    • Creates pip.conf (Linux/macOS) or pip.ini (Windows) with proper configuration
    • Embeds credentials securely in the repository URL
    • Automatically masks passwords in logs using core.setSecret()
    • Handles missing or partial credentials gracefully
  • src/setup-python.ts: Integrated pip configuration into the workflow, running after cache restoration but before package installation

  • tests/utils.test.ts: Added comprehensive unit tests covering:

    • Configuration with URL only
    • Configuration with credentials
    • Empty URL handling
    • Partial credential warnings
    • Directory creation
  • README.md: Added documentation with usage examples and security notes

Usage Example

steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
  with:
    python-version: '3.13'
    pypi-url: ${{ secrets.PYPI_REPO_URL }}
    pypi-username: ${{ secrets.PYPI_USER }}
    pypi-password: ${{ secrets.PYPI_PASSWORD }}
- run: pip install -r requirements.txt

Testing

All unit tests pass successfully:

  • ✅ Creates pip config file with URL only
  • ✅ Creates pip config file with credentials
  • ✅ Does nothing when pypiUrl is not provided
  • ✅ Warns when only username is provided
  • ✅ Warns when only password is provided
  • ✅ Creates config directory if it does not exist

Fixes #814

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Add support for custom PyPI repository configuration
f054be5
- Add pypi-url, pypi-username, and pypi-password inputs to action.yml
- Implement configurePipRepository() function in utils.ts to create pip.conf/pip.ini
- Integrate pip configuration into setup-python.ts workflow
- Add comprehensive unit tests for pip configuration functionality
- Update README.md with usage examples and documentation
- Automatically mask credentials in logs for security

Fixes actions#814
@heckerdj
heckerdj requested a review from a team as a code owner December 16, 2025 16:41
Copilot AI review requested due to automatic review settings December 16, 2025 16:41
Copilot AI reviewed Dec 16, 2025
View reviewed changes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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.

Pull request overview

This PR adds support for configuring pip to use custom PyPI repositories with authentication, enabling users in enterprise environments to work with internal package indices (Nexus, Artifactory, etc.) when public PyPI is blocked or security-scanned packages are required.

Key changes:

  • Added three new optional inputs (pypi-url, pypi-username, pypi-password) to configure custom PyPI repositories
  • Implemented configurePipRepository() function to create platform-specific pip configuration files with embedded credentials
  • Integrated pip configuration into the setup workflow between cache restoration and package installation

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
action.yml Defines three new optional inputs for custom PyPI repository configuration
src/utils.ts Implements configurePipRepository() function to create pip config files with credentials
src/setup-python.ts Integrates pip repository configuration into the main setup workflow
tests/utils.test.ts Adds comprehensive unit tests for the pip configuration functionality
README.md Documents the new feature with usage examples and security notes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/utils.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/utils.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/setup-python.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread __tests__/utils.test.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

heckerdj and others added 2 commits December 19, 2025 08:08
@heckerdj
Update src/setup-python.ts
24f18a9
Co-authored-by: Copilot <[email protected]>
@heckerdj
Update __tests__/utils.test.ts
a272ba3
Co-authored-by: Copilot <[email protected]>
@heckerdj
heckerdj requested a review from Copilot December 19, 2025 14:36
Copilot started reviewing on behalf of heckerdj December 19, 2025 14:36
View session
Copilot AI reviewed Dec 19, 2025
View reviewed changes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread __tests__/utils.test.ts
Comment on lines +383 to +486
describe('configurePipRepository', () => {
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const testHome = path.join(tempDir, 'home');

beforeEach(() => {
// Setup test home directory
process.env.HOME = testHome;
process.env.USERPROFILE = testHome;
if (fs.existsSync(testHome)) {
fs.rmSync(testHome, {recursive: true, force: true});
}
fs.mkdirSync(testHome, {recursive: true});
});

afterEach(() => {
// Cleanup
if (fs.existsSync(testHome)) {
fs.rmSync(testHome, {recursive: true, force: true});
}
process.env.HOME = originalHome;
process.env.USERPROFILE = originalUserProfile;
});

it('creates pip config file with URL only', async () => {
const pypiUrl = 'https://nexus.example.com/repository/pypi/simple';
await configurePipRepository(pypiUrl);

const configDir = IS_WINDOWS
? path.join(testHome, 'pip')
: path.join(testHome, '.pip');
const configFile = IS_WINDOWS ? 'pip.ini' : 'pip.conf';
const configPath = path.join(configDir, configFile);

expect(fs.existsSync(configPath)).toBeTruthy();
const content = fs.readFileSync(configPath, 'utf8');
expect(content).toContain('[global]');
expect(content).toContain(`index-url = ${pypiUrl}`);
});

it('creates pip config file with credentials', async () => {
const pypiUrl = 'https://nexus.example.com/repository/pypi/simple';
const username = 'testuser';
const password = 'testpass';
await configurePipRepository(pypiUrl, username, password);

const configDir = IS_WINDOWS
? path.join(testHome, 'pip')
: path.join(testHome, '.pip');
const configFile = IS_WINDOWS ? 'pip.ini' : 'pip.conf';
const configPath = path.join(configDir, configFile);

expect(fs.existsSync(configPath)).toBeTruthy();
const content = fs.readFileSync(configPath, 'utf8');
expect(content).toContain('[global]');
const encodedUsername = encodeURIComponent(username);
const encodedPassword = encodeURIComponent(password);
expect(content).toContain(`index-url = https://${encodedUsername}:${encodedPassword}@`);
expect(content).toContain('nexus.example.com/repository/pypi/simple');
});

it('does nothing when pypiUrl is not provided', async () => {
await configurePipRepository('');

const configDir = IS_WINDOWS
? path.join(testHome, 'pip')
: path.join(testHome, '.pip');

expect(fs.existsSync(configDir)).toBeFalsy();
});

it('warns when only username is provided', async () => {
const warningMock = jest.spyOn(core, 'warning');
const pypiUrl = 'https://nexus.example.com/repository/pypi/simple';
const username = 'testuser';
await configurePipRepository(pypiUrl, username);

expect(warningMock).toHaveBeenCalledWith(
'Both pypi-username and pypi-password must be provided for authentication. Configuring without credentials.'
);
});

it('warns when only password is provided', async () => {
const warningMock = jest.spyOn(core, 'warning');
const pypiUrl = 'https://nexus.example.com/repository/pypi/simple';
const password = 'testpass';
await configurePipRepository(pypiUrl, undefined, password);

expect(warningMock).toHaveBeenCalledWith(
'Both pypi-username and pypi-password must be provided for authentication. Configuring without credentials.'
);
});

it('creates config directory if it does not exist', async () => {
const pypiUrl = 'https://nexus.example.com/repository/pypi/simple';
const configDir = IS_WINDOWS
? path.join(testHome, 'pip')
: path.join(testHome, '.pip');

expect(fs.existsSync(configDir)).toBeFalsy();
await configurePipRepository(pypiUrl);
expect(fs.existsSync(configDir)).toBeTruthy();
});
});

Copilot AI Dec 19, 2025

Copy link

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Copilot uses AI. Check for mistakes.

@heckerdj heckerdj Dec 20, 2025

Copy link
Copy Markdown
Author

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/utils.ts
} catch (error) {
core.setFailed(
`Failed to create pip config file at ${configPath}: ${error}`
);

Copilot AI Dec 19, 2025

Copy link

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.

Suggested change
);
);
throw error instanceof Error
? error
: new Error(
`Failed to create pip config file at ${configPath}: ${error}`
);

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Copilot uses AI. Check for mistakes.
Comment thread README.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/utils.ts
Comment on lines +493 to +494
core.setFailed(
`Failed to create pip config file at ${configPath}: ${error}`

Copilot AI Dec 19, 2025

Copy link

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.

The error object is being directly interpolated into a string, which may not provide useful information. Error objects should be accessed via their message property or converted to string explicitly for better error reporting.

Suggested change
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}`

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Copilot uses AI. Check for mistakes.
Comment thread src/utils.ts
Comment on lines +433 to +437
export async function configurePipRepository(
pypiUrl: string,
username?: string,
password?: string
): Promise<void> {

Copilot AI Dec 19, 2025

Copy link

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.

The function is declared as async but doesn't use any await operations. The async keyword is unnecessary and adds overhead. Either remove the async keyword and change the return type from Promise to void, or if file operations should be asynchronous for consistency, use the async versions of fs methods like fs.promises.writeFile.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Copilot uses AI. Check for mistakes.
Comment thread src/utils.ts
Comment on lines +445 to +450
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);

Copilot AI Dec 19, 2025

Copy link

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.

If both HOME and USERPROFILE environment variables are undefined or empty, homeDir will be an empty string, resulting in config paths like 'pip/pip.ini' or '.pip/pip.conf' in the current working directory instead of the user's home directory. This could cause pip configuration issues. Consider validating that homeDir is not empty and throwing an error or logging a warning if it cannot be determined.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Copilot uses AI. Check for mistakes.
Comment thread src/utils.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/setup-python.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/utils.ts

// Write the config file
try {
fs.writeFileSync(configPath, configContent, {encoding: 'utf8'});

Copilot AI Dec 19, 2025

Copy link

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.

The config file is written with destructive writeFileSync, which will overwrite any existing pip configuration. If users have existing pip.conf/pip.ini files with other settings (like trusted-hosts, timeout values, etc.), they will be lost. Consider reading and merging with existing configuration, or at minimum documenting this behavior and warning users.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Copilot uses AI. Check for mistakes.

@heckerdj heckerdj Dec 20, 2025

Copy link
Copy Markdown
Author

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
heckerdj and others added 3 commits December 20, 2025 13:34
@heckerdj
Update README.md
fc9c7d0
Co-authored-by: Copilot <[email protected]>
@heckerdj
Update src/setup-python.ts
da6c752
Co-authored-by: Copilot <[email protected]>
@heckerdj
Update src/utils.ts
d20d604
Co-authored-by: Copilot <[email protected]>
@zhivkopopov

zhivkopopov commented Apr 2, 2026

Copy link
Copy Markdown

Hello, @heckerdj is this PR going to be merged anytime soon? 🙏 This functionality is great and would be really useful to us.

vivanov83 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

Copilot code review Copilot
Copilot review effort
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments

At least 1 approving review is required to merge this pull request.

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Optional ability to set the pypi repository and credentials to be used by pip on the runner

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@heckerdj @zhivkopopov
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.