feat: pnpm shrinkwrap support (#1392)

This feature adds support for pnpm shrinkwrap.yaml files.

Closes #1391
This commit is contained in:
Rhys Arkins 2018-01-15 16:55:33 +01:00 committed by GitHub
parent 400ca57398
commit 23e217991c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1844 additions and 62 deletions

View file

@ -146,6 +146,17 @@ async function resolvePackageFiles(config) {
); );
packageFile.packageLock = packageLockFileName; packageFile.packageLock = packageLockFileName;
} }
const shrinkwrapFileName = upath.join(
path.dirname(packageFile.packageFile),
'shrinkwrap.yaml'
);
if (fileList.includes(shrinkwrapFileName)) {
logger.debug(
{ packageFile: packageFile.packageFile },
'Found shrinkwrap.yaml'
);
packageFile.shrinkwrapYaml = shrinkwrapFileName;
}
return packageFile; return packageFile;
} else if (packageFile.packageFile.endsWith('package.js')) { } else if (packageFile.packageFile.endsWith('package.js')) {
// meteor // meteor

View file

@ -3,10 +3,12 @@ const path = require('path');
const upath = require('upath'); const upath = require('upath');
const npm = require('./npm'); const npm = require('./npm');
const yarn = require('./yarn'); const yarn = require('./yarn');
const pnpm = require('./pnpm');
module.exports = { module.exports = {
hasPackageLock, hasPackageLock,
hasYarnLock, hasYarnLock,
hasShrinkwrapYaml,
determineLockFileDirs, determineLockFileDirs,
writeExistingFiles, writeExistingFiles,
writeUpdatedPackageFiles, writeUpdatedPackageFiles,
@ -45,9 +47,26 @@ function hasYarnLock(config, packageFile) {
throw new Error(`hasYarnLock cannot find ${packageFile}`); throw new Error(`hasYarnLock cannot find ${packageFile}`);
} }
function hasShrinkwrapYaml(config, packageFile) {
logger.trace(
{ packageFiles: config.packageFiles, packageFile },
'hasShrinkwrapYaml'
);
for (const p of config.packageFiles) {
if (p.packageFile === packageFile) {
if (p.shrinkwrapYaml) {
return true;
}
return false;
}
}
throw new Error(`hasShrinkwrapYaml cannot find ${packageFile}`);
}
function determineLockFileDirs(config) { function determineLockFileDirs(config) {
const packageLockFileDirs = []; const packageLockFileDirs = [];
const yarnLockFileDirs = []; const yarnLockFileDirs = [];
const shrinkwrapYamlDirs = [];
for (const upgrade of config.upgrades) { for (const upgrade of config.upgrades) {
if (upgrade.type === 'lockFileMaintenance') { if (upgrade.type === 'lockFileMaintenance') {
@ -60,8 +79,11 @@ function determineLockFileDirs(config) {
if (packageFile.packageLock) { if (packageFile.packageLock) {
packageLockFileDirs.push(dirname); packageLockFileDirs.push(dirname);
} }
if (packageFile.shrinkwrapYaml) {
shrinkwrapYamlDirs.push(dirname);
}
} }
return { packageLockFileDirs, yarnLockFileDirs }; return { packageLockFileDirs, yarnLockFileDirs, shrinkwrapYamlDirs };
} }
} }
@ -72,6 +94,9 @@ function determineLockFileDirs(config) {
if (module.exports.hasPackageLock(config, packageFile.name)) { if (module.exports.hasPackageLock(config, packageFile.name)) {
packageLockFileDirs.push(path.dirname(packageFile.name)); packageLockFileDirs.push(path.dirname(packageFile.name));
} }
if (module.exports.hasShrinkwrapYaml(config, packageFile.name)) {
shrinkwrapYamlDirs.push(path.dirname(packageFile.name));
}
} }
// If yarn workspaces are in use, then we need to generate yarn.lock from the workspaces dir // If yarn workspaces are in use, then we need to generate yarn.lock from the workspaces dir
@ -87,7 +112,7 @@ function determineLockFileDirs(config) {
} }
} }
return { yarnLockFileDirs, packageLockFileDirs }; return { yarnLockFileDirs, packageLockFileDirs, shrinkwrapYamlDirs };
} }
async function writeExistingFiles(config) { async function writeExistingFiles(config) {
@ -197,6 +222,20 @@ async function writeExistingFiles(config) {
} else { } else {
await fs.remove(upath.join(basedir, 'yarn.lock')); await fs.remove(upath.join(basedir, 'yarn.lock'));
} }
// TODO: Update the below with this once https://github.com/pnpm/pnpm/issues/992 is fixed
const pnpmBug992 = true;
// istanbul ignore next
if (
packageFile.shrinkwrapYaml &&
config.type !== 'lockFileMaintenance' &&
!pnpmBug992
) {
logger.debug(`Writing shrinkwrap.yaml to ${basedir}`);
const shrinkwrap = await platform.getFile(packageFile.shrinkwrapYaml);
await fs.outputFile(upath.join(basedir, 'shrinkwrap.yaml'), shrinkwrap);
} else {
await fs.remove(upath.join(basedir, 'shrinkwrap.yaml'));
}
} }
} }
@ -316,5 +355,34 @@ async function getUpdatedLockFiles(config) {
} }
} }
} }
for (const lockFileDir of dirs.shrinkwrapYamlDirs) {
logger.debug(`Generating shrinkwrap.yaml for ${lockFileDir}`);
const lockFileName = upath.join(lockFileDir, 'shrinkwrap.yaml');
const res = await pnpm.generateLockFile(
upath.join(config.tmpDir.path, lockFileDir)
);
if (res.error) {
lockFileErrors.push({
lockFile: lockFileName,
stderr: res.stderr,
});
} else {
const existingContent = await platform.getFile(
lockFileName,
config.parentBranch
);
if (res.lockFile !== existingContent) {
logger.debug('shrinkwrap.yaml needs updating');
updatedLockFiles.push({
name: lockFileName,
contents: res.lockFile,
});
} else {
logger.debug("shrinkwrap.yaml hasn't changed");
}
}
}
return { lockFileErrors, updatedLockFiles }; return { lockFileErrors, updatedLockFiles };
} }

View file

@ -0,0 +1,85 @@
const fs = require('fs-extra');
const upath = require('upath');
const { getInstalledPath } = require('get-installed-path');
const { exec } = require('child-process-promise');
module.exports = {
generateLockFile,
};
async function generateLockFile(tmpDir) {
logger.debug(`Spawning pnpm install to create ${tmpDir}/shrinkwrap.yaml`);
let lockFile = null;
let stdout;
let stderr;
try {
const startTime = process.hrtime();
let cmd;
try {
// See if renovate is installed locally
const installedPath = upath.join(
await getInstalledPath('pnpm', {
local: true,
}),
'lib/bin/pnpm.js'
);
cmd = `node ${installedPath}`;
} catch (localerr) {
logger.debug('No locally installed pnpm found');
// Look inside globally installed renovate
try {
const renovateLocation = await getInstalledPath('renovate');
const installedPath = upath.join(
await getInstalledPath('pnpm', {
local: true,
cwd: renovateLocation,
}),
'lib/bin/pnpm.js'
);
cmd = `node ${installedPath}`;
} catch (nestederr) {
logger.debug('Could not find globally nested pnpm');
// look for global pnpm
try {
const installedPath = upath.join(
await getInstalledPath('pnpm'),
'lib/bin/pnpm.js'
);
cmd = `node ${installedPath}`;
} catch (globalerr) {
logger.warn('Could not find globally installed pnpm');
cmd = 'pnpm';
}
}
}
logger.debug(`Using pnpm: ${cmd}`);
cmd += ' install';
cmd += ' --shrinkwrap-only';
cmd += ' --ignore-scripts';
cmd += ' --ignore-pnpmfile';
// TODO: Switch to native util.promisify once using only node 8
({ stdout, stderr } = await exec(cmd, {
cwd: tmpDir,
shell: true,
env: { NODE_ENV: 'dev', PATH: process.env.PATH },
}));
logger.debug(`pnpm stdout:\n${stdout}`);
logger.debug(`pnpm stderr:\n${stderr}`);
const duration = process.hrtime(startTime);
const seconds = Math.round(duration[0] + duration[1] / 1e9);
lockFile = await fs.readFile(upath.join(tmpDir, 'shrinkwrap.yaml'), 'utf8');
logger.info(
{ seconds, type: 'shrinkwrap.yaml', stdout, stderr },
'Generated lockfile'
);
} catch (err) /* istanbul ignore next */ {
logger.info(
{
err,
},
'pnpm install error'
);
return { error: true, stderr: err.stderr };
}
return { lockFile };
}

View file

@ -71,6 +71,7 @@
"npm": "5.6.0", "npm": "5.6.0",
"openpgp": "2.6.1", "openpgp": "2.6.1",
"parse-link-header": "1.0.1", "parse-link-header": "1.0.1",
"pnpm": "1.29.1",
"registry-auth-token": "3.3.1", "registry-auth-token": "3.3.1",
"root-require": "0.3.1", "root-require": "0.3.1",
"semver": "5.4.1", "semver": "5.4.1",

View file

@ -14,6 +14,7 @@ Array [
"npmrc": "npmrc", "npmrc": "npmrc",
"packageFile": "package.json", "packageFile": "package.json",
"packageLock": "package-lock.json", "packageLock": "package-lock.json",
"shrinkwrapYaml": "shrinkwrap.yaml",
"yarnLock": "yarn.lock", "yarnLock": "yarn.lock",
"yarnrc": "yarnrc", "yarnrc": "yarnrc",
}, },

View file

@ -71,6 +71,7 @@ describe('manager/resolve', () => {
platform.getFileList.mockReturnValueOnce([ platform.getFileList.mockReturnValueOnce([
'yarn.lock', 'yarn.lock',
'package-lock.json', 'package-lock.json',
'shrinkwrap.yaml',
]); ]);
platform.getFile.mockReturnValueOnce('{"name": "package.json"}'); platform.getFile.mockReturnValueOnce('{"name": "package.json"}');
platform.getFile.mockReturnValueOnce('npmrc'); platform.getFile.mockReturnValueOnce('npmrc');

View file

@ -5,6 +5,9 @@ Object {
"packageLockFileDirs": Array [ "packageLockFileDirs": Array [
"backend", "backend",
], ],
"shrinkwrapYamlDirs": Array [
"frontend",
],
"yarnLockFileDirs": Array [ "yarnLockFileDirs": Array [
".", ".",
], ],
@ -16,6 +19,9 @@ Object {
"packageLockFileDirs": Array [ "packageLockFileDirs": Array [
"backend", "backend",
], ],
"shrinkwrapYamlDirs": Array [
"frontend",
],
"yarnLockFileDirs": Array [ "yarnLockFileDirs": Array [
".", ".",
], ],
@ -25,6 +31,7 @@ Object {
exports[`workers/branch/lock-files determineLockFileDirs returns root directory if using yarn workspaces 1`] = ` exports[`workers/branch/lock-files determineLockFileDirs returns root directory if using yarn workspaces 1`] = `
Object { Object {
"packageLockFileDirs": Array [], "packageLockFileDirs": Array [],
"shrinkwrapYamlDirs": Array [],
"yarnLockFileDirs": Array [ "yarnLockFileDirs": Array [
".", ".",
], ],

View file

@ -5,10 +5,12 @@ const upath = require('upath');
const npm = require('../../../lib/workers/branch/npm'); const npm = require('../../../lib/workers/branch/npm');
const yarn = require('../../../lib/workers/branch/yarn'); const yarn = require('../../../lib/workers/branch/yarn');
const pnpm = require('../../../lib/workers/branch/pnpm');
const { const {
hasPackageLock, hasPackageLock,
hasYarnLock, hasYarnLock,
hasShrinkwrapYaml,
determineLockFileDirs, determineLockFileDirs,
writeExistingFiles, writeExistingFiles,
writeUpdatedPackageFiles, writeUpdatedPackageFiles,
@ -110,6 +112,53 @@ describe('workers/branch/lock-files', () => {
expect(e).toBeDefined(); expect(e).toBeDefined();
}); });
}); });
describe('hasShrinkWrapYaml', () => {
let config;
beforeEach(() => {
config = {
...defaultConfig,
};
});
it('returns true if found and true', () => {
config.packageFiles = [
{
packageFile: 'package.json',
shrinkwrapYaml: 'some shrinkwrap',
},
];
expect(hasShrinkwrapYaml(config, 'package.json')).toBe(true);
});
it('returns false if found and false', () => {
config.packageFiles = [
{
packageFile: 'package.json',
shrinkwrapYaml: 'some shrinkwrap',
},
{
packageFile: 'backend/package.json',
},
];
expect(hasShrinkwrapYaml(config, 'backend/package.json')).toBe(false);
});
it('throws error if not found', () => {
config.packageFiles = [
{
packageFile: 'package.json',
shrinkwrapYaml: 'some package lock',
},
{
packageFile: 'backend/package.json',
},
];
let e;
try {
hasShrinkwrapYaml(config, 'frontend/package.json');
} catch (err) {
e = err;
}
expect(e).toBeDefined();
});
});
describe('determineLockFileDirs', () => { describe('determineLockFileDirs', () => {
let config; let config;
beforeEach(() => { beforeEach(() => {
@ -124,6 +173,10 @@ describe('workers/branch/lock-files', () => {
packageFile: 'backend/package.json', packageFile: 'backend/package.json',
packageLock: 'some package lock', packageLock: 'some package lock',
}, },
{
packageFile: 'frontend/package.json',
shrinkwrapYaml: 'some package lock',
},
], ],
}; };
}); });
@ -143,6 +196,10 @@ describe('workers/branch/lock-files', () => {
name: 'backend/package.json', name: 'backend/package.json',
contents: 'some contents', contents: 'some contents',
}, },
{
name: 'frontend/package.json',
contents: 'some contents',
},
]; ];
const res = determineLockFileDirs(config); const res = determineLockFileDirs(config);
expect(res).toMatchSnapshot(); expect(res).toMatchSnapshot();
@ -207,7 +264,7 @@ describe('workers/branch/lock-files', () => {
]; ];
await writeExistingFiles(config); await writeExistingFiles(config);
expect(fs.outputFile.mock.calls).toHaveLength(6); expect(fs.outputFile.mock.calls).toHaveLength(6);
expect(fs.remove.mock.calls).toHaveLength(4); expect(fs.remove.mock.calls).toHaveLength(6);
}); });
it('writes package.json of local lib', async () => { it('writes package.json of local lib', async () => {
const renoPath = upath.join(__dirname, '../../../'); const renoPath = upath.join(__dirname, '../../../');
@ -230,7 +287,7 @@ describe('workers/branch/lock-files', () => {
platform.getFile.mockReturnValue('some lock file contents'); platform.getFile.mockReturnValue('some lock file contents');
await writeExistingFiles(config); await writeExistingFiles(config);
expect(fs.outputFile.mock.calls).toHaveLength(4); expect(fs.outputFile.mock.calls).toHaveLength(4);
expect(fs.remove.mock.calls).toHaveLength(0); expect(fs.remove.mock.calls).toHaveLength(1);
}); });
it('Try to write package.json of local lib, but file not found', async () => { it('Try to write package.json of local lib, but file not found', async () => {
const renoPath = upath.join(__dirname, '../../../'); const renoPath = upath.join(__dirname, '../../../');
@ -253,7 +310,7 @@ describe('workers/branch/lock-files', () => {
platform.getFile.mockReturnValue(null); platform.getFile.mockReturnValue(null);
await writeExistingFiles(config); await writeExistingFiles(config);
expect(fs.outputFile.mock.calls).toHaveLength(3); expect(fs.outputFile.mock.calls).toHaveLength(3);
expect(fs.remove.mock.calls).toHaveLength(0); expect(fs.remove.mock.calls).toHaveLength(1);
}); });
it('detect malicious intent (error config in package.json) local lib is not in the repo', async () => { it('detect malicious intent (error config in package.json) local lib is not in the repo', async () => {
const renoPath = upath.join(__dirname, '../../../'); const renoPath = upath.join(__dirname, '../../../');
@ -276,7 +333,7 @@ describe('workers/branch/lock-files', () => {
platform.getFile.mockReturnValue(null); platform.getFile.mockReturnValue(null);
await writeExistingFiles(config); await writeExistingFiles(config);
expect(fs.outputFile.mock.calls).toHaveLength(3); expect(fs.outputFile.mock.calls).toHaveLength(3);
expect(fs.remove.mock.calls).toHaveLength(0); expect(fs.remove.mock.calls).toHaveLength(1);
}); });
}); });
describe('writeUpdatedPackageFiles', () => { describe('writeUpdatedPackageFiles', () => {
@ -336,6 +393,10 @@ describe('workers/branch/lock-files', () => {
yarn.generateLockFile.mockReturnValue({ yarn.generateLockFile.mockReturnValue({
lockFile: 'some lock file contents', lockFile: 'some lock file contents',
}); });
pnpm.generateLockFile = jest.fn();
pnpm.generateLockFile.mockReturnValue({
lockFile: 'some lock file contents',
});
lockFiles.determineLockFileDirs = jest.fn(); lockFiles.determineLockFileDirs = jest.fn();
}); });
afterEach(() => { afterEach(() => {
@ -353,6 +414,7 @@ describe('workers/branch/lock-files', () => {
lockFiles.determineLockFileDirs.mockReturnValueOnce({ lockFiles.determineLockFileDirs.mockReturnValueOnce({
packageLockFileDirs: [], packageLockFileDirs: [],
yarnLockFileDirs: [], yarnLockFileDirs: [],
shrinkwrapYamlDirs: [],
}); });
const res = await getUpdatedLockFiles(config); const res = await getUpdatedLockFiles(config);
expect(res).toMatchSnapshot(); expect(res).toMatchSnapshot();
@ -363,6 +425,7 @@ describe('workers/branch/lock-files', () => {
lockFiles.determineLockFileDirs.mockReturnValueOnce({ lockFiles.determineLockFileDirs.mockReturnValueOnce({
packageLockFileDirs: ['a', 'b'], packageLockFileDirs: ['a', 'b'],
yarnLockFileDirs: ['c', 'd'], yarnLockFileDirs: ['c', 'd'],
shrinkwrapYamlDirs: ['e'],
}); });
const res = await getUpdatedLockFiles(config); const res = await getUpdatedLockFiles(config);
expect(res).toMatchSnapshot(); expect(res).toMatchSnapshot();
@ -370,17 +433,19 @@ describe('workers/branch/lock-files', () => {
expect(res.updatedLockFiles).toHaveLength(0); expect(res.updatedLockFiles).toHaveLength(0);
expect(npm.generateLockFile.mock.calls).toHaveLength(2); expect(npm.generateLockFile.mock.calls).toHaveLength(2);
expect(yarn.generateLockFile.mock.calls).toHaveLength(2); expect(yarn.generateLockFile.mock.calls).toHaveLength(2);
expect(platform.getFile.mock.calls).toHaveLength(4); expect(platform.getFile.mock.calls).toHaveLength(5);
}); });
it('sets error if receiving null', async () => { it('sets error if receiving null', async () => {
lockFiles.determineLockFileDirs.mockReturnValueOnce({ lockFiles.determineLockFileDirs.mockReturnValueOnce({
packageLockFileDirs: ['a', 'b'], packageLockFileDirs: ['a', 'b'],
yarnLockFileDirs: ['c', 'd'], yarnLockFileDirs: ['c', 'd'],
shrinkwrapYamlDirs: ['e'],
}); });
npm.generateLockFile.mockReturnValueOnce({ error: true }); npm.generateLockFile.mockReturnValueOnce({ error: true });
yarn.generateLockFile.mockReturnValueOnce({ error: true }); yarn.generateLockFile.mockReturnValueOnce({ error: true });
pnpm.generateLockFile.mockReturnValueOnce({ error: true });
const res = await getUpdatedLockFiles(config); const res = await getUpdatedLockFiles(config);
expect(res.lockFileErrors).toHaveLength(2); expect(res.lockFileErrors).toHaveLength(3);
expect(res.updatedLockFiles).toHaveLength(0); expect(res.updatedLockFiles).toHaveLength(0);
expect(npm.generateLockFile.mock.calls).toHaveLength(2); expect(npm.generateLockFile.mock.calls).toHaveLength(2);
expect(yarn.generateLockFile.mock.calls).toHaveLength(2); expect(yarn.generateLockFile.mock.calls).toHaveLength(2);
@ -390,15 +455,17 @@ describe('workers/branch/lock-files', () => {
lockFiles.determineLockFileDirs.mockReturnValueOnce({ lockFiles.determineLockFileDirs.mockReturnValueOnce({
packageLockFileDirs: ['a', 'b'], packageLockFileDirs: ['a', 'b'],
yarnLockFileDirs: ['c', 'd'], yarnLockFileDirs: ['c', 'd'],
shrinkwrapYamlDirs: ['e'],
}); });
npm.generateLockFile.mockReturnValueOnce('some new lock file contents'); npm.generateLockFile.mockReturnValueOnce('some new lock file contents');
yarn.generateLockFile.mockReturnValueOnce('some new lock file contents'); yarn.generateLockFile.mockReturnValueOnce('some new lock file contents');
pnpm.generateLockFile.mockReturnValueOnce('some new lock file contents');
const res = await getUpdatedLockFiles(config); const res = await getUpdatedLockFiles(config);
expect(res.lockFileErrors).toHaveLength(0); expect(res.lockFileErrors).toHaveLength(0);
expect(res.updatedLockFiles).toHaveLength(2); expect(res.updatedLockFiles).toHaveLength(3);
expect(npm.generateLockFile.mock.calls).toHaveLength(2); expect(npm.generateLockFile.mock.calls).toHaveLength(2);
expect(yarn.generateLockFile.mock.calls).toHaveLength(2); expect(yarn.generateLockFile.mock.calls).toHaveLength(2);
expect(platform.getFile.mock.calls).toHaveLength(4); expect(platform.getFile.mock.calls).toHaveLength(5);
}); });
}); });
}); });

View file

@ -0,0 +1,95 @@
const pnpmHelper = require('../../../lib/workers/branch/pnpm');
const { getInstalledPath } = require('get-installed-path');
jest.mock('fs-extra');
jest.mock('child-process-promise');
jest.mock('get-installed-path');
getInstalledPath.mockImplementation(() => null);
const fs = require('fs-extra');
const { exec } = require('child-process-promise');
describe('generateLockFile', () => {
it('generates lock files', async () => {
getInstalledPath.mockReturnValueOnce('node_modules/pnpm');
exec.mockReturnValueOnce({
stdout: '',
stderror: '',
});
fs.readFile = jest.fn(() => 'package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir');
expect(fs.readFile.mock.calls.length).toEqual(1);
expect(res.lockFile).toEqual('package-lock-contents');
});
it('catches errors', async () => {
getInstalledPath.mockReturnValueOnce('node_modules/pnpm');
exec.mockReturnValueOnce({
stdout: '',
stderror: 'some-error',
});
fs.readFile = jest.fn(() => {
throw new Error('not found');
});
const res = await pnpmHelper.generateLockFile('some-dir');
expect(fs.readFile.mock.calls.length).toEqual(1);
expect(res.error).toBe(true);
expect(res.lockFile).not.toBeDefined();
});
it('finds pnpm embedded in renovate', async () => {
getInstalledPath.mockImplementationOnce(() => {
throw new Error('not found');
});
getInstalledPath.mockImplementationOnce(() => '/node_modules/renovate');
getInstalledPath.mockImplementationOnce(
() => '/node_modules/renovate/node_modules/pnpm'
);
exec.mockReturnValueOnce({
stdout: '',
stderror: '',
});
fs.readFile = jest.fn(() => 'package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir');
expect(fs.readFile.mock.calls.length).toEqual(1);
expect(res.lockFile).toEqual('package-lock-contents');
});
it('finds pnpm globally', async () => {
getInstalledPath.mockImplementationOnce(() => {
throw new Error('not found');
});
getInstalledPath.mockImplementationOnce(() => '/node_modules/renovate');
getInstalledPath.mockImplementationOnce(() => {
throw new Error('not found');
});
getInstalledPath.mockImplementationOnce(() => '/node_modules/pnpm');
exec.mockReturnValueOnce({
stdout: '',
stderror: '',
});
fs.readFile = jest.fn(() => 'package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir');
expect(fs.readFile.mock.calls.length).toEqual(1);
expect(res.lockFile).toEqual('package-lock-contents');
});
it('uses fallback pnpm', async () => {
getInstalledPath.mockImplementationOnce(() => {
throw new Error('not found');
});
getInstalledPath.mockImplementationOnce(() => '/node_modules/renovate');
getInstalledPath.mockImplementationOnce(() => {
throw new Error('not found');
});
getInstalledPath.mockImplementationOnce(() => {
throw new Error('not found');
});
exec.mockReturnValueOnce({
stdout: '',
stderror: '',
});
fs.readFile = jest.fn(() => 'package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir');
expect(fs.readFile.mock.calls.length).toEqual(1);
expect(res.lockFile).toEqual('package-lock-contents');
});
});

1550
yarn.lock

File diff suppressed because it is too large Load diff