serverless-python-requirements
Version:
Serverless Python Requirements Plugin
148 lines (133 loc) • 4.27 kB
JavaScript
const fse = require('fs-extra');
const path = require('path');
const spawn = require('child-process-ext/spawn');
const { EOL } = require('os');
const semver = require('semver');
const LEGACY_PIPENV_VERSION = '2022.8.5';
async function getPipenvVersion() {
try {
const res = await spawn('pipenv', ['--version'], {
cwd: this.servicePath,
});
const stdoutBuffer =
(res.stdoutBuffer && res.stdoutBuffer.toString().trim()) || '';
const version = stdoutBuffer.split(' ')[2];
if (semver.valid(version)) {
return version;
} else {
throw new this.serverless.classes.Error(
`Unable to parse pipenv version!`,
'PYTHON_REQUIREMENTS_PIPENV_VERSION_ERROR'
);
}
} catch (e) {
const stderrBufferContent =
(e.stderrBuffer && e.stderrBuffer.toString()) || '';
if (stderrBufferContent.includes('command not found')) {
throw new this.serverless.classes.Error(
`pipenv not found! Install it according to the pipenv docs.`,
'PYTHON_REQUIREMENTS_PIPENV_NOT_FOUND'
);
} else {
throw e;
}
}
}
/**
* pipenv install
*/
async function pipfileToRequirements() {
if (
!this.options.usePipenv ||
!fse.existsSync(path.join(this.servicePath, 'Pipfile'))
) {
return;
}
let generateRequirementsProgress;
if (this.progress && this.log) {
generateRequirementsProgress = this.progress.get(
'python-generate-requirements-pipfile'
);
generateRequirementsProgress.update(
'Generating requirements.txt from Pipfile'
);
this.log.info('Generating requirements.txt from Pipfile');
} else {
this.serverless.cli.log('Generating requirements.txt from Pipfile...');
}
try {
// Get and validate pipenv version
if (this.log) {
this.log.info('Getting pipenv version');
} else {
this.serverless.cli.log('Getting pipenv version');
}
const pipenvVersion = await getPipenvVersion();
let res;
if (semver.gt(pipenvVersion, LEGACY_PIPENV_VERSION)) {
// Using new pipenv syntax ( >= 2022.8.13)
// Generate requirements from existing lock file.
// See: https://pipenv.pypa.io/en/latest/advanced/#generating-a-requirements-txt
try {
res = await spawn('pipenv', ['requirements'], {
cwd: this.servicePath,
});
} catch (e) {
const stderrBufferContent =
(e.stderrBuffer && e.stderrBuffer.toString()) || '';
if (stderrBufferContent.includes('FileNotFoundError')) {
// No previous Pipfile.lock, we will try to generate it here
if (this.log) {
this.log.warning(
'No Pipfile.lock found! Review https://pipenv.pypa.io/en/latest/pipfile/ for recommendations.'
);
} else {
this.serverless.cli.log(
'WARNING: No Pipfile.lock found! Review https://pipenv.pypa.io/en/latest/pipfile/ for recommendations.'
);
}
await spawn('pipenv', ['lock'], {
cwd: this.servicePath,
});
res = await spawn('pipenv', ['requirements'], {
cwd: this.servicePath,
});
} else {
throw e;
}
}
} else {
// Falling back to legacy pipenv syntax
res = await spawn(
'pipenv',
['lock', '--requirements', '--keep-outdated'],
{
cwd: this.servicePath,
}
);
}
fse.ensureDirSync(path.join(this.servicePath, '.serverless'));
fse.writeFileSync(
path.join(this.servicePath, '.serverless/requirements.txt'),
removeEditableFlagFromRequirementsString(res.stdoutBuffer)
);
} finally {
generateRequirementsProgress && generateRequirementsProgress.remove();
}
}
/**
*
* @param requirementBuffer
* @returns Buffer with editable flags remove
*/
function removeEditableFlagFromRequirementsString(requirementBuffer) {
const flagStr = '-e ';
const lines = requirementBuffer.toString('utf8').split(EOL);
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith(flagStr)) {
lines[i] = lines[i].substring(flagStr.length);
}
}
return Buffer.from(lines.join(EOL));
}
module.exports = { pipfileToRequirements };