TS Workflow: Auto-Delete Old PyPI Versions
Facing PyPI's version limits can be a real headache when trying to publish updates to your packages. Unfortunately, PyPI doesn't offer a straightforward API or CLI command to automatically delete older versions. This means we need to get creative and automate the process using a TypeScript workflow that mimics UI interactions. In this article, we'll walk through creating a GitHub Actions workflow that does just that: it automatically deletes the oldest PyPI release before publishing a new one. This ensures you stay within PyPI's limits and keeps your deployment process smooth.
The Problem: PyPI Version Limits
When you're actively developing and releasing updates to your Python packages, you might eventually run into PyPI's version limits. This can halt your deployment pipeline and leave you scrambling for a solution. The core issue is that PyPI doesn't provide a direct way to programmatically delete older versions, which forces us to find alternative methods. The challenge is significant because it requires automating a task typically done manually through the PyPI website. Automating this manual process involves simulating user interactions, which can be complex and requires careful planning to ensure reliability and accuracy. Moreover, the solution should be robust enough to handle different scenarios and edge cases, such as network issues or changes in the PyPI website structure. Ultimately, the goal is to create a seamless and automated workflow that prevents deployment failures due to version limits and allows developers to focus on writing code rather than managing package versions.
Why No Direct API?
One might wonder, why doesn't PyPI offer a simple API endpoint for deleting versions? The reasons often come down to maintaining the integrity and history of packages. Deleting a version can have unintended consequences for users who depend on that specific release. Without a proper audit trail and safeguards, accidental deletions could cause significant disruption. Therefore, PyPI prioritizes a more controlled, manual deletion process to minimize the risk of errors. This design decision, while understandable, places the burden on developers to manage version limits manually or devise their own automation solutions. The lack of a direct API is a common pain point in the Python packaging ecosystem, highlighting the need for innovative workarounds like the TypeScript workflow we'll explore in this article. This workaround not only addresses the immediate problem of version limits but also serves as a testament to the flexibility and power of modern automation tools in overcoming platform limitations.
Defining the Solution: A TypeScript Workflow
Our solution involves crafting a TypeScript workflow that runs as a GitHub Action. This workflow will:
- Authenticate with PyPI.
- Navigate to the package's page.
- Identify the oldest version.
- Delete the oldest version.
- Proceed with publishing the new version.
The key here is to use tools that allow us to simulate browser actions, since we're essentially automating what a user would do manually. This approach might seem a bit unconventional, but it's a practical way to overcome the limitations imposed by the lack of a direct API. The choice of TypeScript is strategic because it offers strong typing and excellent tooling, which are crucial for building a reliable and maintainable automation script. Moreover, TypeScript integrates well with Node.js, making it easy to run our workflow in a GitHub Actions environment. The workflow will be designed to be idempotent, meaning that it can be run multiple times without causing unintended side effects. This is important because we want to ensure that the deletion process is consistent and doesn't lead to data corruption or inconsistencies. By carefully orchestrating these steps, we can create a robust and automated solution for managing PyPI version limits.
Step-by-Step: Building the TypeScript Workflow
Let's break down the process of building this workflow.
1. Setting Up Your Project
First, create a new TypeScript project. Initialize npm and install the necessary dependencies. We'll need libraries like puppeteer or playwright to control a headless browser, and dotenv to manage environment variables.
mkdir pypi-version-cleanup
cd pypi-version-cleanup
npm init -y
npm install puppeteer dotenv --save-dev
npm install @types/node --save-dev
tsc --init
It's crucial to set up your project correctly from the start to avoid compatibility issues later on. The puppeteer library will allow us to control a headless Chrome browser, simulating user interactions on the PyPI website. The dotenv library will enable us to securely store and access sensitive information such as our PyPI credentials. The @types/node package provides TypeScript definitions for Node.js, ensuring that our code is type-safe and reliable. Finally, running tsc --init initializes a TypeScript project with a tsconfig.json file, which configures the TypeScript compiler. This file allows us to specify compiler options such as the target ECMAScript version, module system, and source map generation. By following these steps, we create a solid foundation for our TypeScript workflow and ensure that we have the necessary tools and configurations in place.
2. Authentication
Store your PyPI username and password (or, ideally, an API token) as GitHub Secrets. Then, in your TypeScript code, use dotenv to load these secrets into environment variables.
import * as dotenv from 'dotenv';
dotenv.config();
const username = process.env.PYPI_USERNAME;
const password = process.env.PYPI_PASSWORD; // Use API token instead
if (!username || !password) {
throw new Error('PyPI credentials not found in environment variables.');
}
Securely managing your credentials is paramount to prevent unauthorized access to your PyPI account. Storing your username and password directly in the code is a major security risk. Instead, we leverage GitHub Secrets, which are encrypted environment variables stored securely in your repository. The dotenv library allows us to load these secrets into our Node.js environment at runtime. It's highly recommended to use an API token instead of your password, as API tokens can be revoked if compromised. By following these best practices, we ensure that our PyPI credentials are protected and that our workflow adheres to security standards. This approach not only safeguards our account but also promotes a culture of security consciousness within our development team.
3. Browser Automation
Use puppeteer or playwright to launch a headless browser, navigate to the PyPI login page, and authenticate.
import puppeteer from 'puppeteer';
async function deleteOldestVersion(packageName: string) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://pypi.org/account/login/');
await page.type('#username', username);
await page.type('#password', password);
await page.click('button[type=