mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +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:
|
will update `.yarnrc.yml` as following:
|
||||||
|
|
||||||
|
If no registry currently set
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
npmRegistries:
|
npmRegistries:
|
||||||
//npm.pkg.github.com/:
|
//npm.pkg.github.com/:
|
||||||
npmAuthToken: <Decrypted PAT Token>
|
npmAuthToken: <Decrypted PAT Token>
|
||||||
//npm.pkg.github.com:
|
```
|
||||||
# this will not be overwritten and may conflict
|
|
||||||
https://npm.pkg.github.com/:
|
If current registry key has protocol set:
|
||||||
# this will not be overwritten and may conflict
|
|
||||||
|
```yaml
|
||||||
|
npmRegistries:
|
||||||
|
https://npm.pkg.github.com:
|
||||||
|
npmAuthToken: <Decrypted PAT Token>
|
||||||
```
|
```
|
||||||
|
|
||||||
### maven
|
### maven
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type { FileChange } from '../../../../util/git/types';
|
||||||
import type { PostUpdateConfig } from '../../types';
|
import type { PostUpdateConfig } from '../../types';
|
||||||
import * as npm from './npm';
|
import * as npm from './npm';
|
||||||
import * as pnpm from './pnpm';
|
import * as pnpm from './pnpm';
|
||||||
|
import * as rules from './rules';
|
||||||
import type { AdditionalPackageFiles } from './types';
|
import type { AdditionalPackageFiles } from './types';
|
||||||
import * as yarn from './yarn';
|
import * as yarn from './yarn';
|
||||||
import {
|
import {
|
||||||
|
@ -393,11 +394,16 @@ describe('modules/manager/npm/post-update/index', () => {
|
||||||
const spyNpm = jest.spyOn(npm, 'generateLockFile');
|
const spyNpm = jest.spyOn(npm, 'generateLockFile');
|
||||||
const spyYarn = jest.spyOn(yarn, 'generateLockFile');
|
const spyYarn = jest.spyOn(yarn, 'generateLockFile');
|
||||||
const spyPnpm = jest.spyOn(pnpm, 'generateLockFile');
|
const spyPnpm = jest.spyOn(pnpm, 'generateLockFile');
|
||||||
|
const spyProcessHostRules = jest.spyOn(rules, 'processHostRules');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyNpm.mockResolvedValue({});
|
spyNpm.mockResolvedValue({});
|
||||||
spyPnpm.mockResolvedValue({});
|
spyPnpm.mockResolvedValue({});
|
||||||
spyYarn.mockResolvedValue({});
|
spyYarn.mockResolvedValue({});
|
||||||
|
spyProcessHostRules.mockReturnValue({
|
||||||
|
additionalNpmrcContent: [],
|
||||||
|
additionalYarnRcYml: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works', async () => {
|
it('works', async () => {
|
||||||
|
@ -677,5 +683,90 @@ describe('modules/manager/npm/post-update/index', () => {
|
||||||
updatedArtifacts: [],
|
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);
|
await updateNpmrcContent(lockFileDir, npmrcContent, additionalNpmrcContent);
|
||||||
let yarnRcYmlFilename: string | undefined;
|
let yarnRcYmlFilename: string | undefined;
|
||||||
let existingYarnrcYmlContent: string | undefined | null;
|
let existingYarnrcYmlContent: string | undefined | null;
|
||||||
// istanbul ignore if: needs test
|
|
||||||
if (additionalYarnRcYml) {
|
if (additionalYarnRcYml) {
|
||||||
yarnRcYmlFilename = getSiblingFileName(yarnLock, '.yarnrc.yml');
|
yarnRcYmlFilename = getSiblingFileName(yarnLock, '.yarnrc.yml');
|
||||||
existingYarnrcYmlContent = await readLocalFile(yarnRcYmlFilename, 'utf8');
|
existingYarnrcYmlContent = await readLocalFile(yarnRcYmlFilename, 'utf8');
|
||||||
|
@ -573,10 +572,15 @@ export async function getAdditionalFiles(
|
||||||
const existingYarnrRcYml = parseSingleYaml<Record<string, unknown>>(
|
const existingYarnrRcYml = parseSingleYaml<Record<string, unknown>>(
|
||||||
existingYarnrcYmlContent,
|
existingYarnrcYmlContent,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedYarnYrcYml = deepmerge(
|
const updatedYarnYrcYml = deepmerge(
|
||||||
existingYarnrRcYml,
|
existingYarnrRcYml,
|
||||||
|
yarn.fuzzyMatchAdditionalYarnrcYml(
|
||||||
additionalYarnRcYml,
|
additionalYarnRcYml,
|
||||||
|
existingYarnrRcYml,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await writeLocalFile(yarnRcYmlFilename, dump(updatedYarnYrcYml));
|
await writeLocalFile(yarnRcYmlFilename, dump(updatedYarnYrcYml));
|
||||||
logger.debug('Added authentication to .yarnrc.yml');
|
logger.debug('Added authentication to .yarnrc.yml');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -726,4 +726,55 @@ describe('modules/manager/npm/post-update/yarn', () => {
|
||||||
expect(Fixtures.toJSON()['/tmp/renovate/.yarnrc']).toBe('\n\n');
|
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 };
|
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