@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-
132 lines (123 loc) • 3.75 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { execa } from 'execa';
import { getOption } from './util.js';
/**
* @param setupPy
* @param distDir
* @param repoUrl
* @param gpgSign
* @param gpgIdentity
*/
function publishPackage(setupPy, distDir, repoUrl, gpgSign, gpgIdentity) {
return execa(
'python3',
[
'-m',
'twine',
'upload',
'--repository-url',
repoUrl,
'--non-interactive',
'--skip-existing',
'--verbose',
gpgSign ? '--sign' : null,
gpgSign && gpgIdentity ? '--identity' : null,
gpgSign && gpgIdentity ? gpgIdentity : null,
`${distDir}/*`
].filter((arg) => arg !== null),
{
cwd: path.dirname(setupPy),
env: {
TWINE_PASSWORD: process.env.PYPI_TOKEN,
TWINE_USERNAME: process.env.PYPI_USERNAME ? process.env.PYPI_USERNAME : '__token__'
}
}
)
}
/**
* @param pluginConfig
* @param logger
* @param stdout
* @param stderr
*/
async function publishSetupCfg(pluginConfig, logger, stdout, stderr) {
const setupPy = getOption(pluginConfig, 'setupPy')
const distDir = getOption(pluginConfig, 'distDir')
const pypiPublish = getOption(pluginConfig, 'pypiPublish')
const repoUrl = process.env.PYPI_REPO_URL ? process.env.PYPI_REPO_URL : getOption(pluginConfig, 'repoUrl')
const gpgSign = getOption(pluginConfig, 'gpgSign')
const gpgIdentity = getOption(pluginConfig, 'gpgIdentity')
if (pypiPublish) {
logger.log(`Publishing package to ${repoUrl}`)
const result = publishPackage(setupPy, distDir, repoUrl, gpgSign, gpgIdentity)
result.stdout.pipe(stdout, { end: false })
result.stderr.pipe(stderr, { end: false })
await result
} else {
logger.log('Not publishing package due to requested configuration')
}
}
/**
* @param pluginConfig
* @param logger
* @param stdout
* @param stderr
*/
async function publishPyproject(pluginConfig, logger) {
const setupPy = getOption(pluginConfig, 'setupPy')
const pypiPublish = getOption(pluginConfig, 'pypiPublish')
const repoUrl = process.env.PYPI_REPO_URL ? process.env.PYPI_REPO_URL : getOption(pluginConfig, 'repoUrl')
if (pypiPublish) {
try {
logger.log(`Publishing package to ${repoUrl}`)
await execa('poetry', ['config', 'repositories.repo', repoUrl], { cwd: path.dirname(setupPy) })
} catch {
throw new Error(`Failed to run "poetry config repositories.repo ${repoUrl}"`)
}
try {
await execa(
'poetry',
[
'publish',
'--build',
'--repository',
'repo',
'--username',
'__token__',
'--password',
process.env.PYPI_TOKEN,
'--no-interaction',
'-vvv'
],
{ cwd: path.dirname(setupPy) }
)
} catch {
throw new Error(`Failed to run "poetry config repositories.repo ${repoUrl}"`)
}
} else {
logger.log('Not publishing package due to requested configuration')
}
}
/**
* @param pluginConfig
* @param root0
* @param root0.logger
* @param root0.stdout
* @param root0.stderr
*/
export async function publish(pluginConfig, { logger, stdout, stderr }) {
const setupPy = getOption(pluginConfig, 'setupPy');
if (fs.existsSync(setupPy)) {
if (path.basename(setupPy) === "setup.cfg" || path.basename(setupPy) === "setup.py") {
await publishSetupCfg(pluginConfig, logger, stdout, stderr)
} else if (path.basename(setupPy) === "pyproject.toml") {
await publishPyproject(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`)
}
}
}