mirror of https://github.com/renovatebot/renovate
feat: detect languages during onboarding (#919)
* refactor: simplify onboarding logic * docker meteor tests * handle no package files case * fix coveragepull/920/head v9.73.0
parent
ef878b2d08
commit
8b99ce5294
lib/workers/repository
test/workers/repository
|
@ -322,6 +322,9 @@ async function resolvePackageFiles(inputConfig) {
|
|||
const { logger } = config;
|
||||
logger.trace({ config }, 'resolvePackageFiles()');
|
||||
const packageFiles = [];
|
||||
const contentBranch = config.repoIsOnboarded
|
||||
? config.baseBranch
|
||||
: config.onboardingBranch;
|
||||
for (let packageFile of config.packageFiles) {
|
||||
packageFile =
|
||||
typeof packageFile === 'string' ? { packageFile } : packageFile;
|
||||
|
@ -329,7 +332,7 @@ async function resolvePackageFiles(inputConfig) {
|
|||
logger.debug(`Resolving packageFile ${JSON.stringify(packageFile)}`);
|
||||
const pFileRaw = await config.api.getFileContent(
|
||||
packageFile.packageFile,
|
||||
config.baseBranch
|
||||
contentBranch
|
||||
);
|
||||
if (!pFileRaw) {
|
||||
logger.info(
|
||||
|
@ -357,7 +360,7 @@ async function resolvePackageFiles(inputConfig) {
|
|||
if (!inputConfig.ignoreNpmrcFile) {
|
||||
packageFile.npmrc = await config.api.getFileContent(
|
||||
path.join(path.dirname(packageFile.packageFile), '.npmrc'),
|
||||
config.baseBranch
|
||||
contentBranch
|
||||
);
|
||||
}
|
||||
if (!packageFile.npmrc) {
|
||||
|
@ -365,7 +368,7 @@ async function resolvePackageFiles(inputConfig) {
|
|||
}
|
||||
packageFile.yarnrc = await config.api.getFileContent(
|
||||
path.join(path.dirname(packageFile.packageFile), '.yarnrc'),
|
||||
config.baseBranch
|
||||
contentBranch
|
||||
);
|
||||
if (!packageFile.yarnrc) {
|
||||
delete packageFile.yarnrc;
|
||||
|
@ -412,7 +415,7 @@ async function resolvePackageFiles(inputConfig) {
|
|||
);
|
||||
packageFile.yarnLock = await config.api.getFileContent(
|
||||
yarnLockFileName,
|
||||
config.baseBranch
|
||||
contentBranch
|
||||
);
|
||||
if (packageFile.yarnLock) {
|
||||
logger.debug(
|
||||
|
@ -426,7 +429,7 @@ async function resolvePackageFiles(inputConfig) {
|
|||
);
|
||||
packageFile.packageLock = await config.api.getFileContent(
|
||||
packageLockFileName,
|
||||
config.baseBranch
|
||||
contentBranch
|
||||
);
|
||||
if (packageFile.packageLock) {
|
||||
logger.debug(
|
||||
|
@ -446,7 +449,7 @@ async function resolvePackageFiles(inputConfig) {
|
|||
logger.debug(`Resolving packageFile ${JSON.stringify(packageFile)}`);
|
||||
packageFile.content = await config.api.getFileContent(
|
||||
packageFile.packageFile,
|
||||
config.baseBranch
|
||||
contentBranch
|
||||
);
|
||||
const strippedComment = packageFile.content.replace(/^(#.*?\n)+/, '');
|
||||
const fromMatch = strippedComment.match(/^FROM (.*)\n/);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const convertHrTime = require('convert-hrtime');
|
||||
const tmp = require('tmp');
|
||||
const presets = require('../../config/presets');
|
||||
// Workers
|
||||
const branchWorker = require('../branch');
|
||||
// children
|
||||
|
@ -64,6 +63,7 @@ async function renovateRepository(repoConfig, token) {
|
|||
logger.warn(message);
|
||||
}
|
||||
}
|
||||
config = await onboarding.getOnboardingStatus(config);
|
||||
// Detect package files in default branch if not manually provisioned
|
||||
if (config.packageFiles.length === 0) {
|
||||
logger.debug('Detecting package files');
|
||||
|
@ -84,37 +84,6 @@ async function renovateRepository(repoConfig, token) {
|
|||
logger.trace({ config }, 'post-packageFiles config');
|
||||
// TODO: why is this fix needed?!
|
||||
config.logger = logger;
|
||||
config = await onboarding.getOnboardingStatus(config);
|
||||
if (!config.repoIsOnboarded) {
|
||||
config.contentBaseBranch = `${config.branchPrefix}configure`;
|
||||
// Remove packageFile list in case they are provisioned in renovate.json
|
||||
const packageFiles = config.packageFiles.map(
|
||||
packageFile => packageFile.packageFile
|
||||
);
|
||||
config.packageFiles = [];
|
||||
config = await apis.mergeRenovateJson(config, config.contentBaseBranch);
|
||||
// Restore previous packageFile list if not provisioned manually
|
||||
if (config.packageFiles.length === 0) {
|
||||
config.packageFiles = packageFiles;
|
||||
}
|
||||
if (config.baseBranch) {
|
||||
if (await config.api.branchExists(config.baseBranch)) {
|
||||
config.contentBaseBranch = config.baseBranch;
|
||||
} else {
|
||||
const message = `The configured baseBranch "${config.baseBranch}" is not present. Ignoring`;
|
||||
config.errors.push({
|
||||
depName: 'baseBranch',
|
||||
message,
|
||||
});
|
||||
logger.warn(message);
|
||||
}
|
||||
}
|
||||
config = await apis.resolvePackageFiles(config);
|
||||
config = await apis.checkMonorepos(config);
|
||||
config = await presets.resolveConfigPresets(config);
|
||||
config.logger = logger;
|
||||
logger.trace({ config }, 'onboarding config');
|
||||
}
|
||||
config = decryptConfig(config);
|
||||
logger.trace({ config }, 'post-decrypt config');
|
||||
const allUpgrades = await upgrades.determineRepoUpgrades(config);
|
||||
|
@ -168,6 +137,8 @@ async function renovateRepository(repoConfig, token) {
|
|||
// Swallow this error so that other repositories can be processed
|
||||
if (err.message === 'uninitiated') {
|
||||
logger.info('Repository is uninitiated - skipping');
|
||||
} else if (err.message === 'no package files') {
|
||||
logger.info('Repository has no package files - skipping');
|
||||
} else {
|
||||
logger.error(`Failed to process repository: ${err.message}`);
|
||||
logger.debug({ err });
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
const apis = require('./apis');
|
||||
|
||||
const onboardPrTitle = 'Configure Renovate';
|
||||
|
||||
module.exports = {
|
||||
isRepoPrivate,
|
||||
createBranch,
|
||||
createOnboardingBranch,
|
||||
ensurePr,
|
||||
getOnboardingStatus,
|
||||
};
|
||||
|
@ -20,45 +22,48 @@ async function isRepoPrivate(config) {
|
|||
return repoIsPrivate === true;
|
||||
}
|
||||
|
||||
async function createBranch(config) {
|
||||
async function createOnboardingBranch(inputConfig) {
|
||||
let config = { ...inputConfig };
|
||||
const { logger } = config;
|
||||
const onboardBranchName = `${config.branchPrefix}configure`;
|
||||
config.meteor.enabled = true;
|
||||
config.docker.enabled = true;
|
||||
config = await apis.detectPackageFiles(config);
|
||||
if (config.packageFiles.length === 0) {
|
||||
throw new Error('no package files');
|
||||
}
|
||||
const repoIsPrivate = await module.exports.isRepoPrivate(config);
|
||||
let onboardingConfigString;
|
||||
if (repoIsPrivate) {
|
||||
logger.debug('Repo is private - setting to app type');
|
||||
onboardingConfigString = `{\n "extends": ["config:js-app"]\n}\n`;
|
||||
const renovateJson = {
|
||||
extends: [],
|
||||
};
|
||||
if (config.types.npm) {
|
||||
if (repoIsPrivate) {
|
||||
logger.debug('Repo is private - setting to app type');
|
||||
renovateJson.extends.push('config:js-app');
|
||||
} else {
|
||||
logger.debug('Repo is not private - setting to library');
|
||||
renovateJson.extends.push('config:js-lib');
|
||||
}
|
||||
} else {
|
||||
logger.debug('Repo is not private - setting to library');
|
||||
onboardingConfigString = `{\n "extends": ["config:js-lib"]\n}\n`;
|
||||
renovateJson.extends.push('config:base');
|
||||
}
|
||||
const existingContent = await config.api.getFileContent(
|
||||
'renovate.json',
|
||||
onboardBranchName
|
||||
);
|
||||
if (existingContent === onboardingConfigString) {
|
||||
logger.debug('Onboarding branch is already up-to-date');
|
||||
return;
|
||||
if (config.types.meteor) {
|
||||
renovateJson.extends.push(':meteor');
|
||||
}
|
||||
if (existingContent) {
|
||||
logger.debug(
|
||||
{ existingContent, onboardingConfigString },
|
||||
'Updating onboarding branch'
|
||||
);
|
||||
} else {
|
||||
logger.debug('Creating onboarding branch');
|
||||
if (config.types.docker) {
|
||||
renovateJson.extends.push(':docker');
|
||||
}
|
||||
|
||||
logger.info({ renovateJson }, 'Creating onboarding branch');
|
||||
await config.api.commitFilesToBranch(
|
||||
onboardBranchName,
|
||||
config.onboardingBranch,
|
||||
[
|
||||
{
|
||||
name: 'renovate.json',
|
||||
contents: onboardingConfigString,
|
||||
contents: `${JSON.stringify(renovateJson, null, 2)}\n`,
|
||||
},
|
||||
],
|
||||
'Add renovate.json'
|
||||
);
|
||||
return config;
|
||||
}
|
||||
|
||||
async function ensurePr(config, branchUpgrades) {
|
||||
|
@ -229,39 +234,33 @@ With your current configuration, renovate will initially create the following Pu
|
|||
}
|
||||
|
||||
async function getOnboardingStatus(inputConfig) {
|
||||
const config = { ...inputConfig };
|
||||
let config = { ...inputConfig };
|
||||
const { logger } = config;
|
||||
logger.debug('Checking if repo is configured');
|
||||
logger.debug('Checking if repo is onboarded');
|
||||
// Check if repository is configured
|
||||
if (config.onboarding === false) {
|
||||
logger.debug('Repo onboarding is disabled');
|
||||
return { ...config, repoIsOnboarded: true };
|
||||
}
|
||||
if (config.renovateJsonPresent || config.hasPackageJsonRenovateConfig) {
|
||||
logger.debug('Repo has been configured');
|
||||
if (config.renovateJsonPresent) {
|
||||
logger.debug('Repo has renovate.json');
|
||||
return { ...config, repoIsOnboarded: true };
|
||||
}
|
||||
config.onboardingBranch = `${config.branchPrefix}configure`;
|
||||
const pr = await config.api.findPr(
|
||||
`${config.branchPrefix}configure`,
|
||||
config.onboardingBranch,
|
||||
'Configure Renovate'
|
||||
);
|
||||
if (pr) {
|
||||
logger.debug(`Found existing onboarding PR#${pr.number}`);
|
||||
if (pr.isClosed) {
|
||||
logger.debug('Found closed Configure Renovate PR');
|
||||
return { ...config, repoIsOnboarded: true };
|
||||
}
|
||||
// PR exists but hasn't been closed yet
|
||||
logger.debug(
|
||||
`PR #${pr.displayNumber} needs to be closed to enable renovate to continue`
|
||||
);
|
||||
const prDetails = await config.api.getPr(pr.number);
|
||||
if (!prDetails.canRebase) {
|
||||
// Cannot update files if rebasing not possible
|
||||
return { ...config, repoIsOnboarded: false };
|
||||
}
|
||||
if (pr && pr.isClosed) {
|
||||
logger.debug('Found closed Configure Renovate PR');
|
||||
return { ...config, repoIsOnboarded: true };
|
||||
}
|
||||
// Create or update files, then return
|
||||
await module.exports.createBranch(config);
|
||||
if (pr) {
|
||||
logger.debug(`Found existing onboarding PR #${pr.number}`);
|
||||
} else {
|
||||
config = await module.exports.createOnboardingBranch(config);
|
||||
}
|
||||
logger.debug('Merging renovate.json from onboarding branch');
|
||||
config = await apis.mergeRenovateJson(config, config.onboardingBranch);
|
||||
return { ...config, repoIsOnboarded: false };
|
||||
}
|
||||
|
|
|
@ -115,11 +115,9 @@ Array [
|
|||
"content": Object {
|
||||
"workspaces": Array [],
|
||||
},
|
||||
"errors": Array [],
|
||||
"npmrc": "npmrc-1",
|
||||
"packageFile": "package.json",
|
||||
"packageLock": "packageLock-1",
|
||||
"warnings": Array [],
|
||||
"yarnLock": "yarnLock-1",
|
||||
"yarnrc": "yarnrc-1",
|
||||
},
|
||||
|
|
|
@ -374,23 +374,9 @@ Array [
|
|||
Array [
|
||||
Object {
|
||||
"contents": "{
|
||||
\\"extends\\": [\\"config:js-lib\\"]
|
||||
}
|
||||
",
|
||||
"name": "renovate.json",
|
||||
},
|
||||
],
|
||||
"Add renovate.json",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`lib/workers/repository/onboarding getOnboardingStatus(config) commits files if existing content does not match 1`] = `
|
||||
Array [
|
||||
"renovate/configure",
|
||||
Array [
|
||||
Object {
|
||||
"contents": "{
|
||||
\\"extends\\": [\\"config:js-lib\\"]
|
||||
\\"extends\\": [
|
||||
\\"config:js-lib\\"
|
||||
]
|
||||
}
|
||||
",
|
||||
"name": "renovate.json",
|
||||
|
@ -406,7 +392,31 @@ Array [
|
|||
Array [
|
||||
Object {
|
||||
"contents": "{
|
||||
\\"extends\\": [\\"config:js-app\\"]
|
||||
\\"extends\\": [
|
||||
\\"config:js-app\\"
|
||||
]
|
||||
}
|
||||
",
|
||||
"name": "renovate.json",
|
||||
},
|
||||
],
|
||||
"Add renovate.json",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`lib/workers/repository/onboarding getOnboardingStatus(config) throws if no packageFiles 1`] = `[Error: no package files]`;
|
||||
|
||||
exports[`lib/workers/repository/onboarding getOnboardingStatus(config) uses base + docker + meteor 1`] = `
|
||||
Array [
|
||||
"renovate/configure",
|
||||
Array [
|
||||
Object {
|
||||
"contents": "{
|
||||
\\"extends\\": [
|
||||
\\"config:base\\",
|
||||
\\":meteor\\",
|
||||
\\":docker\\"
|
||||
]
|
||||
}
|
||||
",
|
||||
"name": "renovate.json",
|
||||
|
|
|
@ -369,6 +369,7 @@ describe('workers/repository/apis', () => {
|
|||
expect(res.packageFiles).toEqual([]);
|
||||
});
|
||||
it('includes files with content', async () => {
|
||||
config.repoIsOnboarded = true;
|
||||
config.api.getFileContent.mockReturnValueOnce(
|
||||
JSON.stringify({
|
||||
renovate: {},
|
||||
|
|
|
@ -101,7 +101,7 @@ describe('workers/repository', () => {
|
|||
...{ packageFiles: [] },
|
||||
}));
|
||||
await repositoryWorker.renovateRepository(config);
|
||||
expect(onboarding.getOnboardingStatus.mock.calls.length).toBe(0);
|
||||
expect(apis.resolvePackageFiles.mock.calls.length).toBe(0);
|
||||
expect(config.logger.error.mock.calls.length).toBe(0);
|
||||
});
|
||||
it('does not skip repository if package.json', async () => {
|
||||
|
@ -265,5 +265,18 @@ describe('workers/repository', () => {
|
|||
await repositoryWorker.renovateRepository(config);
|
||||
expect(config.logger.error.mock.calls.length).toBe(0);
|
||||
});
|
||||
it('handles special no package files error', async () => {
|
||||
apis.initApis.mockImplementationOnce(() => {
|
||||
// Create a new object, that prototypically inherits from the Error constructor
|
||||
function MyError() {
|
||||
this.message = 'no package files';
|
||||
}
|
||||
MyError.prototype = Object.create(Error.prototype);
|
||||
MyError.prototype.constructor = MyError;
|
||||
throw new MyError();
|
||||
});
|
||||
await repositoryWorker.renovateRepository(config);
|
||||
expect(config.logger.error.mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const onboarding = require('../../../lib/workers/repository/onboarding');
|
||||
const apis = require('../../../lib/workers/repository/apis');
|
||||
const logger = require('../../_fixtures/logger');
|
||||
const defaultConfig = require('../../../lib/config/defaults').getConfig();
|
||||
|
||||
|
@ -225,8 +226,10 @@ describe('lib/workers/repository/onboarding', () => {
|
|||
config.api = {
|
||||
commitFilesToBranch: jest.fn(),
|
||||
createPr: jest.fn(() => ({ displayNumber: 1 })),
|
||||
findFilePaths: jest.fn(() => []),
|
||||
findPr: jest.fn(),
|
||||
getFileContent: jest.fn(),
|
||||
getFileJson: jest.fn(() => ({})),
|
||||
getPr: jest.fn(() => {}),
|
||||
getCommitMessages: jest.fn(),
|
||||
};
|
||||
|
@ -256,17 +259,8 @@ describe('lib/workers/repository/onboarding', () => {
|
|||
expect(config.api.findPr.mock.calls.length).toBe(1);
|
||||
expect(config.api.commitFilesToBranch.mock.calls.length).toBe(0);
|
||||
});
|
||||
it('commits files if pr is not closed and is rebaseable', async () => {
|
||||
config.api.findPr.mockReturnValueOnce({});
|
||||
config.api.getPr.mockReturnValueOnce({ canRebase: true });
|
||||
const res = await onboarding.getOnboardingStatus(config);
|
||||
expect(res.repoIsOnboarded).toEqual(false);
|
||||
expect(config.api.findPr.mock.calls.length).toBe(1);
|
||||
expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1);
|
||||
});
|
||||
it('skips file update if existing pr is not rebaseable', async () => {
|
||||
config.api.findPr.mockReturnValueOnce({});
|
||||
config.api.getPr.mockReturnValueOnce({ canRebase: false });
|
||||
it('skips commit files and returns false if open pr', async () => {
|
||||
config.api.findPr.mockReturnValueOnce({ isClosed: false });
|
||||
const res = await onboarding.getOnboardingStatus(config);
|
||||
expect(res.repoIsOnboarded).toEqual(false);
|
||||
expect(config.api.findPr.mock.calls.length).toBe(1);
|
||||
|
@ -287,21 +281,32 @@ describe('lib/workers/repository/onboarding', () => {
|
|||
expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1);
|
||||
expect(config.api.commitFilesToBranch.mock.calls[0]).toMatchSnapshot();
|
||||
});
|
||||
it('commits files if existing content does not match', async () => {
|
||||
config.api.getFileContent.mockReturnValueOnce('some-different-content');
|
||||
it('uses base + docker + meteor', async () => {
|
||||
apis.detectPackageFiles = jest.fn(input => ({
|
||||
...input,
|
||||
packageFiles: [{}, {}],
|
||||
types: {
|
||||
meteor: true,
|
||||
docker: true,
|
||||
},
|
||||
}));
|
||||
const res = await onboarding.getOnboardingStatus(config);
|
||||
expect(res.repoIsOnboarded).toEqual(false);
|
||||
expect(config.api.findPr.mock.calls.length).toBe(1);
|
||||
expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1);
|
||||
expect(config.api.commitFilesToBranch.mock.calls[0]).toMatchSnapshot();
|
||||
});
|
||||
it('skips commit files if existing content matches', async () => {
|
||||
const existingContent = `{\n "extends": ["config:js-lib"]\n}\n`;
|
||||
config.api.getFileContent.mockReturnValueOnce(existingContent);
|
||||
const res = await onboarding.getOnboardingStatus(config);
|
||||
expect(res.repoIsOnboarded).toEqual(false);
|
||||
expect(config.api.findPr.mock.calls.length).toBe(1);
|
||||
expect(config.api.commitFilesToBranch.mock.calls.length).toBe(0);
|
||||
it('throws if no packageFiles', async () => {
|
||||
apis.detectPackageFiles = jest.fn(input => ({
|
||||
...input,
|
||||
}));
|
||||
let e;
|
||||
try {
|
||||
await onboarding.getOnboardingStatus(config);
|
||||
} catch (err) {
|
||||
e = err;
|
||||
}
|
||||
expect(e).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue