Skip to content
Snippets Groups Projects
pdm.ts 3.92 KiB
Newer Older
import is from '@sindresorhus/is';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import { exec } from '../../../../util/exec';
import type { ExecOptions, ToolConstraint } from '../../../../util/exec/types';
import { getSiblingFileName, readLocalFile } from '../../../../util/fs';
import { PypiDatasource } from '../../../datasource/pypi';
import type {
  PackageDependency,
  UpdateArtifact,
  UpdateArtifactsResult,
} from '../../types';
import type { PyProject } from '../schema';
import { parseDependencyGroupRecord } from '../utils';
import type { PyProjectProcessor } from './types';

export class PdmProcessor implements PyProjectProcessor {
  process(project: PyProject, deps: PackageDependency[]): PackageDependency[] {
    const pdm = project.tool?.pdm;
    if (is.nullOrUndefined(pdm)) {
      return deps;
    }

    deps.push(
      ...parseDependencyGroupRecord(
        'tool.pdm.dev-dependencies',
        pdm['dev-dependencies']
      )
    );

    const pdmSource = pdm.source;
    if (is.nullOrUndefined(pdmSource)) {
      return deps;
    }

    // add pypi default url, if there is no source declared with the name `pypi`. https://daobook.github.io/pdm/pyproject/tool-pdm/#specify-other-sources-for-finding-packages
    const containsPyPiUrl = pdmSource.some((value) => value.name === 'pypi');
    const registryUrls: string[] = [];
    if (!containsPyPiUrl) {
      registryUrls.push(PypiDatasource.defaultURL);
    }
    for (const source of pdmSource) {
      registryUrls.push(source.url);
    }
    for (const dep of deps) {
      dep.registryUrls = registryUrls;
    }

    return deps;
  }

  async updateArtifacts(
    updateArtifact: UpdateArtifact
  ): Promise<UpdateArtifactsResult[] | null> {
    const { config, updatedDeps, packageFileName } = updateArtifact;

    const isLockFileMaintenance = config.updateType === 'lockFileMaintenance';

    // abort if no lockfile is defined
    const lockFileName = getSiblingFileName(packageFileName, 'pdm.lock');
    try {
      const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');
      if (is.nullOrUndefined(existingLockFileContent)) {
        logger.debug('No pdm.lock found');
        return null;
      }
      const pythonConstraint: ToolConstraint = {
        toolName: 'python',
        constraint: config.constraints?.python,
      };
      const pdmConstraint: ToolConstraint = {
        toolName: 'pdm',
        constraint: config.constraints?.pdm,
      };

      const execOptions: ExecOptions = {
        toolConstraints: [pythonConstraint, pdmConstraint],
      };

      // on lockFileMaintenance do not specify any packages and update the complete lock file
      // else only update specific packages
      let packageList = '';
      if (!isLockFileMaintenance) {
        packageList = ' ';
        packageList += updatedDeps.map((value) => value.packageName).join(' ');
      }
      const cmd = `pdm update${packageList}`;
      await exec(cmd, execOptions);

      // check for changes
      const fileChanges: UpdateArtifactsResult[] = [];
      const newLockContent = await readLocalFile(lockFileName, 'utf8');
      const isLockFileChanged = existingLockFileContent !== newLockContent;
      if (isLockFileChanged) {
        fileChanges.push({
          file: {
            type: 'addition',
            path: lockFileName,
            contents: newLockContent,
          },
        });
      } else {
        logger.debug('pdm.lock is unchanged');
      }

      return fileChanges.length ? fileChanges : null;
    } catch (err) {
      // istanbul ignore if
      if (err.message === TEMPORARY_ERROR) {
        throw err;
      }
      logger.debug({ err }, 'Failed to update PDM lock file');
      return [
        {
          artifactError: {
            lockFile: lockFileName,
            stderr: err.message,
          },
        },
      ];
    }
  }