mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 22:46:27 +00:00
feat(npm): fuzzy merge registries in .yarnrc.yml (#26922)
Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
parent
88000a4f9b
commit
88daaf5a89
5 changed files with 179 additions and 6 deletions
|
@ -382,14 +382,20 @@ For example, the Renovate configuration:
|
|||
|
||||
will update `.yarnrc.yml` as following:
|
||||
|
||||
If no registry currently set
|
||||
|
||||
```yaml
|
||||
npmRegistries:
|
||||
//npm.pkg.github.com/:
|
||||
npmAuthToken: <Decrypted PAT Token>
|
||||
//npm.pkg.github.com:
|
||||
# this will not be overwritten and may conflict
|
||||
https://npm.pkg.github.com/:
|
||||
# this will not be overwritten and may conflict
|
||||
```
|
||||
|
||||
If current registry key has protocol set:
|
||||
|
||||
```yaml
|
||||
npmRegistries:
|
||||
https://npm.pkg.github.com:
|
||||
npmAuthToken: <Decrypted PAT Token>
|
||||
```
|
||||
|
||||
### maven
|
||||
|
|
|
@ -7,6 +7,7 @@ import type { FileChange } from '../../../../util/git/types';
|
|||
import type { PostUpdateConfig } from '../../types';
|
||||
import * as npm from './npm';
|
||||
import * as pnpm from './pnpm';
|
||||
import * as rules from './rules';
|
||||
import type { AdditionalPackageFiles } from './types';
|
||||
import * as yarn from './yarn';
|
||||
import {
|
||||
|
@ -393,11 +394,16 @@ describe('modules/manager/npm/post-update/index', () => {
|
|||
const spyNpm = jest.spyOn(npm, 'generateLockFile');
|
||||
const spyYarn = jest.spyOn(yarn, 'generateLockFile');
|
||||
const spyPnpm = jest.spyOn(pnpm, 'generateLockFile');
|
||||
const spyProcessHostRules = jest.spyOn(rules, 'processHostRules');
|
||||
|
||||
beforeEach(() => {
|
||||
spyNpm.mockResolvedValue({});
|
||||
spyPnpm.mockResolvedValue({});
|
||||
spyYarn.mockResolvedValue({});
|
||||
spyProcessHostRules.mockReturnValue({
|
||||
additionalNpmrcContent: [],
|
||||
additionalYarnRcYml: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('works', async () => {
|
||||
|
@ -677,5 +683,90 @@ describe('modules/manager/npm/post-update/index', () => {
|
|||
updatedArtifacts: [],
|
||||
});
|
||||
});
|
||||
|
||||
describe('should fuzzy merge yarn npmRegistries', () => {
|
||||
beforeEach(() => {
|
||||
spyProcessHostRules.mockReturnValue({
|
||||
additionalNpmrcContent: [],
|
||||
additionalYarnRcYml: {
|
||||
npmRegistries: {
|
||||
'//my-private-registry': {
|
||||
npmAuthToken: 'xxxxxx',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
fs.getSiblingFileName.mockReturnValue('.yarnrc.yml');
|
||||
});
|
||||
|
||||
it('should fuzzy merge the yarnrc Files', async () => {
|
||||
(yarn.fuzzyMatchAdditionalYarnrcYml as jest.Mock).mockReturnValue({
|
||||
npmRegistries: {
|
||||
'https://my-private-registry': { npmAuthToken: 'xxxxxx' },
|
||||
},
|
||||
});
|
||||
fs.readLocalFile.mockImplementation((f): Promise<any> => {
|
||||
if (f === '.yarnrc.yml') {
|
||||
return Promise.resolve(
|
||||
'npmRegistries:\n' +
|
||||
' https://my-private-registry:\n' +
|
||||
' npmAlwaysAuth: true\n',
|
||||
);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
spyYarn.mockResolvedValueOnce({ error: false, lockFile: '{}' });
|
||||
await getAdditionalFiles(
|
||||
{
|
||||
...updateConfig,
|
||||
updateLockFiles: true,
|
||||
reuseExistingBranch: true,
|
||||
},
|
||||
additionalFiles,
|
||||
);
|
||||
expect(fs.writeLocalFile).toHaveBeenCalledWith(
|
||||
'.yarnrc.yml',
|
||||
'npmRegistries:\n' +
|
||||
' https://my-private-registry:\n' +
|
||||
' npmAlwaysAuth: true\n' +
|
||||
' npmAuthToken: xxxxxx\n',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if there is an error writing the yarnrc.yml', async () => {
|
||||
fs.readLocalFile.mockImplementation((f): Promise<any> => {
|
||||
if (f === '.yarnrc.yml') {
|
||||
return Promise.resolve(
|
||||
`yarnPath: .yarn/releases/yarn-3.0.1.cjs\na: b\n`,
|
||||
);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
fs.writeLocalFile.mockImplementation((f): Promise<any> => {
|
||||
if (f === '.yarnrc.yml') {
|
||||
throw new Error();
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
spyYarn.mockResolvedValueOnce({ error: false, lockFile: '{}' });
|
||||
|
||||
await getAdditionalFiles(
|
||||
{
|
||||
...updateConfig,
|
||||
updateLockFiles: true,
|
||||
reuseExistingBranch: true,
|
||||
},
|
||||
additionalFiles,
|
||||
).catch(() => {});
|
||||
|
||||
expect(logger.logger.warn).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'Error appending .yarnrc.yml content',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -563,7 +563,6 @@ export async function getAdditionalFiles(
|
|||
await updateNpmrcContent(lockFileDir, npmrcContent, additionalNpmrcContent);
|
||||
let yarnRcYmlFilename: string | undefined;
|
||||
let existingYarnrcYmlContent: string | undefined | null;
|
||||
// istanbul ignore if: needs test
|
||||
if (additionalYarnRcYml) {
|
||||
yarnRcYmlFilename = getSiblingFileName(yarnLock, '.yarnrc.yml');
|
||||
existingYarnrcYmlContent = await readLocalFile(yarnRcYmlFilename, 'utf8');
|
||||
|
@ -573,10 +572,15 @@ export async function getAdditionalFiles(
|
|||
const existingYarnrRcYml = parseSingleYaml<Record<string, unknown>>(
|
||||
existingYarnrcYmlContent,
|
||||
);
|
||||
|
||||
const updatedYarnYrcYml = deepmerge(
|
||||
existingYarnrRcYml,
|
||||
additionalYarnRcYml,
|
||||
yarn.fuzzyMatchAdditionalYarnrcYml(
|
||||
additionalYarnRcYml,
|
||||
existingYarnrRcYml,
|
||||
),
|
||||
);
|
||||
|
||||
await writeLocalFile(yarnRcYmlFilename, dump(updatedYarnYrcYml));
|
||||
logger.debug('Added authentication to .yarnrc.yml');
|
||||
} catch (err) {
|
||||
|
|
|
@ -726,4 +726,55 @@ describe('modules/manager/npm/post-update/yarn', () => {
|
|||
expect(Fixtures.toJSON()['/tmp/renovate/.yarnrc']).toBe('\n\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fuzzyMatchAdditionalYarnrcYml()', () => {
|
||||
it.each`
|
||||
additionalRegistry | existingRegistry | expectedRegistry
|
||||
${['//my-private-registry']} | ${['//my-private-registry']} | ${['//my-private-registry']}
|
||||
${[]} | ${['//my-private-registry']} | ${[]}
|
||||
${[]} | ${[]} | ${[]}
|
||||
${null} | ${null} | ${[]}
|
||||
${['//my-private-registry']} | ${[]} | ${['//my-private-registry']}
|
||||
${['//my-private-registry']} | ${['https://my-private-registry']} | ${['https://my-private-registry']}
|
||||
${['//my-private-registry']} | ${['http://my-private-registry']} | ${['http://my-private-registry']}
|
||||
${['//my-private-registry']} | ${['http://my-private-registry/']} | ${['http://my-private-registry/']}
|
||||
${['//my-private-registry']} | ${['https://my-private-registry/']} | ${['https://my-private-registry/']}
|
||||
${['//my-private-registry']} | ${['//my-private-registry/']} | ${['//my-private-registry/']}
|
||||
${['//my-private-registry/']} | ${['//my-private-registry/']} | ${['//my-private-registry/']}
|
||||
${['//my-private-registry/']} | ${['//my-private-registry']} | ${['//my-private-registry']}
|
||||
`(
|
||||
'should return $expectedRegistry when parsing $additionalRegistry against local $existingRegistry',
|
||||
({
|
||||
additionalRegistry,
|
||||
existingRegistry,
|
||||
expectedRegistry,
|
||||
}: Record<
|
||||
'additionalRegistry' | 'existingRegistry' | 'expectedRegistry',
|
||||
string[]
|
||||
>) => {
|
||||
expect(
|
||||
yarnHelper.fuzzyMatchAdditionalYarnrcYml(
|
||||
{
|
||||
npmRegistries: additionalRegistry?.reduce(
|
||||
(acc, cur) => ({
|
||||
...acc,
|
||||
[cur]: { npmAuthToken: 'xxxxxx' },
|
||||
}),
|
||||
{},
|
||||
),
|
||||
},
|
||||
{
|
||||
npmRegistries: existingRegistry?.reduce(
|
||||
(acc, cur) => ({
|
||||
...acc,
|
||||
[cur]: { npmAuthToken: 'xxxxxx' },
|
||||
}),
|
||||
{},
|
||||
),
|
||||
},
|
||||
).npmRegistries,
|
||||
).toContainAllKeys(expectedRegistry);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -315,3 +315,24 @@ export async function generateLockFile(
|
|||
}
|
||||
return { lockFile };
|
||||
}
|
||||
|
||||
export function fuzzyMatchAdditionalYarnrcYml<
|
||||
T extends { npmRegistries?: Record<string, unknown> },
|
||||
>(additionalYarnRcYml: T, existingYarnrRcYml: T): T {
|
||||
const keys = new Map(
|
||||
Object.keys(existingYarnrRcYml.npmRegistries ?? {}).map((x) => [
|
||||
x.replace(/\/$/, '').replace(/^https?:/, ''),
|
||||
x,
|
||||
]),
|
||||
);
|
||||
|
||||
return {
|
||||
...additionalYarnRcYml,
|
||||
npmRegistries: Object.entries(additionalYarnRcYml.npmRegistries ?? {})
|
||||
.map(([k, v]) => {
|
||||
const key = keys.get(k.replace(/\/$/, '')) ?? k;
|
||||
return { [key]: v };
|
||||
})
|
||||
.reduce((acc, cur) => ({ ...acc, ...cur }), {}),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue