mirror of
https://github.com/renovatebot/renovate.git
synced 2025-05-13 08:01:49 +00:00
450 lines
14 KiB
TypeScript
450 lines
14 KiB
TypeScript
import { codeBlock } from 'common-tags';
|
|
import { GlobalConfig } from '../../../../config/global';
|
|
import * as yaml from '../../../../util/yaml';
|
|
import type { PackageFile } from '../../types';
|
|
import type { NpmManagerData } from '../types';
|
|
import {
|
|
detectPnpmWorkspaces,
|
|
extractPnpmFilters,
|
|
extractPnpmWorkspaceFile,
|
|
findPnpmWorkspace,
|
|
getPnpmLock,
|
|
} from './pnpm';
|
|
import { Fixtures } from '~test/fixtures';
|
|
import { fs, getFixturePath, logger, partial } from '~test/util';
|
|
|
|
vi.mock('../../../../util/fs');
|
|
|
|
describe('modules/manager/npm/extract/pnpm', () => {
|
|
beforeAll(() => {
|
|
GlobalConfig.set({ localDir: getFixturePath('pnpm-monorepo/', '..') });
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('.extractPnpmFilters()', () => {
|
|
it('detects errors in pnpm-workspace.yml file structure', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce('p!!!ckages:\n - "packages/*"');
|
|
|
|
const workSpaceFilePath = getFixturePath(
|
|
'pnpm-monorepo/pnpm-workspace.yml',
|
|
'..',
|
|
);
|
|
const res = await extractPnpmFilters(workSpaceFilePath);
|
|
expect(res).toBeUndefined();
|
|
expect(logger.logger.trace).toHaveBeenCalledWith(
|
|
{
|
|
fileName: expect.any(String),
|
|
},
|
|
'Failed to find required "packages" array in pnpm-workspace.yaml',
|
|
);
|
|
});
|
|
|
|
it('detects errors when opening pnpm-workspace.yml file', async () => {
|
|
vi.spyOn(yaml, 'parseSingleYaml').mockImplementationOnce(() => {
|
|
throw new Error();
|
|
});
|
|
|
|
const res = await extractPnpmFilters('pnpm-workspace.yml');
|
|
expect(res).toBeUndefined();
|
|
expect(logger.logger.trace).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
fileName: expect.any(String),
|
|
err: expect.anything(),
|
|
}),
|
|
'Failed to parse pnpm-workspace.yaml',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('.findPnpmWorkspace()', () => {
|
|
it('detects missing pnpm-workspace.yaml', async () => {
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce(null);
|
|
|
|
const packageFile = 'package.json';
|
|
const res = await findPnpmWorkspace(packageFile);
|
|
expect(res).toBeNull();
|
|
expect(logger.logger.trace).toHaveBeenCalledWith(
|
|
expect.objectContaining({ packageFile }),
|
|
'Failed to locate pnpm-workspace.yaml in a parent directory.',
|
|
);
|
|
});
|
|
|
|
it('detects missing pnpm-lock.yaml when pnpm-workspace.yaml was already found', async () => {
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('pnpm-workspace.yaml');
|
|
fs.localPathExists.mockResolvedValueOnce(false);
|
|
|
|
const packageFile = 'package.json';
|
|
const res = await findPnpmWorkspace(packageFile);
|
|
expect(res).toBeNull();
|
|
expect(logger.logger.trace).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceYamlPath: 'pnpm-workspace.yaml',
|
|
packageFile,
|
|
}),
|
|
'Failed to find a pnpm-lock.yaml sibling for the workspace.',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('.detectPnpmWorkspaces()', () => {
|
|
beforeEach(async () => {
|
|
const realFs = await vi.importActual<typeof fs>('../../../../util/fs');
|
|
|
|
// The real implementations of these functions are used for this block;
|
|
// they do static path manipulation.
|
|
fs.findLocalSiblingOrParent.mockImplementation(
|
|
realFs.findLocalSiblingOrParent,
|
|
);
|
|
fs.getSiblingFileName.mockImplementation(realFs.getSiblingFileName);
|
|
|
|
// Falls through to reading from the fixture path defined in GlobalConfig
|
|
// at the top of this file
|
|
fs.readLocalFile.mockImplementation(realFs.readLocalFile);
|
|
});
|
|
|
|
it('uses pnpm workspaces', async () => {
|
|
fs.localPathExists.mockResolvedValue(true);
|
|
const packageFiles = partial<PackageFile<NpmManagerData>>([
|
|
{
|
|
packageFile: 'package.json',
|
|
managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' },
|
|
},
|
|
{
|
|
packageFile: 'nested-packages/group/a/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/nested-group-a',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'nested-packages/group/b/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/nested-group-b',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'non-nested-packages/a/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/non-nested-a',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'non-nested-packages/b/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/non-nested-b',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'solo-package/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/solo',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'solo-package-leading-dot-slash/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/solo-leading-dot-slash',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'solo-package-leading-double-dot-slash/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/solo-leading-double-dot-slash',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'solo-package-trailing-slash/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/solo-trailing-slash',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'test/test-package/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/test-package',
|
|
},
|
|
},
|
|
{
|
|
packageFile: 'tests/test-package2/package.json',
|
|
managerData: {
|
|
pnpmShrinkwrap: undefined,
|
|
packageJsonName: '@demo/test-package2',
|
|
},
|
|
},
|
|
]);
|
|
|
|
await detectPnpmWorkspaces(packageFiles);
|
|
expect(packageFiles).toMatchSnapshot();
|
|
expect(
|
|
packageFiles.every(
|
|
(packageFile) =>
|
|
packageFile.managerData?.pnpmShrinkwrap !== undefined,
|
|
),
|
|
).toBeTrue();
|
|
});
|
|
|
|
it('skips when pnpm shrinkwrap file has already been provided', async () => {
|
|
const packageFiles = [
|
|
{
|
|
packageFile: 'package.json',
|
|
managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' },
|
|
},
|
|
];
|
|
|
|
await detectPnpmWorkspaces(packageFiles);
|
|
expect(packageFiles).toEqual([
|
|
{
|
|
packageFile: 'package.json',
|
|
managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('filters none matching packages', async () => {
|
|
fs.localPathExists.mockResolvedValue(true);
|
|
const packageFiles = [
|
|
{
|
|
packageFile: 'package.json',
|
|
managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' },
|
|
},
|
|
{
|
|
packageFile: 'nested-packages/group/a/package.json',
|
|
packageJsonName: '@demo/nested-group-a',
|
|
managerData: { pnpmShrinkwrap: undefined },
|
|
},
|
|
{
|
|
packageFile: 'not-matching/b/package.json',
|
|
packageJsonName: '@not-matching/b',
|
|
managerData: { pnpmShrinkwrap: undefined },
|
|
},
|
|
];
|
|
|
|
await detectPnpmWorkspaces(packageFiles);
|
|
expect(packageFiles).toEqual([
|
|
{
|
|
packageFile: 'package.json',
|
|
managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' },
|
|
},
|
|
{
|
|
packageFile: 'nested-packages/group/a/package.json',
|
|
packageJsonName: '@demo/nested-group-a',
|
|
managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' },
|
|
},
|
|
{
|
|
packageFile: 'not-matching/b/package.json',
|
|
packageJsonName: '@not-matching/b',
|
|
managerData: { pnpmShrinkwrap: undefined },
|
|
},
|
|
]);
|
|
expect(
|
|
packageFiles.find(
|
|
(packageFile) =>
|
|
packageFile.packageFile === 'not-matching/b/package.json',
|
|
)?.managerData.pnpmShrinkwrap,
|
|
).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('.getPnpmLock()', () => {
|
|
it('returns empty if failed to parse', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce(undefined as never);
|
|
const res = await getPnpmLock('package.json');
|
|
expect(res.lockedVersionsWithPath).toBeUndefined();
|
|
});
|
|
|
|
it('extracts version from monorepo', async () => {
|
|
const plocktest1Lock = Fixtures.get('pnpm-monorepo/pnpm-lock.yaml', '..');
|
|
fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock);
|
|
const res = await getPnpmLock('package.json');
|
|
expect(Object.keys(res.lockedVersionsWithPath!)).toHaveLength(11);
|
|
});
|
|
|
|
it('extracts version from normal repo', async () => {
|
|
const plocktest1Lock = Fixtures.get(
|
|
'lockfile-parsing/pnpm-lock.yaml',
|
|
'..',
|
|
);
|
|
fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock);
|
|
const res = await getPnpmLock('package.json');
|
|
expect(Object.keys(res.lockedVersionsWithPath!)).toHaveLength(1);
|
|
});
|
|
|
|
it('extracts version from catalogs', async () => {
|
|
const lockfileContent = codeBlock`
|
|
lockfileVersion: '9.0'
|
|
|
|
settings:
|
|
autoInstallPeers: true
|
|
excludeLinksFromLockfile: false
|
|
|
|
catalogs:
|
|
default:
|
|
react:
|
|
specifier: ^18
|
|
version: 18.3.1
|
|
|
|
importers:
|
|
|
|
.:
|
|
dependencies:
|
|
react:
|
|
specifier: 'catalog:'
|
|
version: 18.3.1
|
|
|
|
packages:
|
|
|
|
js-tokens@4.0.0:
|
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
|
|
|
loose-envify@1.4.0:
|
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
|
hasBin: true
|
|
|
|
react@18.3.1:
|
|
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
|
engines: {node: '>=0.10.0'}
|
|
|
|
snapshots:
|
|
|
|
js-tokens@4.0.0: {}
|
|
|
|
loose-envify@1.4.0:
|
|
dependencies:
|
|
js-tokens: 4.0.0
|
|
|
|
react@18.3.1:
|
|
dependencies:
|
|
loose-envify: 1.4.0
|
|
`;
|
|
fs.readLocalFile.mockResolvedValueOnce(lockfileContent);
|
|
const res = await getPnpmLock('package.json');
|
|
expect(Object.keys(res.lockedVersionsWithCatalog!)).toHaveLength(1);
|
|
});
|
|
|
|
it('returns empty if no deps', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce('{}');
|
|
const res = await getPnpmLock('package.json');
|
|
expect(res.lockedVersionsWithPath).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('.extractPnpmWorkspaceFile()', () => {
|
|
it('handles empty catalog entries', async () => {
|
|
expect(
|
|
await extractPnpmWorkspaceFile(
|
|
{ catalog: {}, catalogs: {} },
|
|
'pnpm-workspace.yaml',
|
|
),
|
|
).toMatchObject({
|
|
deps: [],
|
|
});
|
|
});
|
|
|
|
it('parses valid pnpm-workspace.yaml file', async () => {
|
|
expect(
|
|
await extractPnpmWorkspaceFile(
|
|
{
|
|
catalog: {
|
|
react: '18.3.0',
|
|
},
|
|
catalogs: {
|
|
react17: {
|
|
react: '17.0.2',
|
|
},
|
|
},
|
|
},
|
|
'pnpm-workspace.yaml',
|
|
),
|
|
).toMatchObject({
|
|
deps: [
|
|
{
|
|
currentValue: '18.3.0',
|
|
datasource: 'npm',
|
|
depName: 'react',
|
|
depType: 'pnpm.catalog.default',
|
|
prettyDepType: 'pnpm.catalog.default',
|
|
},
|
|
{
|
|
currentValue: '17.0.2',
|
|
datasource: 'npm',
|
|
depName: 'react',
|
|
depType: 'pnpm.catalog.react17',
|
|
prettyDepType: 'pnpm.catalog.react17',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('finds relevant lockfile', async () => {
|
|
const lockfileContent = codeBlock`
|
|
lockfileVersion: '9.0'
|
|
|
|
catalogs:
|
|
default:
|
|
react:
|
|
specifier: 18.3.1
|
|
version: 18.3.1
|
|
|
|
importers:
|
|
|
|
.:
|
|
dependencies:
|
|
react:
|
|
specifier: 'catalog:'
|
|
version: 18.3.1
|
|
|
|
packages:
|
|
|
|
js-tokens@4.0.0:
|
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
|
|
|
loose-envify@1.4.0:
|
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
|
hasBin: true
|
|
|
|
react@18.3.1:
|
|
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
|
engines: {node: '>=0.10.0'}
|
|
|
|
snapshots:
|
|
|
|
js-tokens@4.0.0: {}
|
|
|
|
loose-envify@1.4.0:
|
|
dependencies:
|
|
js-tokens: 4.0.0
|
|
|
|
react@18.3.1:
|
|
dependencies:
|
|
loose-envify: 1.4.0
|
|
`;
|
|
fs.readLocalFile.mockResolvedValueOnce(lockfileContent);
|
|
fs.getSiblingFileName.mockReturnValueOnce('pnpm-lock.yaml');
|
|
expect(
|
|
await extractPnpmWorkspaceFile(
|
|
{
|
|
catalog: {
|
|
react: '18.3.1',
|
|
},
|
|
},
|
|
'pnpm-workspace.yaml',
|
|
),
|
|
).toMatchObject({
|
|
managerData: {
|
|
pnpmShrinkwrap: 'pnpm-lock.yaml',
|
|
},
|
|
});
|
|
});
|
|
});
|
|
});
|