Files
Jamie Tanna 5b1749983c feat: always set CI=true for subprocesses
As noted in #43761, we had recently set `CI=true` for the Renovate and
Containerbase orgs on Mend-hosted apps due to a pnpm v11 requirement:

    ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY  Aborted removal of
      modules directory due to no TTY

    If you are running pnpm in CI, set the CI environment variable to
      "true", or set "confirmModulesPurge" to "false".

Similarly in #43606, it was noted that we should do this for `mise lock`
to mark the workspace as "trusted".

Although we went with a different situation, there will be cases where
this is useful for tools being executed, for instance when we invoke `yarn`.

We also make sure to keep our documentation up-to-date as we add to the
specific overrides.

With help from Claude Sonnet and Opus to:

- perform some of the mechanical updates as part of this change
- ensure that the Docker sidecar always has `hardcodedProcessEnv` passed
  into it
- catch a needed change to `exposeAllEnv` handling

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
2026-08-24 17:21:24 +01:00

383 lines
12 KiB
TypeScript

import upath from 'upath';
import { mockExecAll } from '~test/exec-util.ts';
import { fs, partial } from '~test/util.ts';
import { GlobalConfig } from '../../../../config/global.ts';
import type {
InternalGlobalConfigOptions,
RepoGlobalConfig,
} from '../../../../config/types.ts';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages.ts';
import { logger } from '../../../../logger/index.ts';
import * as hostRules from '../../../../util/host-rules.ts';
import { getPkgReleases as _getPkgReleases } from '../../../datasource/index.ts';
import type { UpdateArtifact, UpdateArtifactsConfig } from '../../types.ts';
import { parsePyProject } from '../extract.ts';
import { depTypes } from '../utils.ts';
import { PdmProcessor } from './pdm.ts';
vi.mock('../../../../util/fs/index.ts');
vi.mock('../../../datasource/index.ts');
const getPkgReleases = vi.mocked(_getPkgReleases);
const config: UpdateArtifactsConfig = {};
const adminConfig: RepoGlobalConfig & InternalGlobalConfigOptions = {
localDir: upath.join('/tmp/github/some/repo'),
cacheDir: upath.join('/tmp/cache'),
containerbaseDir: upath.join('/tmp/cache/containerbase'),
binarySource: 'global',
};
const processor = new PdmProcessor();
describe('modules/manager/pep621/processors/pdm', () => {
describe('updateArtifacts()', () => {
it('throws TEMPORARY_ERROR', async () => {
fs.readLocalFile.mockRejectedValueOnce(new Error(TEMPORARY_ERROR));
const result = processor.updateArtifacts(
partial<UpdateArtifact>({ config: {} }),
partial(),
);
await expect(result).rejects.toThrow(TEMPORARY_ERROR);
});
it('return null if there is no lock file', async () => {
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
const updatedDeps = [{ packageName: 'dep1' }];
const result = await processor.updateArtifacts(
{
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config,
updatedDeps,
},
parsePyProject('')!,
);
expect(result).toBeNull();
});
it('return null if the lock file is unchanged', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set({
...adminConfig,
binarySource: 'docker',
dockerSidecarImage: 'ghcr.io/renovatebot/base-image',
});
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('test content');
// python
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
});
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});
const updatedDeps = [{ packageName: 'dep1' }];
const result = await processor.updateArtifacts(
{
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: { constraints: {} },
updatedDeps,
},
parsePyProject('')!,
);
expect(result).toBeNull();
expect(execSnapshots).toMatchObject([
{
cmd: 'docker pull ghcr.io/renovatebot/base-image',
},
{
cmd: 'docker ps --filter name=renovate_sidecar -aq',
},
{
cmd:
'docker run --rm --name=renovate_sidecar --label=renovate_child ' +
'-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' +
'-v "/tmp/cache":"/tmp/cache" ' +
'-e CI ' +
'-e CONTAINERBASE_CACHE_DIR ' +
'-w "/tmp/github/some/repo" ' +
'ghcr.io/renovatebot/base-image ' +
"bash -l -c '" +
'install-tool python 3.11.2 ' +
'&& ' +
'install-tool pdm v2.5.0 ' +
'&& ' +
'pdm update --no-sync --update-eager dep1' +
"'",
},
]);
});
it('returns artifact error', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockImplementationOnce(() => {
throw new Error('test error');
});
const updatedDeps = [{ packageName: 'dep1' }];
const result = await processor.updateArtifacts(
{
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
},
parsePyProject('')!,
);
expect(result).toEqual([
{ artifactError: { fileName: 'pdm.lock', stderr: 'test error' } },
]);
expect(execSnapshots).toEqual([]);
});
it('return update dep update', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set(adminConfig);
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('changed test content');
// python
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
});
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});
const updatedDeps = [
{
packageName: 'dep1',
depType: depTypes.dependencies,
},
{ packageName: 'dep2', depType: depTypes.dependencies },
{
packageName: 'dep3',
managerData: { depGroup: 'group1' },
depType: depTypes.optionalDependencies,
},
{
packageName: 'dep4',
depType: depTypes.optionalDependencies,
managerData: { depGroup: 'group1' },
},
{
packageName: 'dep5',
depType: depTypes.pdmDevDependencies,
managerData: { depGroup: 'group2' },
},
{
packageName: 'dep6',
depType: depTypes.pdmDevDependencies,
managerData: { depGroup: 'group2' },
},
{
packageName: 'dep7',
depType: depTypes.pdmDevDependencies,
managerData: { depGroup: 'group3' },
},
{
packageName: 'dep8',
depType: depTypes.pdmDevDependencies,
managerData: { depGroup: 'group3' },
},
{ packageName: 'dep9', depType: depTypes.buildSystemRequires },
{
packageName: 'dep10',
depType: depTypes.dependencyGroups,
managerData: { depGroup: 'dev' },
},
];
const result = await processor.updateArtifacts(
{
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
},
parsePyProject('')!,
);
expect(result).toEqual([
{
file: {
contents: 'changed test content',
path: 'pdm.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pdm update --no-sync --update-eager dep1 dep2',
},
{
cmd: 'pdm update --no-sync --update-eager -G group1 dep3 dep4',
},
{
cmd: 'pdm update --no-sync --update-eager -dG group2 dep5 dep6',
},
{
cmd: 'pdm update --no-sync --update-eager -dG group3 dep7 dep8',
},
{
cmd: 'pdm update --no-sync --update-eager -dG dev dep10',
},
]);
});
it('discard dependencies if the devGroup is missing', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set(adminConfig);
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('test content');
// python
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
});
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});
const updatedDeps = [
{
packageName: 'dep3',
depType: depTypes.optionalDependencies,
},
{
packageName: 'dep5',
depType: depTypes.pdmDevDependencies,
},
{
packageName: 'dep10',
depType: depTypes.dependencyGroups,
},
];
const result = await processor.updateArtifacts(
{
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
},
parsePyProject('')!,
);
expect(result).toBeNull();
expect(execSnapshots).toEqual([]);
expect(logger.once.warn).toHaveBeenCalledTimes(3);
});
it('return update on lockfileMaintenance', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set(adminConfig);
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('changed test content');
// python
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
});
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});
const result = await processor.updateArtifacts(
{
packageFileName: 'folder/pyproject.toml',
newPackageFileContent: '',
config: {
isLockFileMaintenance: true,
},
updatedDeps: [],
},
parsePyProject('')!,
);
expect(result).toEqual([
{
file: {
contents: 'changed test content',
path: 'pdm.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pdm update --no-sync --update-eager',
options: {
cwd: '/tmp/github/some/repo/folder',
},
},
]);
});
it('sets Git environment variables', async () => {
hostRules.add({
matchHost: 'https://example.com',
username: 'user',
password: 'pass',
});
const execSnapshots = mockExecAll();
GlobalConfig.set(adminConfig);
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('changed test content');
// python
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
});
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});
const result = await processor.updateArtifacts(
{
packageFileName: 'folder/pyproject.toml',
newPackageFileContent: '',
config: {
isLockFileMaintenance: true,
},
updatedDeps: [],
},
parsePyProject('')!,
);
expect(result).toEqual([
{
file: {
contents: 'changed test content',
path: 'pdm.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pdm update --no-sync --update-eager',
options: {
cwd: '/tmp/github/some/repo/folder',
env: {
GIT_CONFIG_COUNT: '3',
GIT_CONFIG_KEY_0: 'url.https://user:pass@example.com/.insteadOf',
GIT_CONFIG_KEY_1: 'url.https://user:pass@example.com/.insteadOf',
GIT_CONFIG_KEY_2: 'url.https://user:pass@example.com/.insteadOf',
GIT_CONFIG_VALUE_0: 'ssh://git@example.com/',
GIT_CONFIG_VALUE_1: 'git@example.com:',
GIT_CONFIG_VALUE_2: 'https://example.com/',
},
},
},
]);
});
});
});