@pdecat/semantic-release-python
Version:
A semantic-release plugin for Python (with support for both setup.cfg/setup.py and pyproject.toml with e.g. Poetry and uv). Fork of https://github.com/unimonkiez/semantic-release-python which is a fork of https://github.com/megabyte-labs/semantic-release-
162 lines (141 loc) • 4.25 kB
JavaScript
import FormData from 'form-data'
import fs from 'fs'
import got from 'got'
import path from 'path'
import { execa } from 'execa';
import { getOption } from './util.js';
/**
* @param name
*/
export function assertEnvVar(name) {
if (!process.env[name]) {
throw new Error(`Environment variable ${name} is not set`)
}
}
/**
* @param executable
* @param args
* @param exitCode
*/
export async function assertExitCode(executable, args = [], exitCode = 0) {
let res
try {
res = await execa(executable, args)
} catch (error) {
res = error
}
if (res.exitCode != exitCode) {
throw new Error(`command: ${res.command}, exit code: ${res.exitCode}, expected: ${exitCode}`)
}
}
/**
* @param name
*/
export async function assertPackage(name) {
try {
await assertExitCode('pip', ['show', name], 0)
} catch {
throw new Error(`Package ${name} is not installed`)
}
}
/**
* @param setupPy
* @param logger
*/
export async function verifySetupPy(setupPy, logger) {
try {
const verifyPath = path.resolve(__dirname, 'verifySetup.py')
const setupPyFolder = path.basename(setupPy)
const cwd = path.dirname(setupPy)
logger.log(`Running verifyPython.py on ${verifyPath} with ${setupPyFolder} at ${cwd}`)
await execa('python3', [verifyPath, setupPyFolder], {
cwd
})
} catch (error) {
logger.log(error)
throw new Error(`version in ${setupPy}`, error)
}
}
/**
* @param repoUrl
* @param username
* @param token
*/
export async function verifyAuth(repoUrl, username, token) {
const form = new FormData()
form.append(':action', 'file_upload')
const basicAuth = Buffer.from(`${username}:${token}`).toString('base64')
const headers = {
Authorization: `Basic ${basicAuth}`
}
try {
await got(repoUrl, {
body: form,
headers: Object.assign(headers, form.getHeaders()),
method: 'post'
})
} catch (error) {
if (error.response && error.response.statusCode == 403) {
throw error
}
}
}
/**
* @param pluginConfig
* @param logger
*/
async function verifySetupCfg(pluginConfig, logger) {
const setupPy = getOption(pluginConfig, 'setupPy')
const pypiPublish = getOption(pluginConfig, 'pypiPublish')
if (pypiPublish !== false) {
const username = process.env.PYPI_USERNAME ? process.env.PYPI_USERNAME : '__token__'
const token = process.env.PYPI_TOKEN
const repoUrl = process.env.PYPI_REPO_URL ? process.env.PYPI_REPO_URL : getOption(pluginConfig, 'repoUrl')
assertEnvVar('PYPI_TOKEN')
logger.log('Check if setuptools, wheel and twine are installed')
await assertPackage('setuptools')
await assertPackage('wheel')
await assertPackage('twine')
logger.log(`Verify authentication for ${username}@${repoUrl}`)
await verifyAuth(repoUrl, username, token)
}
logger.log('Verify that version is not set in setup.py')
await verifySetupPy(setupPy, logger)
}
/**
* @param pluginConfig
* @param logger
*/
async function verifyPyproject(pluginConfig, logger) {
const pypiPublish = getOption(pluginConfig, 'pypiPublish')
if (pypiPublish !== false) {
const username = process.env.PYPI_USERNAME ? process.env.PYPI_USERNAME : '__token__'
const token = process.env.PYPI_TOKEN
const repoUrl = process.env.PYPI_REPO_URL ? process.env.PYPI_REPO_URL : getOption(pluginConfig, 'repoUrl')
assertEnvVar('PYPI_TOKEN')
logger.log('Check if poetry is installed')
await assertExitCode('poetry')
logger.log(`Verify authentication for ${username}@${repoUrl}`)
await verifyAuth(repoUrl, username, token)
}
}
/**
* @param pluginConfig
* @param root0
* @param root0.logger
*/
export async function verifyConditions(pluginConfig, { logger }) {
const setupPy = getOption(pluginConfig, 'setupPy');
if (fs.existsSync(setupPy)) {
if (path.basename(setupPy) === "setup.cfg") {
await verifySetupCfg(pluginConfig, logger)
} else if (path.basename(setupPy) === "pyproject.toml") {
await verifyPyproject(pluginConfig, logger)
}
} else {
const pypiPublish = getOption(pluginConfig, 'pypiPublish')
if (pypiPublish !== false) {
throw new Error(`Project must have either a setup.cfg or a pyproject.toml file`)
}
}
}