Skip to content
Snippets Groups Projects
Unverified Commit cd72cdf2 authored by Rhys Arkins's avatar Rhys Arkins Committed by GitHub
Browse files

feat(config): detectGlobalManagerConfig (#11951)

parent 79e65bd0
Branches
Tags
No related merge requests found
......@@ -130,6 +130,15 @@ e.g.
This configuration will be applied after all other environment variables so that it can be used to override defaults.
## detectGlobalManagerConfig
The purpose of this capability is to allow a bot admin to configure manager-specific files such as a global `.npmrc` file, instead of configuring it in Renovate config.
This feature is disabled by default because it may prove surprising or undesirable for some users who don't expect Renovate to go into their home directory and import registry or credential information.
Currently this capability is supported for the `npm` manager only - specifically the `~/.npmrc` file.
If found, it will be imported into `config.npmrc` with `config.npmrcMerge` will be set to `true`.
## dockerChildPrefix
Adds a custom prefix to the default Renovate sidecar Docker containers name and label.
......
......@@ -7,6 +7,14 @@ import * as pep440Versioning from '../../versioning/pep440';
import type { RenovateOptions } from '../types';
const options: RenovateOptions[] = [
{
name: 'detectGlobalManagerConfig',
description:
'If true, Renovate will attempt to read global manager config from the file system.',
type: 'boolean',
default: false,
globalOnly: true,
},
{
name: 'allowPostUpgradeCommandTemplating',
description: 'If true allow templating for post-upgrade commands.',
......
......@@ -2,6 +2,8 @@ import { loadModules } from '../util/modules';
import type { ManagerApi } from './types';
import * as manager from '.';
jest.mock('../util/fs');
describe('manager/index', () => {
describe('get()', () => {
it('gets something', () => {
......@@ -43,6 +45,12 @@ describe('manager/index', () => {
}
});
describe('detectGlobalConfig()', () => {
it('iterates through managers', async () => {
expect(await manager.detectAllGlobalConfig()).toEqual({});
});
});
describe('extractAllPackageFiles()', () => {
it('returns null', async () => {
manager.getManagers().set('dummy', {
......
......@@ -15,6 +15,7 @@ import type { RangeStrategy } from '../types';
import managers from './api';
import type {
ExtractConfig,
GlobalManagerConfig,
ManagerApi,
PackageFile,
RangeConfig,
......@@ -47,6 +48,18 @@ export const getLanguageList = (): string[] => languageList;
export const getManagerList = (): string[] => managerList;
export const getManagers = (): Map<string, ManagerApi> => managers;
export async function detectAllGlobalConfig(): Promise<GlobalManagerConfig> {
let config: GlobalManagerConfig = {};
for (const managerName of managerList) {
const manager = managers.get(managerName);
if (manager.detectGlobalConfig) {
// This should use mergeChildConfig once more than one manager is supported, but introduces a cyclic dependency
config = { ...config, ...(await manager.detectGlobalConfig()) };
}
}
return config;
}
export async function extractAllPackageFiles(
manager: string,
config: ExtractConfig,
......
import { fs } from '../../../test/util';
import { detectGlobalConfig } from './detect';
jest.mock('../../util/fs');
describe('manager/npm/detect', () => {
describe('.detectGlobalConfig()', () => {
it('detects .npmrc in home directory', async () => {
fs.readFile.mockResolvedValueOnce(
'registry=https://registry.npmjs.org\n'
);
const res = await detectGlobalConfig();
expect(res).toMatchInlineSnapshot(`
Object {
"npmrc": "registry=https://registry.npmjs.org
",
"npmrcMerge": true,
}
`);
expect(res.npmrc).toBeDefined();
expect(res.npmrcMerge).toBe(true);
});
it('handles no .npmrc', async () => {
fs.readFile.mockImplementationOnce(() => Promise.reject());
const res = await detectGlobalConfig();
expect(res).toEqual({});
});
});
});
import os from 'os';
import is from '@sindresorhus/is';
import { join } from 'upath';
import { logger } from '../../logger';
import { readFile } from '../../util/fs';
import { GlobalManagerConfig } from '../types';
export async function detectGlobalConfig(): Promise<GlobalManagerConfig> {
const res: GlobalManagerConfig = {};
const homedir = os.homedir();
const npmrcFileName = join(homedir, '.npmrc');
try {
const npmrc = await readFile(npmrcFileName, 'utf8');
if (is.nonEmptyString(npmrc)) {
res.npmrc = npmrc;
res.npmrcMerge = true;
logger.debug(`Detected ${npmrcFileName} and adding it to global config`);
}
} catch (err) {
logger.warn({ npmrcFileName }, 'Error reading .npmrc file');
}
return res;
}
import { LANGUAGE_JAVASCRIPT } from '../../constants/languages';
import * as npmVersioning from '../../versioning/npm';
export { detectGlobalConfig } from './detect';
export { extractAllPackageFiles } from './extract';
export {
bumpPackageVersion,
......
......@@ -220,6 +220,11 @@ export interface UpdateLockedConfig {
newVersion?: string;
}
export interface GlobalManagerConfig {
npmrc?: string;
npmrcMerge?: boolean;
}
export interface ManagerApi {
defaultConfig: Record<string, unknown>;
language?: string;
......@@ -231,6 +236,8 @@ export interface ManagerApi {
bumpVersion: ReleaseType | string
): Result<BumpPackageVersionResult>;
detectGlobalConfig?(): Result<GlobalManagerConfig>;
extractAllPackageFiles?(
config: ExtractConfig,
files: string[]
......
......@@ -3,6 +3,8 @@ import { readFile } from '../../../../util/fs';
import getArgv from './__fixtures__/argv';
jest.mock('../../../../datasource/npm');
jest.mock('../../../../util/fs');
try {
jest.mock('../../config.js');
} catch (err) {
......@@ -118,5 +120,10 @@ describe('workers/global/config/parse/index', () => {
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed.endpoint).toEqual('https://github.renovatebot.com/api/v3/');
});
it('parses global manager config', async () => {
defaultArgv = defaultArgv.concat(['--detect-global-manager-config=true']);
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed.npmrc).toBeNull();
});
});
});
......@@ -2,6 +2,7 @@ import * as defaultsParser from '../../../../config/defaults';
import { AllConfig } from '../../../../config/types';
import { mergeChildConfig } from '../../../../config/utils';
import { addStream, logger, setContext } from '../../../../logger';
import { detectAllGlobalConfig } from '../../../../manager';
import { ensureDir, getSubDirectory, readFile } from '../../../../util/fs';
import { ensureTrailingSlash } from '../../../../util/url';
import * as cliParser from './cli';
......@@ -73,6 +74,13 @@ export async function parseConfigs(
logger.debug({ config: envConfig }, 'Env config');
logger.debug({ config: combinedConfig }, 'Combined config');
if (config.detectGlobalManagerConfig) {
logger.debug('Detecting global manager config');
const globalManagerConfig = await detectAllGlobalConfig();
logger.debug({ config: globalManagerConfig }, 'Global manager config');
config = mergeChildConfig(config, globalManagerConfig);
}
// Get global config
logger.trace({ config }, 'Full config');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment