Skip to content
Snippets Groups Projects
Unverified Commit ca821eb0 authored by Nathan Kleyn's avatar Nathan Kleyn Committed by GitHub
Browse files

feat(bun): Add support for updating text-format lockfile when package.json changes (#33189)


Co-authored-by: default avatarRhys Arkins <rhys@arkins.net>
parent 2342d9f7
Branches
Tags 19.38.3
No related merge requests found
...@@ -41,101 +41,198 @@ describe('modules/manager/bun/artifacts', () => { ...@@ -41,101 +41,198 @@ describe('modules/manager/bun/artifacts', () => {
expect(await updateArtifacts(updateArtifact)).toBeNull(); expect(await updateArtifacts(updateArtifact)).toBeNull();
}); });
it('skips if cannot read lock file', async () => { describe('when using .lockb lockfile format', () => {
updateArtifact.updatedDeps = [ it('skips if cannot read lock file', async () => {
{ manager: 'bun', lockFiles: ['bun.lockb'] }, updateArtifact.updatedDeps = [
]; { manager: 'bun', lockFiles: ['bun.lockb'] },
expect(await updateArtifacts(updateArtifact)).toBeNull(); ];
}); expect(await updateArtifacts(updateArtifact)).toBeNull();
});
it('returns null if lock content unchanged', async () => { it('returns null if lock content unchanged', async () => {
updateArtifact.updatedDeps = [ updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] }, { manager: 'bun', lockFiles: ['bun.lockb'] },
]; ];
const oldLock = Buffer.from('old'); const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never); fs.readFile.mockResolvedValueOnce(oldLock as never);
fs.readFile.mockResolvedValueOnce(oldLock as never); fs.readFile.mockResolvedValueOnce(oldLock as never);
expect(await updateArtifacts(updateArtifact)).toBeNull(); expect(await updateArtifacts(updateArtifact)).toBeNull();
}); });
it('returns updated lock content', async () => { it('returns updated lock content', async () => {
updateArtifact.updatedDeps = [ updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] }, { manager: 'bun', lockFiles: ['bun.lockb'] },
]; ];
const oldLock = Buffer.from('old'); const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never); fs.readFile.mockResolvedValueOnce(oldLock as never);
const newLock = Buffer.from('new'); const newLock = Buffer.from('new');
fs.readFile.mockResolvedValueOnce(newLock as never); fs.readFile.mockResolvedValueOnce(newLock as never);
expect(await updateArtifacts(updateArtifact)).toEqual([ expect(await updateArtifacts(updateArtifact)).toEqual([
{ {
file: { file: {
path: 'bun.lockb', path: 'bun.lockb',
type: 'addition', type: 'addition',
contents: newLock, contents: newLock,
},
}, },
}, ]);
]); });
});
it('supports lockFileMaintenance', async () => { it('supports lockFileMaintenance', async () => {
updateArtifact.updatedDeps = [ updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] }, { manager: 'bun', lockFiles: ['bun.lockb'] },
]; ];
updateArtifact.config.updateType = 'lockFileMaintenance'; updateArtifact.config.updateType = 'lockFileMaintenance';
const oldLock = Buffer.from('old'); const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never); fs.readFile.mockResolvedValueOnce(oldLock as never);
const newLock = Buffer.from('new'); const newLock = Buffer.from('new');
fs.readFile.mockResolvedValueOnce(newLock as never); fs.readFile.mockResolvedValueOnce(newLock as never);
expect(await updateArtifacts(updateArtifact)).toEqual([ expect(await updateArtifacts(updateArtifact)).toEqual([
{ {
file: { file: {
path: 'bun.lockb', path: 'bun.lockb',
type: 'addition', type: 'addition',
contents: newLock, contents: newLock,
},
}, },
}, ]);
]); });
});
it('handles temporary error', async () => { it('handles temporary error', async () => {
const execError = new ExecError(TEMPORARY_ERROR, { const execError = new ExecError(TEMPORARY_ERROR, {
cmd: '', cmd: '',
stdout: '', stdout: '',
stderr: '', stderr: '',
options: { encoding: 'utf8' }, options: { encoding: 'utf8' },
});
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
exec.mockRejectedValueOnce(execError);
await expect(updateArtifacts(updateArtifact)).rejects.toThrow(
TEMPORARY_ERROR,
);
});
it('handles full error', async () => {
const execError = new ExecError('nope', {
cmd: '',
stdout: '',
stderr: '',
options: { encoding: 'utf8' },
});
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
exec.mockRejectedValueOnce(execError);
expect(await updateArtifacts(updateArtifact)).toEqual([
{ artifactError: { lockFile: 'bun.lockb', stderr: 'nope' } },
]);
}); });
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
exec.mockRejectedValueOnce(execError);
await expect(updateArtifacts(updateArtifact)).rejects.toThrow(
TEMPORARY_ERROR,
);
}); });
it('handles full error', async () => { describe('when using .lock lockfile format', () => {
const execError = new ExecError('nope', { it('skips if cannot read lock file', async () => {
cmd: '', updateArtifact.updatedDeps = [
stdout: '', { manager: 'bun', lockFiles: ['bun.lock'] },
stderr: '', ];
options: { encoding: 'utf8' }, expect(await updateArtifacts(updateArtifact)).toBeNull();
});
it('returns null if lock content unchanged', async () => {
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lock'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
fs.readFile.mockResolvedValueOnce(oldLock as never);
expect(await updateArtifacts(updateArtifact)).toBeNull();
});
it('returns updated lock content', async () => {
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lock'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
const newLock = Buffer.from('new');
fs.readFile.mockResolvedValueOnce(newLock as never);
expect(await updateArtifacts(updateArtifact)).toEqual([
{
file: {
path: 'bun.lock',
type: 'addition',
contents: newLock,
},
},
]);
});
it('supports lockFileMaintenance', async () => {
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lock'] },
];
updateArtifact.config.updateType = 'lockFileMaintenance';
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
const newLock = Buffer.from('new');
fs.readFile.mockResolvedValueOnce(newLock as never);
expect(await updateArtifacts(updateArtifact)).toEqual([
{
file: {
path: 'bun.lock',
type: 'addition',
contents: newLock,
},
},
]);
});
it('handles temporary error', async () => {
const execError = new ExecError(TEMPORARY_ERROR, {
cmd: '',
stdout: '',
stderr: '',
options: { encoding: 'utf8' },
});
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lock'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
exec.mockRejectedValueOnce(execError);
await expect(updateArtifacts(updateArtifact)).rejects.toThrow(
TEMPORARY_ERROR,
);
});
it('handles full error', async () => {
const execError = new ExecError('nope', {
cmd: '',
stdout: '',
stderr: '',
options: { encoding: 'utf8' },
});
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lock'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
exec.mockRejectedValueOnce(execError);
expect(await updateArtifacts(updateArtifact)).toEqual([
{ artifactError: { lockFile: 'bun.lock', stderr: 'nope' } },
]);
}); });
updateArtifact.updatedDeps = [
{ manager: 'bun', lockFiles: ['bun.lockb'] },
];
const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never);
exec.mockRejectedValueOnce(execError);
expect(await updateArtifacts(updateArtifact)).toEqual([
{ artifactError: { lockFile: 'bun.lockb', stderr: 'nope' } },
]);
}); });
}); });
describe('bun command execution', () => { describe('bun command execution', () => {
it('check install options with configs', async () => { it('check install options with configs', async () => {
const lockfileFormats = ['bun.lockb', 'bun.lock'];
const testCases = [ const testCases = [
{ {
allowScripts: undefined, allowScripts: undefined,
...@@ -184,38 +281,40 @@ describe('modules/manager/bun/artifacts', () => { ...@@ -184,38 +281,40 @@ describe('modules/manager/bun/artifacts', () => {
}, },
]; ];
for (const testCase of testCases) { for (const lockFile of lockfileFormats) {
GlobalConfig.set({ for (const testCase of testCases) {
...globalConfig, GlobalConfig.set({
allowScripts: testCase.allowScripts, ...globalConfig,
}); allowScripts: testCase.allowScripts,
const updateArtifact: UpdateArtifact = { });
config: { ignoreScripts: testCase.ignoreScripts }, const updateArtifact: UpdateArtifact = {
newPackageFileContent: '', config: { ignoreScripts: testCase.ignoreScripts },
packageFileName: '', newPackageFileContent: '',
updatedDeps: [{ manager: 'bun', lockFiles: ['bun.lockb'] }], packageFileName: '',
}; updatedDeps: [{ manager: 'bun', lockFiles: [lockFile] }],
};
const oldLock = Buffer.from('old'); const oldLock = Buffer.from('old');
fs.readFile.mockResolvedValueOnce(oldLock as never); fs.readFile.mockResolvedValueOnce(oldLock as never);
const newLock = Buffer.from('new'); const newLock = Buffer.from('new');
fs.readFile.mockResolvedValueOnce(newLock as never); fs.readFile.mockResolvedValueOnce(newLock as never);
await updateArtifacts(updateArtifact); await updateArtifacts(updateArtifact);
expect(exec).toHaveBeenCalledWith(testCase.expectedCmd, { expect(exec).toHaveBeenCalledWith(testCase.expectedCmd, {
cwdFile: '', cwdFile: '',
docker: {}, docker: {},
toolConstraints: [ toolConstraints: [
{ {
toolName: 'bun', toolName: 'bun',
}, },
], ],
userConfiguredEnv: undefined, userConfiguredEnv: undefined,
}); });
exec.mockClear(); exec.mockClear();
GlobalConfig.reset(); GlobalConfig.reset();
}
} }
}); });
}); });
......
...@@ -9,60 +9,120 @@ describe('modules/manager/bun/extract', () => { ...@@ -9,60 +9,120 @@ describe('modules/manager/bun/extract', () => {
expect(await extractAllPackageFiles({}, ['package.json'])).toEqual([]); expect(await extractAllPackageFiles({}, ['package.json'])).toEqual([]);
}); });
it('ignores missing package.json file', async () => { describe('when using the .lockb lockfile format', () => {
expect(await extractAllPackageFiles({}, ['bun.lockb'])).toEqual([]); it('ignores missing package.json file', async () => {
}); expect(await extractAllPackageFiles({}, ['bun.lockb'])).toEqual([]);
});
it('ignores invalid package.json file', async () => { it('ignores invalid package.json file', async () => {
(fs.readLocalFile as jest.Mock).mockResolvedValueOnce('invalid'); (fs.readLocalFile as jest.Mock).mockResolvedValueOnce('invalid');
expect(await extractAllPackageFiles({}, ['bun.lockb'])).toEqual([]); expect(await extractAllPackageFiles({}, ['bun.lockb'])).toEqual([]);
}); });
it('handles null response', async () => { it('handles null response', async () => {
fs.getSiblingFileName.mockReturnValueOnce('package.json'); fs.getSiblingFileName.mockReturnValueOnce('package.json');
fs.readLocalFile.mockResolvedValueOnce( fs.readLocalFile.mockResolvedValueOnce(
// This package.json returns null from the extractor // This package.json returns null from the extractor
JSON.stringify({ JSON.stringify({
_id: 1, _id: 1,
_args: 1, _args: 1,
_from: 1, _from: 1,
}), }),
); );
expect(await extractAllPackageFiles({}, ['bun.lockb'])).toEqual([]); expect(await extractAllPackageFiles({}, ['bun.lockb'])).toEqual([]);
}); });
it('parses valid package.json file', async () => { it('parses valid package.json file', async () => {
fs.getSiblingFileName.mockReturnValueOnce('package.json'); fs.getSiblingFileName.mockReturnValueOnce('package.json');
fs.readLocalFile.mockResolvedValueOnce( fs.readLocalFile.mockResolvedValueOnce(
JSON.stringify({ JSON.stringify({
name: 'test', name: 'test',
version: '0.0.1', version: '0.0.1',
dependencies: { dependencies: {
dep1: '1.0.0', dep1: '1.0.0',
},
}),
);
expect(await extractAllPackageFiles({}, ['bun.lockb'])).toMatchObject([
{
deps: [
{
currentValue: '1.0.0',
datasource: 'npm',
depName: 'dep1',
depType: 'dependencies',
prettyDepType: 'dependency',
},
],
extractedConstraints: {},
lockFiles: ['bun.lockb'],
managerData: {
hasPackageManager: false,
packageJsonName: 'test',
},
packageFile: 'package.json',
packageFileVersion: '0.0.1',
}, },
}), ]);
); });
expect(await extractAllPackageFiles({}, ['bun.lockb'])).toMatchObject([ });
{
deps: [ describe('when using the .lock lockfile format', () => {
{ it('ignores missing package.json file', async () => {
currentValue: '1.0.0', expect(await extractAllPackageFiles({}, ['bun.lock'])).toEqual([]);
datasource: 'npm', });
depName: 'dep1',
depType: 'dependencies', it('ignores invalid package.json file', async () => {
prettyDepType: 'dependency', (fs.readLocalFile as jest.Mock).mockResolvedValueOnce('invalid');
expect(await extractAllPackageFiles({}, ['bun.lock'])).toEqual([]);
});
it('handles null response', async () => {
fs.getSiblingFileName.mockReturnValueOnce('package.json');
fs.readLocalFile.mockResolvedValueOnce(
// This package.json returns null from the extractor
JSON.stringify({
_id: 1,
_args: 1,
_from: 1,
}),
);
expect(await extractAllPackageFiles({}, ['bun.lock'])).toEqual([]);
});
it('parses valid package.json file', async () => {
fs.getSiblingFileName.mockReturnValueOnce('package.json');
fs.readLocalFile.mockResolvedValueOnce(
JSON.stringify({
name: 'test',
version: '0.0.1',
dependencies: {
dep1: '1.0.0',
},
}),
);
expect(await extractAllPackageFiles({}, ['bun.lock'])).toMatchObject([
{
deps: [
{
currentValue: '1.0.0',
datasource: 'npm',
depName: 'dep1',
depType: 'dependencies',
prettyDepType: 'dependency',
},
],
extractedConstraints: {},
lockFiles: ['bun.lock'],
managerData: {
hasPackageManager: false,
packageJsonName: 'test',
}, },
], packageFile: 'package.json',
extractedConstraints: {}, packageFileVersion: '0.0.1',
lockFiles: ['bun.lockb'],
managerData: {
hasPackageManager: false,
packageJsonName: 'test',
}, },
packageFile: 'package.json', ]);
packageFileVersion: '0.0.1', });
},
]);
}); });
}); });
}); });
...@@ -18,7 +18,12 @@ export async function extractAllPackageFiles( ...@@ -18,7 +18,12 @@ export async function extractAllPackageFiles(
): Promise<PackageFile[]> { ): Promise<PackageFile[]> {
const packageFiles: PackageFile<NpmManagerData>[] = []; const packageFiles: PackageFile<NpmManagerData>[] = [];
for (const matchedFile of matchedFiles) { for (const matchedFile of matchedFiles) {
if (!matchesFileName(matchedFile, 'bun.lockb')) { if (
!(
matchesFileName(matchedFile, 'bun.lockb') ||
matchesFileName(matchedFile, 'bun.lock')
)
) {
logger.warn({ matchedFile }, 'Invalid bun lockfile match'); logger.warn({ matchedFile }, 'Invalid bun lockfile match');
continue; continue;
} }
......
...@@ -13,7 +13,7 @@ export const supersedesManagers = ['npm']; ...@@ -13,7 +13,7 @@ export const supersedesManagers = ['npm'];
export const supportsLockFileMaintenance = true; export const supportsLockFileMaintenance = true;
export const defaultConfig = { export const defaultConfig = {
fileMatch: ['(^|/)bun\\.lockb$'], fileMatch: ['(^|/)bun\\.lockb?$'],
digest: { digest: {
prBodyDefinitions: { prBodyDefinitions: {
Change: Change:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment