diff --git a/.gitignore b/.gitignore index 47416a0e..29fb909e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ .env +.vscode package-lock.json # jest coverage output diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43c45070..ba17525d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ To run the tests you will need [Docker](https://docs.docker.com/engine/installat ```bash # Install CouchDB on Docker -$ docker pull apache/couchdb:2.1.1 +$ docker pull apache/couchdb:2.3.1 ``` A fake private key in the .env file is needed generate it like this: * Linux diff --git a/content/initial-pr.js b/content/initial-pr.js index d538dc50..ec615f2c 100644 --- a/content/initial-pr.js +++ b/content/initial-pr.js @@ -163,7 +163,7 @@ There is a collection of [frequently asked questions](https://greenkeeper.io/faq // needs to handle files as an array of arrays! function hasLockFileText (files) { if (!files) return - const lockFiles = ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'].filter((key) => { + const lockFiles = ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock', 'pnpm-lock.yaml'].filter((key) => { if (_.isArray(files[key]) && files[key].length) { return true } diff --git a/content/timeout-issue.js b/content/timeout-issue.js index 175a45c8..ae8423bf 100644 --- a/content/timeout-issue.js +++ b/content/timeout-issue.js @@ -6,7 +6,17 @@ module.exports = ({ fullName }) => To enable Greenkeeper, you need to make sure that a [commit status](https://help.github.com/articles/about-statuses/) is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes. -Since we didn’t receive a CI status on the ${branchLink(fullName)} branch, it’s possible that you don’t have CI set up yet. We recommend using [Travis CI](https://travis-ci.com), but Greenkeeper will work with every other CI service as well. +Since we didn’t receive a CI status on the ${branchLink(fullName)} branch, it’s possible that you don’t have CI set up yet. +We recommend using: +- [CircleCI](https://circleci.com) +- [Travis CI](https://travis-ci.com) +- [Buildkite](https://buildkite.com/) +- [CodeShip](https://codeship.com) +- [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines) +- [TeamCity](https://www.jetbrains.com/teamcity) +- [Buddy](https://buddy.works) +- [AppVeyor](https://www.appveyor.com) +But Greenkeeper will work with every other CI service as well. If you _have_ already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with ${md.code('greenkeeper/')}. diff --git a/content/update-pr.js b/content/update-pr.js index 2c5f218b..ade593bd 100644 --- a/content/update-pr.js +++ b/content/update-pr.js @@ -1,7 +1,9 @@ const _ = require('lodash') const md = require('./template') -module.exports = ({ version, dependencyLink, dependency, monorepoGroupName, release, diffCommits, oldVersionResolved, type, packageUpdateList }) => { +module.exports = ({ + version, dependencyLink, dependency, monorepoGroupName, release, diffCommits, oldVersionResolved, type, packageUpdateList, license, licenseHasChanged, previousLicense, publisher +}) => { const hasReleaseInfo = release && diffCommits return md` ${monorepoGroupName @@ -15,11 +17,16 @@ ${monorepoGroupName && `\nThis monorepo update includes releases of one or more } --- +${publisher && `**Publisher:** ${publisher}`} +${license && `**License:** ${licenseHasChanged ? `This package’s license **has changed** from \`${previousLicense}\` to \`${license}\` in this release 🤔` : `${license}`}`} + ${hasReleaseInfo ? _.compact([release, diffCommits]) : `[Find out more about this release](${dependencyLink}).` } +--- +
FAQ and help diff --git a/index.js b/index.js index 61288cce..c597ba5c 100644 --- a/index.js +++ b/index.js @@ -100,8 +100,29 @@ require('./lib/rollbar') setInterval(scheduleReminders, 24 * 60 * 60 * 1000) setInterval(scheduleMonorepoReleaseSupervisor, 5 * 60 * 1000) + const isBad = (data) => { + const values = Object.values(data) + const baddies = ['gatsby', 'material-ui', 'react-cosmos'] + let bad = false + baddies.forEach((baddie) => { + values.forEach((value) => { + if (String(value).match(baddie)) { + bad = true + statsd.increment('jobs.baddie', { tag: baddie }) + } + }) + }) + return bad + } + async function consume (job) { const data = JSON.parse(job.content.toString()) + + if (isBad(data)) { + channel.ack(job) + return + } + const jobsWithoutOwners = ['registry-change', 'stripe-event', 'schedule-stale-initial-pr-reminders', 'reset', 'cancel-stripe-subscription', 'update-nodejs-version', 'deprecate-nodejs-version', 'monorepo-supervisor'] if (jobsWithoutOwners.includes(data.name) || data.type === 'marketplace_purchase') { return queueJob(data.name, job) diff --git a/jobs/create-group-version-branch.js b/jobs/create-group-version-branch.js index ff22e779..a6a37209 100644 --- a/jobs/create-group-version-branch.js +++ b/jobs/create-group-version-branch.js @@ -18,7 +18,8 @@ const { generateGitHubCompareURL, hasTooManyPackageJSONs, getSatisfyingVersions, - getOldVersionResolved + getOldVersionResolved, + getLicenseAndPublisherFromVersions } = require('../utils/utils') const { isPartOfMonorepo, @@ -216,7 +217,7 @@ module.exports = async function ( }) const oldVersionResolved = getOldVersionResolved(satisfyingVersions, npmDoc.distTags, 'latest') if (!oldVersionResolved) { - log.warn(`exited transform creation: could not resolve old version for ${depName} (no update?)`, { newVersion: version, satisfyingVersions, latestDependencyVersion, oldPkgVersion }) + log.info(`exited transform creation: ${depName} ${latestDependencyVersion} is not an update for ${oldPkgVersion}`, { newVersion: version, satisfyingVersions, latestDependencyVersion, oldPkgVersion }) return null } @@ -360,6 +361,9 @@ module.exports = async function ( prTitles: config.prTitles }) const dependencyLink = getFormattedDependencyURL({ repositoryURL: transforms[0].repoURL }) + + const { license, previousLicense, licenseHasChanged, publisher } = getLicenseAndPublisherFromVersions({ versions, version, oldVersionResolved }) + // maybe adapt PR body const body = prContent({ dependencyLink, @@ -370,7 +374,11 @@ module.exports = async function ( type: highestPriorityDependency, release, diffCommits, - packageUpdateList + packageUpdateList, + license, + previousLicense, + licenseHasChanged, + publisher }) // verify pull requests commit diff --git a/jobs/create-version-branch.js b/jobs/create-version-branch.js index a73a840a..b7828ba3 100644 --- a/jobs/create-version-branch.js +++ b/jobs/create-version-branch.js @@ -22,7 +22,8 @@ const { createTransformFunction, generateGitHubCompareURL, hasTooManyPackageJSONs, getSatisfyingVersions, - getOldVersionResolved + getOldVersionResolved, + getLicenseAndPublisherFromVersions } = require('../utils/utils') const prContent = require('../content/update-pr') @@ -179,7 +180,7 @@ module.exports = async function ( }) const oldVersionResolved = getOldVersionResolved(satisfyingVersions, npmDoc.distTags, 'latest') if (!oldVersionResolved) { - log.warn(`exited transform creation: could not resolve old version for ${depName} (no update?)`, { newVersion: version, json, satisfyingVersions, latestDependencyVersion, oldPkgVersion }) + log.info(`exited transform creation: ${depName} ${latestDependencyVersion} is not an update for ${oldPkgVersion}`, { newVersion: version, json, satisfyingVersions, latestDependencyVersion, oldPkgVersion }) return null } @@ -336,6 +337,8 @@ module.exports = async function ( dependency: dependencyKey, prTitles: config.prTitles }) + const { license, previousLicense, licenseHasChanged, publisher } = getLicenseAndPublisherFromVersions({ versions, version, oldVersionResolved }) + const body = prContent({ dependencyLink, oldVersionResolved, @@ -345,7 +348,11 @@ module.exports = async function ( diffCommits, monorepoGroupName, type, - packageUpdateList + packageUpdateList, + license, + previousLicense, + licenseHasChanged, + publisher }) // verify pull requests commit diff --git a/jobs/github-event/push.js b/jobs/github-event/push.js index 5814b125..cd3cd5ec 100644 --- a/jobs/github-event/push.js +++ b/jobs/github-event/push.js @@ -29,6 +29,7 @@ module.exports = async function (data) { 'package.json', 'package-lock.json', 'npm-shrinkwrap.json', + 'pnpm-lock.yaml', 'yarn.lock', 'greenkeeper.json' ] diff --git a/lib/create-branch.js b/lib/create-branch.js index d11d3f02..d63e3a94 100644 --- a/lib/create-branch.js +++ b/lib/create-branch.js @@ -1,49 +1,27 @@ +const micromatch = require('micromatch') +const _ = require('lodash') +const { join } = require('path') + const statsd = require('./statsd') const githubQueue = require('./github-queue') -const _ = require('lodash') const { getNewLockfile } = require('../lib/lockfile') -const { getLockfilePath } = require('../utils/utils') +const { getLockfilePath, compactArray } = require('../utils/utils') const { getMessage } = require('../lib/get-message') const { getGithubFile } = require('../lib/get-files') +const { getExecTokens } = require('../lib/get-exec-tokens') const Log = require('gk-log') const dbs = require('../lib/dbs') global.Promise = require('bluebird') -module.exports = async ( - { - installationId, - newBranch, - branch, - owner, - repoName, - repoDoc, - message, - transforms, - path, - transform, - processLockfiles, - commitMessageTemplates - } -) => { - if (!transforms) transforms = [{ transform, path, message }] - const ghqueue = githubQueue(installationId) +const getCommitsFromTransforms = async (ghqueue, { transforms, owner, repoName, branch }, log) => { let contents = {} - const logs = dbs.getLogsDb() - const log = Log({ - logsDb: logs, - accountId: repoDoc ? repoDoc.accountId : '', - repoSlug: repoDoc ? repoDoc.fullName : '', - context: 'create-branch' }) - - const commits = (await Promise.mapSeries(transforms, async ( - { path, transform, message, create }, - index - ) => { + const commits = (await Promise.mapSeries(transforms, async ({ path, transform, message, create }, index) => { let blob = {} + try { if (contents[path]) { blob.content = contents[path] @@ -69,8 +47,222 @@ module.exports = async ( contents[path] = await transform(oldContent, path) if (!contents[path] || contents[path] === oldContent) return return { path, content: contents[path], message, index } - })).filter(c => c) + })) + return compactArray(commits) +} + +const createLockfileCommits = async ({ commits, repoDoc, installationId, commitMessageTemplates, transforms, owner, repoName, branch }, log) => { + const ghqueue = githubQueue(installationId) + const lockfileCommits = [] + + // we need to iterate over every changed package file, not every package file commit + // we reverse because we want the most recent commit with the all the changes to the file (the last one) + // we clone because we don’t actually want to do the commits backwards + const dedupedCommits = _.uniqBy(_.clone(commits).reverse(), commit => commit.path) + // For yarn workspaces, we have to send the updated packages object (after applying all the update commits). + // So we need to iterate over all commits, replace all updated packages in the packages object, + // send all of them (old and updated together) to the exec server, tell it in which directory to run + // yarn install, and get the old yarn lock from that dir as well + let updatedPackages = _.clone(repoDoc.packages) + let workspaceRootsToUpdate = [] + let packageJsonPathsWithWorkspaceDefinitions = [] + const isYarn = repoDoc.files['yarn.lock'].length > 0 + if (isYarn) { + packageJsonPathsWithWorkspaceDefinitions = Object.keys(repoDoc.packages).filter(path => { + const packageJson = repoDoc.packages[path] + const workspaceDefinition = packageJson.workspaces + // either has simple workspace definition… + if (workspaceDefinition && workspaceDefinition.length > 0) { + return path + } + // or a complex definition + if (workspaceDefinition && workspaceDefinition.packages && workspaceDefinition.packages.length > 0) { + return path + } + }) + } + const execTokens = await getExecTokens({ + installationId, + repoDoc + }, log) + // all commits of a workspaceRoot + // all commits of no workspace + for (const commit of dedupedCommits) { + // continue skips the current iteration but continues with the for loop as a whole + if (!commit.path.includes('package.json')) continue + const packageJsonPath = commit.path + const lockfilePath = getLockfilePath(repoDoc.files, packageJsonPath) + if (isYarn) { + // Iterate though all workspace definitions until we find a workspace path/glob that fits our commit.path + const workspaceRootPath = getWorkspaceRootPathForCommit({ + workspaceRootPaths: packageJsonPathsWithWorkspaceDefinitions, + repoDoc, + commit + }) + + if (workspaceRootPath) { + // Record that we need to run yarn install in this directory + if (!workspaceRootsToUpdate.includes(workspaceRootPath)) workspaceRootsToUpdate.push(workspaceRootPath) + // Update our current packages object with this new, updated package + updatedPackages[commit.path] = JSON.parse(commit.content) + // skip the rest of the loop for this commit, we don’t want a lockfile per file, we’ll make a new + // lockfile per workspace in a separate loop later + continue + } else { + // if this repo has workspaces, but this package json isn’t in one, do not get a lockfile for it + continue + } + } + if (!lockfilePath) continue + const lockfileCommit = await updateLockfile({ + transforms, + lockfilePath, + ghqueue, + owner, + repoName, + branch, + packageJson: commit.content, + execTokens, + commitMessageTemplates, + log + }) + if (lockfileCommit) { + lockfileCommits.push({ ...lockfileCommit, index: commits.length + lockfileCommits.length }) + } + } // done iterating over all commits + // Loop through all workspaces and get a lockfile for each of them + for (const workspaceRoot of workspaceRootsToUpdate) { + const lockfileCommit = await updateLockfile({ + transforms, + lockfilePath: workspaceRoot.replace('package.json', 'yarn.lock'), + packages: updatedPackages, + workspaceRoot, + ghqueue, + owner, + repoName, + branch, + execTokens, + commitMessageTemplates, + log + }) + if (lockfileCommit) { + lockfileCommits.push({ ...lockfileCommit, index: commits.length + lockfileCommits.length }) + } + } + return lockfileCommits +} + +const updateLockfile = async ({ + transforms, + lockfilePath, + ghqueue, + owner, + repoName, + branch, + packageJson, + execTokens, + commitMessageTemplates, + packages, // optional, for yarn workspaces + workspaceRoot, // optional, for yarn workspaces + log +}) => { + // Get versions for logging + const versions = transforms.map(transform => { + return { + dependency: transform.dependency, + version: transform.version, + oldVersion: transform.oldVersion + } + }) + if (workspaceRoot) { + log.info('starting yarn workspace lockfile update', { lockfilePath, workspaceRoot, versions }) + } else { + log.info('starting single-file lockfile update', { lockfilePath, versions }) + } + const oldLockfile = await getGithubFile(ghqueue, { path: lockfilePath, owner, repo: repoName, sha: branch }, log) + const oldLockfileContent = Buffer.from(oldLockfile.content, 'base64').toString() + + try { + let type = 'npm' + if (lockfilePath.includes('pnpm-lock.yaml')) type = 'pnpm' + if (lockfilePath.includes('yarn.lock')) type = 'yarn' + const { ok, contents, error } = await getNewLockfile({ packageJson, packages, workspaceRoot, lock: oldLockfileContent, type, repositoryTokens: execTokens }) + if (ok) { + // !ok means the old and new lockfile are the same, so we don’t make a commit + log.info(`new lockfile contents for ${lockfilePath} received`) + statsd.increment('lockfiles') + + const lockfileCommitMessage = getMessage(commitMessageTemplates, 'lockfileUpdate', { lockfilePath: lockfilePath }) + log.info(`created lockfile commit for ${lockfilePath}`, { lockfileCommitMessage }) + return { + path: lockfilePath, + content: contents, + message: lockfileCommitMessage + } + } else { // ok: false + log.error(`error building lockfile for ${lockfilePath}`, { error }) + return undefined + } + } catch (e) { + log.error('error fetching updated lockfile from exec server', { e: e.error || e }) + return undefined + } +} + +const getWorkspaceRootPathForCommit = ({ + workspaceRootPaths: packageJsonPathsWithWorkspaceDefinitions, + repoDoc, + commit +}) => { + return packageJsonPathsWithWorkspaceDefinitions.find(packageJsonPath => { + // the workspaceRoot itself is also always part of the workspace + if (commit.path === packageJsonPath) return true + + const rootPath = packageJsonPath.replace('package.json', '') + // Get the packageJson with the workspace definitions + const packageJson = repoDoc.packages[packageJsonPath] + // Get workspace definitions, can be directly in `workspaces` or `workspaces.packages` + // workspaceDefinitions will be sth like `[ 'jobs/*', 'docs' ]` + const workspaceDefinitions = packageJson.workspaces && packageJson.workspaces.packages ? packageJson.workspaces.packages : packageJson.workspaces + const matchingPaths = workspaceDefinitions.map(definition => { + // Join the definition PJ path with the definition path to get the absolute definition paths from the repo root, + // not the workspace root + return join(rootPath, definition) + }) + // Figure out whether this workspace definition includes the PJ we’re updating + const commitPathWithoutFilename = commit.path.replace('/package.json', '') + const isParentWorkspaceOfPackageJson = micromatch.isMatch(commitPathWithoutFilename, matchingPaths) + return isParentWorkspaceOfPackageJson + }) +} + +module.exports = async ( + { + installationId, + newBranch, + branch, + owner, + repoName, + repoDoc, + message, + transforms, + path, + transform, + processLockfiles, + commitMessageTemplates + } +) => { + if (!transforms) transforms = [{ transform, path, message }] + const ghqueue = githubQueue(installationId) + + const logs = dbs.getLogsDb() + const log = Log({ + logsDb: logs, + accountId: repoDoc ? repoDoc.accountId : '', + repoSlug: repoDoc ? repoDoc.fullName : '', + context: 'create-branch' }) + let commits = await getCommitsFromTransforms(ghqueue, { transforms, owner, repoName, branch }, log) if (commits.length === 0) return /* @@ -89,88 +281,17 @@ module.exports = async ( */ if (processLockfiles && repoDoc && repoDoc.files) { - // we need to iterate over every changed package file, not every package file commit - // we reverse because we want the most recent commit with the all the changes to the file (the last one) - // we clone because we don’t actually want to do the commits backwards - const dedupedCommits = _.uniqBy(_.clone(commits).reverse(), commit => commit.path) - for (const commit of dedupedCommits) { - // continue skips the current iteration but continues with the for loop as a whole - if (!commit.path.includes('package.json')) continue - const lockfilePath = getLockfilePath(repoDoc.files, commit.path) - - if (!lockfilePath) continue - const versions = transforms.map(transform => { - return { - dependency: transform.dependency, - version: transform.version, - oldVersion: transform.oldVersion - } - }) - log.info('starting lockfile update', { lockfilePath, versions }) - - const oldLockfile = await getGithubFile(ghqueue, { path: lockfilePath, owner, repo: repoName, sha: branch }, log) - const oldLockfileContent = Buffer.from(oldLockfile.content, 'base64').toString() - - const isNpm = lockfilePath.includes('package-lock.json') - try { - const { tokens, 'token-audits': tokenAudits } = await dbs() // eslint-disable-line - - /* - This is the structure of the tokens 'model' - _id: `${accountId} - tokens: { - ${repoId}: { - npm: ${token}, - github: ${token} - } - } - */ - let execTokens = '' - let repositoryTokens = '' - try { - repositoryTokens = await tokens.get(repoDoc.accountId) - } catch (error) { - log.error(`Unable to get repository token`, { lockfilePath, error }) - } - - if (repositoryTokens) { - execTokens = JSON.stringify(repositoryTokens.tokens[repoDoc._id]) - const datetime = new Date().toISOString().substr(0, 19).replace(/[^0-9]/g, '') - - // write audit log entry to 'token-audits' db - // log entry type: 'read' - try { - await tokenAudits.put({ - _id: `${installationId}:${repoDoc._id}:${datetime}:read`, - keys: Object.keys(repositoryTokens.tokens[repoDoc._id]) - }) - } catch (error) { - log.error(`Unable to store token audit log`, { lockfilePath, error }) - } - } - - const { ok, contents, error } = await getNewLockfile({ packageJson: commit.content, lock: oldLockfileContent, isNpm, repositoryTokens: execTokens }) - if (ok) { - // !ok means the old and new lockfile are the same, so we don’t make a commit - log.info(`new lockfile contents for ${lockfilePath} received`) - statsd.increment('lockfiles') - - const lockfileCommitMessage = getMessage(commitMessageTemplates, 'lockfileUpdate', { lockfilePath: lockfilePath }) - - commits.push({ - path: lockfilePath, - content: contents, - message: lockfileCommitMessage, - index: commits.length - }) - log.info(`created lockfile commit for ${lockfilePath}`, { lockfileCommitMessage }) - } else { // ok: false - log.info(`error building lockfile for ${lockfilePath}`, { error }) - } - } catch (e) { - log.error('error fetching updated lockfile from exec server', { e: e.error || e }) - } - } + const lockfileCommits = await createLockfileCommits( + { commits, + repoDoc, + installationId, + commitMessageTemplates, + transforms, + owner, + repoName, + branch }, + log) + commits = [...commits, ...lockfileCommits] } const head = await ghqueue.read(github => github.gitdata.getRef({ diff --git a/lib/get-exec-tokens.js b/lib/get-exec-tokens.js new file mode 100644 index 00000000..da02dac7 --- /dev/null +++ b/lib/get-exec-tokens.js @@ -0,0 +1,55 @@ +async function getExecTokens ({ + installationId, + repoDoc +}, log) { + try { + const dbs = require('./dbs') + const { tokens, 'token-audits': tokenAudits } = await dbs() // eslint-disable-line + + /* + This is the structure of the tokens 'model' + _id: `${accountId} + tokens: { + ${repoId}: { + npm: ${token}, + github: ${token} + } + } + */ + let execTokens = '' + let repositoryTokens = '' + try { + repositoryTokens = await tokens.get(repoDoc.accountId) + log.info('repository tokens received') + } catch (error) { + if (error.status === 404) { + log.info(`No repository token set`, { error }) + } else { + log.error(`Unable to get repository token`, { error }) + } + } + + if (repositoryTokens && repositoryTokens.tokens[repoDoc._id]) { + execTokens = JSON.stringify(repositoryTokens.tokens[repoDoc._id]) + const datetime = new Date().toISOString().substr(0, 19).replace(/[^0-9]/g, '') + + // write audit log entry to 'token-audits' db + // log entry type: 'read' + try { + await tokenAudits.put({ + _id: `${installationId}:${repoDoc._id}:${datetime}:read`, + keys: Object.keys(repositoryTokens.tokens[repoDoc._id]) + }) + } catch (error) { + log.error(`Unable to store token audit log`, { installationId, repositoryId: repoDoc._id, error }) + } + } + return execTokens + } catch (error) { + log.error(`Error while fetching repo tokens`, { installationId, repositoryId: repoDoc._id, error }) + } +} + +module.exports = { + getExecTokens +} diff --git a/lib/get-files.js b/lib/get-files.js index 749bbf23..f3711090 100644 --- a/lib/get-files.js +++ b/lib/get-files.js @@ -7,13 +7,13 @@ const fileList = [ 'package.json', 'package-lock.json', 'npm-shrinkwrap.json', + 'pnpm-lock.yaml', 'yarn.lock' ] async function getGithubFile (ghqueue, { path, owner, repo, sha, ref }, log) { let name = path if (path.includes('/')) name = path.split('/')[1] - try { statsd.increment('get_github_file_small', { tag: name }) if (ref) { @@ -92,6 +92,7 @@ function addRelatedLockfilePaths (files) { files.map(path => { relatedLockfilePaths.push(path.replace('package.json', 'package-lock.json')) relatedLockfilePaths.push(path.replace('package.json', 'yarn.lock')) + relatedLockfilePaths.push(path.replace('package.json', 'pnpm-lock.yaml')) relatedLockfilePaths.push(path.replace('package.json', 'npm-shrinkwrap.json')) }) return relatedLockfilePaths @@ -182,7 +183,8 @@ function formatPackageJson (content) { 'greenkeeper', 'engines', 'maintainers', - 'author' + 'author', + 'workspaces' ]) }) diff --git a/lib/lockfile.js b/lib/lockfile.js index 3e5f482d..4c4d1b44 100644 --- a/lib/lockfile.js +++ b/lib/lockfile.js @@ -40,7 +40,13 @@ async function findNextServer () { }) return sortedServers[0] } -async function getNewLockfile ({ packageJson, lock, isNpm, repositoryTokens }) { +async function getNewLockfile ({ + packageJson, + packages, + workspaceRoot, + lock, + type, + repositoryTokens }) { const logs = dbs.getLogsDb() const log = Log({ logsDb: logs, @@ -48,7 +54,6 @@ async function getNewLockfile ({ packageJson, lock, isNpm, repositoryTokens }) { repoSlug: null, context: 'lockfile' }) - const type = isNpm ? 'npm' : 'yarn' const nextServer = await findNextServer() jobCountByServer[nextServer] = jobCountByServer[nextServer] ? jobCountByServer[nextServer] + 1 : 1 return promiseRetry((retry, number) => { @@ -59,13 +64,21 @@ async function getNewLockfile ({ packageJson, lock, isNpm, repositoryTokens }) { body: { type, packageJson, + packages, + workspaceRoot, lock, repositoryTokens } }) .then(result => { jobCountByServer[nextServer]-- - return result + if ((result instanceof Error) || result.message) { + // result is either `error`, `{ok: false}`, `{ok: false, error}` or `{ok: true, contents}` + const error = result + throw error + } else { + return result + } }) .catch(error => { if (number >= 3) { diff --git a/lib/repository-docs.js b/lib/repository-docs.js index d52a9382..88e307d2 100644 --- a/lib/repository-docs.js +++ b/lib/repository-docs.js @@ -38,7 +38,8 @@ async function updateRepoDoc ({ installationId, doc, filePaths, log }) { 'package.json': [], 'package-lock.json': [], 'yarn.lock': [], - 'npm-shrinkwrap.json': [] + 'npm-shrinkwrap.json': [], + 'pnpm-lock.yaml': [] } let filePathsFromConfig = [] diff --git a/lib/worker.js b/lib/worker.js index 94f69dba..add80e65 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -84,7 +84,7 @@ module.exports = async function worker (scheduleJob, channel, job) { channel.nack(job) } - const error = typeof err === 'object' && !(err instanceof Error) + const error = typeof err === 'object' && !(err instanceof Error) && err.message ? new Error(err.message) : err diff --git a/package.json b/package.json index 125c484c..881b03c5 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,17 @@ "name": "@greenkeeper/jobs", "version": "0.0.0-development", "dependencies": { - "@octokit/rest": "16.14.1", + "@octokit/rest": "16.19.0", "amqplib": "^0.5.0", "bluebird": "^3.4.6", "catbox": "^7.1.3", "catbox-memory": "^2.0.4", "couchdb-bootstrap": "14.1.1", "envalid": "^5.0.0", - "escape-string-regexp": "^1.0.5", + "escape-string-regexp": "^2.0.0", "github-url-from-git": "^1.4.0", "gk-log": "1.5.0", - "greenkeeper-monorepo-definitions": "^1.14.1", + "greenkeeper-monorepo-definitions": "^1.19.1", "hot-shots": "^5.0.0", "joi": "^14.0.0", "js-yaml": "^3.7.0", @@ -20,6 +20,7 @@ "jsonwebtoken": "^8.1.1", "lodash": "^4.17.10", "mergejson": "^1.0.30", + "micromatch": "^4.0.2", "nodemailer": "^6.0.0", "npm-registry-client": "^8.3.0", "pouchdb-http": "^6.0.2", @@ -40,7 +41,7 @@ }, "devDependencies": { "jest": "^22.4.2", - "lolex": "^3.0.0", + "lolex": "^4.0.1", "nock": "^10.0.0", "prettier-standard-formatter": "^0.222222222222222.333333333333333", "simple-mock": "^0.8.0", diff --git a/start-couchdb b/start-couchdb index 90b62139..66024d1b 100755 --- a/start-couchdb +++ b/start-couchdb @@ -10,7 +10,7 @@ if [[ -n "$GK_COUCHDB" ]]; then else echo "Starting CouchDB via Docker" docker rm -fv gk-couchdb || true - docker run -d -p 5984:5984 --name gk-couchdb apache/couchdb:2.1.1 + docker run -d -p 5984:5984 --name gk-couchdb apache/couchdb:2.3.1 fi TIMEOUT=280 diff --git a/test/jobs/__snapshots__/create-group-version-branch.js.snap b/test/jobs/__snapshots__/create-group-version-branch.js.snap index 0f3a21d5..7a33f755 100644 --- a/test/jobs/__snapshots__/create-group-version-branch.js.snap +++ b/test/jobs/__snapshots__/create-group-version-branch.js.snap @@ -6,9 +6,9 @@ Object { "body": " ## There have been updates to the *pouchdb* monorepo: -- The \`dependency\` [pouchdb](https://www.npmjs.com/package/pouchdb) was updated from \`1.0.0\` to \`2.0.0\`. -- The \`dependency\` [pouchdb-adapter-utils](https://www.npmjs.com/package/pouchdb-adapter-utils) was updated from \`1.0.0\` to \`2.0.0\`. -- The \`devDependency\` [pouchdb-core](https://www.npmjs.com/package/pouchdb-core) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`dependency\` [pouchdb](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`dependency\` [pouchdb-adapter-utils](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`devDependency\` [pouchdb-core](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. These versions are **not covered** by your **current version range**. @@ -18,7 +18,12 @@ This monorepo update includes releases of one or more dependencies which all bel --- -[Find out more about this release](). +**Publisher:** finn +**License:** MIT + +[Find out more about this release](https://github.com/dep/dep). + +---
FAQ and help @@ -43,7 +48,7 @@ Object { "body": " ## There have been updates to the *pouchdb* monorepo: -- The \`dependency\` [pouchdb](https://www.npmjs.com/package/pouchdb) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`dependency\` [pouchdb](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. These versions are **not covered** by your **current version range**. @@ -53,7 +58,12 @@ This monorepo update includes releases of one or more dependencies which all bel --- -[Find out more about this release](). +**Publisher:** finn +**License:** MIT + +[Find out more about this release](https://github.com/dep/dep). + +---
FAQ and help @@ -78,9 +88,9 @@ Object { "body": " ## There have been updates to the *pouchdb* monorepo: -- The \`dependency\` [pouchdb](https://www.npmjs.com/package/pouchdb) was updated from \`1.0.0\` to \`2.0.0\`. -- The \`dependency\` [pouchdb-adapter-utils](https://www.npmjs.com/package/pouchdb-adapter-utils) was updated from \`1.0.0\` to \`2.0.0\`. -- The \`devDependency\` [pouchdb-browser](https://www.npmjs.com/package/pouchdb-browser) was updated from \`1.0.0\` to \`1.8.0\`. +- The \`dependency\` [pouchdb](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`dependency\` [pouchdb-adapter-utils](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`devDependency\` [pouchdb-browser](https://github.com/dep/dep) was updated from \`1.0.0\` to \`1.8.0\`. These versions are **not covered** by your **current version range**. @@ -90,7 +100,12 @@ This monorepo update includes releases of one or more dependencies which all bel --- -[Find out more about this release](). +**Publisher:** finn +**License:** MIT + +[Find out more about this release](https://github.com/dep/dep). + +---
FAQ and help @@ -115,7 +130,7 @@ Object { "body": " ## There have been updates to the *react* monorepo: -- The \`dependency\` [react](https://www.npmjs.com/package/react) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`dependency\` [react](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. These versions are **not covered** by your **current version range**. @@ -125,7 +140,12 @@ This monorepo update includes releases of one or more dependencies which all bel --- -[Find out more about this release](). +**Publisher:** finn +**License:** MIT + +[Find out more about this release](https://github.com/dep/dep). + +---
FAQ and help @@ -150,7 +170,7 @@ Object { "body": " ## There have been updates to the *react* monorepo: -- The \`dependency\` [react](https://www.npmjs.com/package/react) was updated from \`1.0.0\` to \`2.0.0\`. +- The \`dependency\` [react](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. These versions are **not covered** by your **current version range**. @@ -160,7 +180,12 @@ This monorepo update includes releases of one or more dependencies which all bel --- -[Find out more about this release](). +**Publisher:** finn +**License:** MIT + +[Find out more about this release](https://github.com/dep/dep). + +---
FAQ and help @@ -186,7 +211,7 @@ Array [ `; exports[`create-group-version-branch no new pull request, but new comment on PR, 1 group, 2 packages, same dependencyType, old PR exists 1`] = ` -"- The \`dependency\` [react](https://www.npmjs.com/package/react) was updated from \`1.0.0\` to \`2.0.0\`. +"- The \`dependency\` [react](https://github.com/dep/dep) was updated from \`1.0.0\` to \`2.0.0\`. [Update to these versions instead 🚀](https://github.com/hans/monorepo/compare/master...hans:greenkeeper%2Fdefault%2Fmonorepo.react-20190330170013)" `; @@ -210,8 +235,13 @@ If you don’t accept this pull request, your project will work just like it did --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](). +--- +
FAQ and help diff --git a/test/jobs/__snapshots__/create-version-branch.js.snap b/test/jobs/__snapshots__/create-version-branch.js.snap index 6518ddaf..6f9d5428 100644 --- a/test/jobs/__snapshots__/create-version-branch.js.snap +++ b/test/jobs/__snapshots__/create-version-branch.js.snap @@ -27,8 +27,13 @@ This monorepo update includes releases of one or more dependencies which all bel --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/orgname/colors). +--- +
FAQ and help @@ -47,8 +52,8 @@ exports[`create version branch for dependencies from monorepos new pull request " ## There have been updates to the *numbers* monorepo: -- The \`devDependency\` [numbers](https://www.npmjs.com/package/numbers) was updated from \`2.1.0\` to \`2.2.0\`. -- The \`dependency\` [numbers-four](https://www.npmjs.com/package/numbers-four) was updated from \`1.2.0\` to \`1.3.0\`. +- The \`devDependency\` [numbers](https://github.com/dep/dep) was updated from \`2.1.0\` to \`2.2.0\`. +- The \`dependency\` [numbers-four](https://github.com/dep/dep) was updated from \`1.2.0\` to \`1.3.0\`. These versions are **not covered** by your **current version range**. @@ -58,7 +63,12 @@ This monorepo update includes releases of one or more dependencies which all bel --- -[Find out more about this release](). +**Publisher:** finn + + +[Find out more about this release](https://github.com/dep/dep). + +---
FAQ and help @@ -91,8 +101,13 @@ This monorepo update includes releases of one or more dependencies which all bel --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/orgname/flowers). +--- +
FAQ and help @@ -121,8 +136,13 @@ This monorepo update includes releases of one or more dependencies which all bel --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/orgname/colors). +--- +
FAQ and help @@ -153,8 +173,13 @@ This monorepo update includes releases of one or more dependencies which all bel --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/jest/jest). +--- +
FAQ and help @@ -180,8 +205,13 @@ If you don’t accept this pull request, your project will work just like it did --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/best/best). +--- +
FAQ and help @@ -205,8 +235,13 @@ If you don’t accept this pull request, your project will work just like it did --- +**Publisher:** finn +**License:** This package’s license **has changed** from \`BYOL\` to \`MIT\` in this release 🤔 + [Find out more about this release](https://github.com/finnpauls/dep). +--- +
FAQ and help @@ -230,8 +265,13 @@ If you don’t accept this pull request, your project will work just like it did --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/finnpauls/dep). +--- +
FAQ and help @@ -255,8 +295,13 @@ If you don’t accept this pull request, your project will work just like it did --- +**Publisher:** finn +**License:** MIT + [Find out more about this release](https://github.com/finnpauls/dep). +--- +
FAQ and help diff --git a/test/jobs/create-group-version-branch.js b/test/jobs/create-group-version-branch.js index b93669f1..b364144e 100644 --- a/test/jobs/create-group-version-branch.js +++ b/test/jobs/create-group-version-branch.js @@ -5,6 +5,18 @@ const removeIfExists = require('../helpers/remove-if-exists') nock.disableNetConnect() nock.enableNetConnect('localhost') +const versionInfos = { + 'repository': { + type: 'git', + url: 'git+https://github.com/dep/dep.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } +} + describe('create-group-version-branch', async () => { beforeEach(() => { jest.resetModules() @@ -20,8 +32,8 @@ describe('create-group-version-branch', async () => { latest: '2.0.0' }, versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) await npm.put({ @@ -30,9 +42,9 @@ describe('create-group-version-branch', async () => { latest: '2.0.1' }, versions: { - '1.0.0': {}, - '2.0.0': {}, - '2.0.1': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos, + '2.0.1': versionInfos } }) await npm.put({ @@ -41,8 +53,8 @@ describe('create-group-version-branch', async () => { latest: '2.0.0' }, versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) await npm.put({ @@ -51,8 +63,8 @@ describe('create-group-version-branch', async () => { latest: '2.0.0' }, versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) await npm.put({ @@ -61,8 +73,8 @@ describe('create-group-version-branch', async () => { latest: '2.0.0' }, versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) await npm.put({ @@ -71,8 +83,8 @@ describe('create-group-version-branch', async () => { latest: '1.8.0' }, versions: { - '1.0.0': {}, - '1.8.0': {} + '1.0.0': versionInfos, + '1.8.0': versionInfos } }) @@ -378,8 +390,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -495,8 +507,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -613,8 +625,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'backend': { @@ -673,8 +685,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -757,8 +769,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0-prerelease': {} + '1.0.0': versionInfos, + '2.0.0-prerelease': versionInfos }, group: { 'default': { @@ -860,8 +872,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -963,9 +975,9 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {}, - '2.0.1': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos, + '2.0.1': versionInfos }, group: { 'default': { @@ -1112,8 +1124,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -1295,8 +1307,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -1417,8 +1429,8 @@ describe('create-group-version-branch', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -1615,8 +1627,8 @@ describe('create-group-version-branch with lockfiles', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos }, group: { 'default': { @@ -1708,8 +1720,8 @@ describe('create-group-version-branch with lockfiles', async () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '1.1.0': {} + '1.0.0': versionInfos, + '1.1.0': versionInfos }, group: { 'default': { diff --git a/test/jobs/create-version-branch.js b/test/jobs/create-version-branch.js index f9ed267e..9d328fd4 100644 --- a/test/jobs/create-version-branch.js +++ b/test/jobs/create-version-branch.js @@ -9,6 +9,19 @@ nock.disableNetConnect() nock.enableNetConnect('localhost:5984') let defaultPrivateKey = process.env.PRIVATE_KEY + +const versionInfos = { + 'repository': { + type: 'git', + url: 'git+https://github.com/dep/dep.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } +} + describe('create version branch', () => { beforeEach(() => { delete process.env.IS_ENTERPRISE @@ -268,8 +281,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': { ...versionInfos, license: 'BYOL' }, + '2.0.0': versionInfos } }) @@ -388,8 +401,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) @@ -509,8 +522,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) @@ -580,8 +593,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) @@ -642,8 +655,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) @@ -703,8 +716,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0-beta': {} + '1.0.0': versionInfos, + '2.0.0-beta': versionInfos } }) @@ -787,8 +800,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) @@ -851,9 +864,9 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '1.1.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '1.1.0': versionInfos, + '2.0.0': versionInfos } }) @@ -915,8 +928,8 @@ describe('create version branch', () => { oldVersion: '^2.0.1', oldVersionResolved: '2.0.1', versions: { - '1.0.0': {}, - '2.0.1': {} + '1.0.0': versionInfos, + '2.0.1': versionInfos } }) // no new job scheduled @@ -1022,8 +1035,8 @@ describe('create version branch', () => { oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { - '1.0.0': {}, - '2.0.1': {} + '1.0.0': versionInfos, + '2.0.1': versionInfos } }) // no new job scheduled @@ -1042,8 +1055,8 @@ describe('create version branch', () => { oldVersion: '^2.0.1', oldVersionResolved: '2.0.1', versions: { - '1.0.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '2.0.0': versionInfos } }) // no new job scheduled @@ -1124,7 +1137,12 @@ describe('create version branch', () => { repositoryId: '51', type: 'devDependencies', version: '2.0.2', - oldVersion: '1.3.0' + oldVersion: '^1.3.0', + oldVersionResolved: '1.3.0', + versions: { + '1.0.0': versionInfos, + '2.0.0': versionInfos + } }) expect(githubMock.isDone()).toBeTruthy() @@ -1158,7 +1176,12 @@ describe('create version branch', () => { accountId: '2323', repositoryId: '47', version: '1.0.1', - oldVersion: '^1.0.0' + oldVersion: '^1.0.0', + oldVersionResolved: '1.0.0', + versions: { + '1.0.0': versionInfos, + '2.0.0': versionInfos + } }) // no new job scheduled @@ -1196,18 +1219,33 @@ describe('create version branch', () => { 'repository': { 'type': 'git', 'url': 'git+https://github.com/jest/jest.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '1.1.1': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/jest/jest.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '1.2.0': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/jest/jest.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -1222,7 +1260,7 @@ describe('create version branch', () => { }) .get('/rate_limit') .optionally() - .reply(200, {}) + .reply(200) .get('/repos/espy/test') .reply(200, { default_branch: 'master' @@ -1279,8 +1317,9 @@ describe('create version branch', () => { oldVersion: '1.1.1', oldVersionResolved: '1.1.1', versions: { - '1.0.0': {}, - '1.1.1': {} + '1.0.0': versionInfos, + '1.1.1': versionInfos, + '1.2.0': versionInfos } }) @@ -1378,9 +1417,9 @@ describe('create version branch', () => { oldVersion: '^2.0.0', oldVersionResolved: '2.0.0', versions: { - '1.0.0': {}, - '1.1.0': {}, - '2.0.0': {} + '1.0.0': versionInfos, + '1.1.0': versionInfos, + '2.0.0': versionInfos } }) @@ -1423,18 +1462,33 @@ describe('create version branch', () => { 'repository': { 'type': 'git', 'url': 'git+https://github.com/best/best.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '1.1.1': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/best/best.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '1.2.0': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/best/best.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -1501,11 +1555,12 @@ describe('create version branch', () => { repositoryId: '50_cvb_lockfile', type: 'devDependencies', version: '1.2.0', - oldVersion: '1.1.1', + oldVersion: '^1.1.1', oldVersionResolved: '1.1.1', versions: { - '1.0.0': {}, - '1.1.1': {} + '1.0.0': versionInfos, + '1.1.1': versionInfos, + '1.2.0': versionInfos } }) @@ -1561,7 +1616,12 @@ describe('create version branch', () => { oldVersionResolved: '5.5.5', versions: { '5.5.6': { - gitHead: 'deadbeef222' + gitHead: 'deadbeef222', + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } } } }) @@ -1789,12 +1849,22 @@ describe('create version branch for dependencies from monorepos', () => { oldVersionResolved: '1.0.0', versions: { '1.0.0': { - gitHead: 'deadbeef100' + gitHead: 'deadbeef100', + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } }, '2.0.0': { gitHead: 'deadbeef222', repository: { url: 'https://github.com/colors/monorepo' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -1920,12 +1990,22 @@ describe('create version branch for dependencies from monorepos', () => { oldVersionResolved: '1.0.0', versions: { '1.0.0': { - gitHead: 'deadbeef100' + gitHead: 'deadbeef100', + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } }, '2.0.0': { gitHead: 'deadbeef222', repository: { url: 'https://github.com/colors/monorepo' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -1974,8 +2054,8 @@ describe('create version branch for dependencies from monorepos', () => { latest: '2.2.0' }, versions: { - '2.1.0': {}, - '2.2.0': {} + '2.1.0': versionInfos, + '2.2.0': versionInfos } }) await npm.put({ @@ -1985,10 +2065,10 @@ describe('create version branch for dependencies from monorepos', () => { latest: '1.5.0' }, versions: { - '1.2.0': {}, - '1.3.0': {}, - '1.4.0': {}, - '1.5.0': {} + '1.2.0': versionInfos, + '1.3.0': versionInfos, + '1.4.0': versionInfos, + '1.5.0': versionInfos } }) await npm.put({ @@ -1998,8 +2078,8 @@ describe('create version branch for dependencies from monorepos', () => { latest: '1.3.0' }, versions: { - '1.2.0': {}, - '1.3.0': {} + '1.2.0': versionInfos, + '1.3.0': versionInfos } }) expect.assertions(11) @@ -2084,17 +2164,34 @@ describe('create version branch for dependencies from monorepos', () => { repositoryId: 'mono-deps-diff', type: 'dependencies', version: '2.2.0', - - oldVersion: '2.1.0', + oldVersion: '^2.1.0', oldVersionResolved: '2.1.0', versions: { '1.0.0': { - gitHead: 'deadbeef100' + gitHead: 'deadbeef100', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } }, '2.1.0': { gitHead: 'deadbeef222', repository: { url: 'https://github.com/numbers/monorepo' + }, + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + }, + '2.2.0': { + gitHead: 'deadbeef220', + repository: { + url: 'https://github.com/numbers/monorepo' + }, + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -2238,23 +2335,37 @@ describe('create version branch for dependencies from monorepos', () => { repositoryId: 'mono-2', type: 'dependencies', version: '2.0.0', - oldVersion: '^1.0.0', oldVersionResolved: '1.0.0', versions: { '1.0.0': { - gitHead: 'deadbeef100' + gitHead: 'deadbeef100', + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } }, '2.0.0': { gitHead: 'deadbeef222', repository: { url: 'https://github.com/colors/monorepo' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '3.0.0': { gitHead: 'deadbeef333', repository: { url: 'https://github.com/colors/monorepo' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -2301,18 +2412,33 @@ describe('create version branch for dependencies from monorepos', () => { 'repository': { 'type': 'git', 'url': 'git+https://github.com/airbnb/enzyme.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '3.4.3': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/airbnb/enzyme.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '3.4.4': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/airbnb/enzyme.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -2328,12 +2454,22 @@ describe('create version branch for dependencies from monorepos', () => { 'repository': { 'type': 'git', 'url': 'git+https://github.com/airbnb/enzyme.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } }, '1.2.0': { 'repository': { 'type': 'git', 'url': 'git+https://github.com/airbnb/enzyme.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' } } } @@ -2446,8 +2582,22 @@ describe('create version branch for dependencies from monorepos', () => { oldVersion: '^3.3.0', oldVersionResolved: undefined, versions: { - '1.6.0': { gitHead: 'wau' }, - '1.5.0': { gitHead: 'woof' } + '1.6.0': { + gitHead: 'wau', + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + }, + '1.5.0': { + gitHead: 'woof', + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + } } }) @@ -2581,7 +2731,8 @@ describe('create version branch for dependencies from monorepos', () => { type: 'devDependencies', version: '7.0.0-rc.2', oldVersion: '^7.0.0-rc.1', - oldVersionResolved: undefined + oldVersionResolved: undefined, + versions: versionInfos }) expect(githubMock.isDone()).toBeTruthy() diff --git a/test/lib/__snapshots__/create-branch.js.snap b/test/lib/create-branch/__snapshots__/create-branch.js.snap similarity index 85% rename from test/lib/__snapshots__/create-branch.js.snap rename to test/lib/create-branch/__snapshots__/create-branch.js.snap index 6a062696..39278c16 100644 --- a/test/lib/__snapshots__/create-branch.js.snap +++ b/test/lib/create-branch/__snapshots__/create-branch.js.snap @@ -1,6 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`create branch with lockfiles change one file (package.json) and generate its lockfile (old syntax) 1`] = ` +exports[`create branch with lockfiles change a package.json and generate its lockfile for pnpm 1`] = ` +Object { + "lock": "{\\"devDependencies\\":{\\"jest\\":\\"1.1.1\\"}}", + "packageJson": "{\\"devDependencies\\":{\\"jest\\":\\"1.2.0\\"}}", + "repositoryTokens": "", + "type": "pnpm", +} +`; + +exports[`create branch with lockfiles change one file (package.json) and generate its lockfile 1`] = ` Object { "lock": "{\\"devDependencies\\":{\\"jest\\":\\"1.1.1\\"}}", "packageJson": "{\\"devDependencies\\":{\\"jest\\":\\"1.2.0\\"}}", diff --git a/test/lib/create-branch/__snapshots__/yarn-workspaces.js.snap b/test/lib/create-branch/__snapshots__/yarn-workspaces.js.snap new file mode 100644 index 00000000..39126133 --- /dev/null +++ b/test/lib/create-branch/__snapshots__/yarn-workspaces.js.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`create branch with yarn workspace lockfiles handle a complex yarn workspace 1`] = ` +Object { + "lock": "{\\"very-excellent-lockfile\\":\\"nah\\"}", + "packages": Object { + "non-root/docs/package.json": Object { + "dependencies": Object { + "react": "2.0.0", + }, + }, + "non-root/jobs/first-job/package.json": Object { + "dependencies": Object { + "react": "2.0.0", + }, + }, + "non-root/jobs/second-job/package.json": Object { + "dependencies": Object { + "react": "2.0.0", + }, + }, + "non-root/outside-workspace/package.json": Object { + "dependencies": Object { + "nothing": "1.0.0", + }, + }, + "non-root/package.json": Object { + "dependencies": Object { + "nothing": "1.0.0", + }, + "workspaces": Array [ + "jobs/*", + "docs", + ], + }, + }, + "repositoryTokens": "", + "type": "yarn", + "workspaceRoot": "non-root/package.json", +} +`; + +exports[`create branch with yarn workspace lockfiles handle a simple yarn workspace 1`] = ` +Object { + "lock": "{\\"very-excellent-lockfile\\":\\"nah\\"}", + "packages": Object { + "jobs/first-job/package.json": Object { + "dependencies": Object { + "react": "2.0.0", + }, + }, + "package.json": Object { + "dependencies": Object { + "react": "2.0.0", + }, + "workspaces": Array [ + "jobs/*", + ], + }, + }, + "repositoryTokens": "", + "type": "yarn", + "workspaceRoot": "package.json", +} +`; diff --git a/test/lib/create-branch.js b/test/lib/create-branch/create-branch.js similarity index 90% rename from test/lib/create-branch.js rename to test/lib/create-branch/create-branch.js index 991a5bbe..091c5480 100644 --- a/test/lib/create-branch.js +++ b/test/lib/create-branch/create-branch.js @@ -1,8 +1,8 @@ const nock = require('nock') -const createBranch = require('../../lib/create-branch') -const { createTransformFunction } = require('../../utils/utils') -const dbs = require('../../lib/dbs') +const createBranch = require('../../../lib/create-branch') +const { createTransformFunction } = require('../../../utils/utils') +const dbs = require('../../../lib/dbs') nock.disableNetConnect() nock.enableNetConnect('localhost') @@ -768,8 +768,8 @@ describe('create branch', async () => { }) }) -describe('create branch with lockfiles', async () => { - test('change one file (package.json) and generate its lockfile (old syntax)', async () => { +describe('create branch with lockfiles', () => { + test('change one file (package.json) and generate its lockfile', async () => { const packageFileContents = { devDependencies: { 'jest': '1.1.1' } } @@ -889,10 +889,11 @@ describe('create branch with lockfiles', async () => { fullName: 'finnp/one-lockfile-old-syntax', private: false, files: { - 'package.json': true, - 'package-lock.json': true, - 'npm-shrinkwrap.json': false, - 'yarn.lock': false + 'package.json': ['package.json'], + 'package-lock.json': ['package-lock.json'], + 'npm-shrinkwrap.json': [], + 'yarn.lock': [], + 'pnpm-lock.yaml': [] }, packages: { 'package.json': { @@ -1112,7 +1113,8 @@ describe('create branch with lockfiles', async () => { 'package.json': ['package.json', 'frontend/package.json'], 'package-lock.json': ['package-lock.json', 'frontend/package-lock.json'], 'npm-shrinkwrap.json': [], - 'yarn.lock': [] + 'yarn.lock': [], + 'pnpm-lock.yaml': [] }, packages: { 'package.json': { @@ -1133,6 +1135,150 @@ describe('create branch with lockfiles', async () => { expect(gitHubNock.isDone()).toBeTruthy() }) + test('change a package.json and generate its lockfile for pnpm', async () => { + const packageFileContents = { devDependencies: { + 'jest': '1.1.1' + } } + const updatedPackageFileContents = { devDependencies: { + 'jest': '1.2.0' + } } + const gitHubNock = nock('https://api.github.com') + .post('/app/installations/123/access_tokens') + .optionally() + .reply(200, { + token: 'secret' + }) + .get('/rate_limit') + .optionally() + .reply(200) + .get('/repos/owner/repo/contents/package.json') + .query({ ref: 'master' }) + .reply(200, { + type: 'file', + content: Buffer.from(JSON.stringify(packageFileContents)).toString('base64') + }) + .get('/repos/owner/repo/contents/pnpm-lock.yaml') + .reply(200, { + type: 'file', + path: 'pnpm-lock.yaml', + name: 'pnpm-lock.yaml', + content: Buffer.from(JSON.stringify(packageFileContents)).toString('base64') + }) + .get('/repos/owner/repo/git/refs/heads/master') + .reply(200, { + object: { + sha: '123abc' + } + }) + // First tree and commit for package.json + .post('/repos/owner/repo/git/trees', { + tree: [ + { + path: 'package.json', + content: JSON.stringify(updatedPackageFileContents), + mode: '100644', + type: 'blob' + } + ], + base_tree: '123abc' + }) + .reply(201, { + sha: 'def456' + }) + .post('/repos/owner/repo/git/commits', { + message: 'new commit', + tree: 'def456', + parents: ['123abc'] + }) + .reply(201, { + sha: '789beef' + }) + // Second tree and commit for pnpm-lock.yaml + .post('/repos/owner/repo/git/trees', { + tree: [ + { + path: 'pnpm-lock.yaml', + content: '{"devDependencies":{"jest": {"version": "1.2.0"}}}', + mode: '100644', + type: 'blob' + } + ], + base_tree: '789beef' + }) + .reply(201, { + sha: 'lol999' + }) + .post('/repos/owner/repo/git/commits', { + message: 'Updated lockfile pnpm-lock.yaml, yay', + tree: 'lol999', + parents: ['789beef'] + }) + .reply(201, { + sha: 'finalsha123' + }) + .post('/repos/owner/repo/git/refs', { + ref: 'refs/heads/testBranch', + sha: 'finalsha123' + }) + .reply(201) + + nock('http://localhost:1234') + .post('/', (body) => { + expect(body.packageJson).toEqual(JSON.stringify(updatedPackageFileContents)) + expect(body.type).toEqual('pnpm') + expect(typeof body.packageJson).toBe('string') + expect(typeof body.lock).toBe('string') + expect(body).toMatchSnapshot() + return true + }) + .reply(200, () => { + return { + ok: true, + contents: '{"devDependencies":{"jest": {"version": "1.2.0"}}}' + } + }) + + const sha = await createBranch({ + installationId: 123, + owner: 'owner', + repoName: 'repo', + branch: 'master', + newBranch: 'testBranch', + transforms: [ + { + path: 'package.json', + transform: oldPkg => JSON.stringify(updatedPackageFileContents), + message: 'new commit' + } + ], + processLockfiles: true, + commitMessageTemplates: { 'lockfileUpdate': 'Updated lockfile ${lockfilePath}, yay' }, // eslint-disable-line no-template-curly-in-string + repoDoc: { + _id: 'one-lockfile-pnpm', + accountId: '124', + fullName: 'finnp/one-lockfile-pnpm', + private: false, + files: { + 'package.json': ['package.json'], + 'package-lock.json': [], + 'npm-shrinkwrap.json': [], + 'yarn.lock': [], + 'pnpm-lock.yaml': ['pnpm-lock.yaml'] + }, + packages: { + 'package.json': { + devDependencies: { + 'jest': '1.0.0' + } + } + } + } + }) + + expect(sha).toEqual('finalsha123') + expect(gitHubNock.isDone()).toBeTruthy() + }) + test('don’t generate the same lockfile multiple times', async () => { // multiple commits to the same package file should only result in a single lockfile update, // meaning a single exec server request and a single github tree update plus commit @@ -1296,7 +1442,8 @@ describe('create branch with lockfiles', async () => { 'package.json': ['package.json'], 'package-lock.json': ['package-lock.json'], 'npm-shrinkwrap.json': [], - 'yarn.lock': [] + 'yarn.lock': [], + 'pnpm-lock.yaml': [] }, packages: { 'package.json': { @@ -1407,10 +1554,11 @@ describe('create branch with lockfiles', async () => { fullName: 'finnp/one-lockfile-old-syntax', private: false, files: { - 'package.json': true, - 'package-lock.json': true, - 'npm-shrinkwrap.json': false, - 'yarn.lock': false + 'package.json': ['package.json'], + 'package-lock.json': ['package-lock.json'], + 'npm-shrinkwrap.json': [], + 'yarn.lock': [], + 'pnpm-lock.yaml': [] }, packages: { 'package.json': { @@ -1559,10 +1707,11 @@ describe('create branch with lockfiles', async () => { fullName: 'finnp/one-lockfile-with-token', private: false, files: { - 'package.json': true, - 'package-lock.json': true, - 'npm-shrinkwrap.json': false, - 'yarn.lock': false + 'package.json': ['package.json'], + 'package-lock.json': ['package-lock.json'], + 'npm-shrinkwrap.json': [], + 'yarn.lock': [], + 'pnpm-lock.yaml': [] }, packages: { 'package.json': { diff --git a/test/lib/create-branch/yarn-workspaces.js b/test/lib/create-branch/yarn-workspaces.js new file mode 100644 index 00000000..aa93fa10 --- /dev/null +++ b/test/lib/create-branch/yarn-workspaces.js @@ -0,0 +1,467 @@ +const nock = require('nock') + +const createBranch = require('../../../lib/create-branch') + +nock.disableNetConnect() +nock.enableNetConnect('localhost') + +describe('create branch with yarn workspace lockfiles', () => { + test('handle a simple yarn workspace', async () => { + expect.assertions(8) + const packages = { + 'package.json': { + dependencies: { + react: '1.0.0' + }, + workspaces: ['jobs/*'] + }, + 'jobs/first-job/package.json': { + dependencies: { + react: '1.0.0' + } + } + } + const updatedPackages = { + 'package.json': { + dependencies: { + react: '2.0.0' + }, + workspaces: ['jobs/*'] + }, + 'jobs/first-job/package.json': { + dependencies: { + react: '2.0.0' + } + } + } + const gitHubNock = nock('https://api.github.com') + .post('/app/installations/123/access_tokens') + .optionally() + .reply(200, { + token: 'secret' + }) + .get('/rate_limit') + .optionally() + .reply(200, {}) + .get('/repos/espy/yarn-workspaces-lockfile/contents/package.json') + .query({ ref: 'master' }) + .reply(200, { + type: 'file', + content: Buffer.from(JSON.stringify(packages['package.json'])).toString('base64') + }) + .get('/repos/espy/yarn-workspaces-lockfile/contents/jobs/first-job/package.json') + .query({ ref: 'master' }) + .reply(200, { + type: 'file', + content: Buffer.from(JSON.stringify(packages['jobs/first-job/package.json'])).toString('base64') + }) + // get yarn lock + .get('/repos/espy/yarn-workspaces-lockfile/contents/yarn.lock') + .reply(200, { + type: 'file', + path: 'yarn.lock', + name: 'yarn.lock', + content: Buffer.from('{"very-excellent-lockfile":"nah"}').toString('base64') + }) + // get sha for master + .get('/repos/espy/yarn-workspaces-lockfile/git/refs/heads/master') + .reply(200, { + object: { + sha: 'master-sha' + } + }) + // make 2 commits (update package.json & lockfile) + // tree for package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'package.json', + content: JSON.stringify(updatedPackages['package.json']), + mode: '100644', + type: 'blob' + } + ], + base_tree: 'jobs/first-job/package.json-commit-sha' + }) + .reply(201, { + sha: 'package.json-tree' + }) + // commit for package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'new commit to update package.json', + tree: 'package.json-tree', + parents: ['jobs/first-job/package.json-commit-sha'] + }) + .reply(201, { + sha: 'package.json-commit-sha' + }) + // tree for 2. package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'jobs/first-job/package.json', + content: JSON.stringify(updatedPackages['jobs/first-job/package.json']), + mode: '100644', + type: 'blob' + } + ], + base_tree: 'master-sha' + }) + .reply(201, { + sha: 'jobs/first-job/package.json-tree-sha' + }) + // commit for package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'new commit to update jobs/first-job/package.json', + tree: 'jobs/first-job/package.json-tree-sha', + parents: ['master-sha'] + }) + .reply(201, { + sha: 'jobs/first-job/package.json-commit-sha' + }) + // tree and commit for yarn.lock + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'yarn.lock', + content: '{"very-excellent-lockfile":"yes"}', + mode: '100644', + type: 'blob' + } + ], + base_tree: 'package.json-commit-sha' + }) + .reply(201, { + sha: 'yarn.lock-tree-sha' + }) + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'Updated lockfile yarn.lock, yay', + tree: 'yarn.lock-tree-sha', + parents: ['package.json-commit-sha'] + }) + .reply(201, { + sha: 'yarn.lock-commit-sha' + }) + .post('/repos/espy/yarn-workspaces-lockfile/git/refs', { + ref: 'refs/heads/testBranch', + sha: 'yarn.lock-commit-sha' + }) + .reply(201) + + nock('http://localhost:1234') + .post('/', (body) => { + expect(typeof body.type).toBe('string') + expect(typeof body.workspaceRoot).toBe('string') + expect(typeof body.packageJson).toBe('undefined') + expect(typeof body.packages).toBe('object') + expect(typeof body.lock).toBe('string') + expect(body).toMatchSnapshot() + return true + }) + .reply(200, () => { + return { + ok: true, + contents: '{"very-excellent-lockfile":"yes"}' + } + }) + const sha = await createBranch({ + installationId: '123', + owner: 'espy', + repoName: 'yarn-workspaces-lockfile', + branch: 'master', + newBranch: 'testBranch', + transforms: [ + { + path: 'jobs/first-job/package.json', + transform: oldPkg => JSON.stringify(updatedPackages['jobs/first-job/package.json']), + message: 'new commit to update jobs/first-job/package.json' + }, { + path: 'package.json', + transform: oldPkg => JSON.stringify(updatedPackages['package.json']), + message: 'new commit to update package.json' + } + ], + processLockfiles: true, + commitMessageTemplates: { 'lockfileUpdate': 'Updated lockfile ${lockfilePath}, yay' }, // eslint-disable-line no-template-curly-in-string + repoDoc: { + _id: 'yarn-workspaces-lockfile', + accountId: '123', + fullName: 'espy/yarn-workspaces-lockfile', + private: false, + files: { + 'package.json': ['package.json', 'jobs/first-job/package.json'], + 'package-lock.json': [], + 'npm-shrinkwrap.json': [], + 'yarn.lock': ['yarn.lock'] + }, + packages + } + }) + expect(sha).toEqual('yarn.lock-commit-sha') + expect(gitHubNock.isDone()).toBeTruthy() + }) + + test('handle a complex yarn workspace', async () => { + expect.assertions(8) + const packages = { + 'non-root/package.json': { + dependencies: { + nothing: '1.0.0' + }, + workspaces: ['jobs/*', 'docs'] + }, + 'non-root/jobs/first-job/package.json': { + dependencies: { + react: '1.0.0' + } + }, + 'non-root/jobs/second-job/package.json': { + dependencies: { + react: '1.0.0' + } + }, + 'non-root/docs/package.json': { + dependencies: { + react: '1.0.0' + } + }, + 'non-root/outside-workspace/package.json': { + dependencies: { + nothing: '1.0.0' + } + } + } + const updatedPackages = { + 'non-root/package.json': { + dependencies: { + nothing: '1.0.0' + }, + workspaces: ['jobs/*', 'docs'] + }, + 'non-root/jobs/first-job/package.json': { + dependencies: { + react: '2.0.0' + } + }, + 'non-root/jobs/second-job/package.json': { + dependencies: { + react: '2.0.0' + } + }, + 'non-root/docs/package.json': { + dependencies: { + react: '2.0.0' + } + }, + 'non-root/outside-workspace/package.json': { + dependencies: { + nothing: '1.0.0' + } + } + } + + const gitHubNock = nock('https://api.github.com') + .post('/app/installations/123/access_tokens') + .optionally() + .reply(200, { + token: 'secret' + }) + .get('/rate_limit') + .optionally() + .reply(200) + .get('/repos/espy/yarn-workspaces-lockfile/contents/non-root/jobs/first-job/package.json') + .query({ ref: 'master' }) + .reply(200, { + type: 'file', + content: Buffer.from(JSON.stringify(packages['non-root/jobs/first-job/package.json'])).toString('base64') + }) + .get('/repos/espy/yarn-workspaces-lockfile/contents/non-root/jobs/second-job/package.json') + .query({ ref: 'master' }) + .reply(200, { + type: 'file', + content: Buffer.from(JSON.stringify(packages['non-root/jobs/second-job/package.json'])).toString('base64') + }) + .get('/repos/espy/yarn-workspaces-lockfile/contents/non-root/docs/package.json') + .query({ ref: 'master' }) + .reply(200, { + type: 'file', + content: Buffer.from(JSON.stringify(packages['non-root/docs/package.json'])).toString('base64') + }) + // get yarn lock + .get('/repos/espy/yarn-workspaces-lockfile/contents/non-root/yarn.lock') + .reply(200, { + type: 'file', + path: 'non-root/yarn.lock', + name: 'yarn.lock', + content: Buffer.from('{"very-excellent-lockfile":"nah"}').toString('base64') + }) + // get sha for master + .get('/repos/espy/yarn-workspaces-lockfile/git/refs/heads/master') + .reply(200, { + object: { + sha: 'master-sha' + } + }) + // create commits + // tree for 1-job package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'non-root/jobs/first-job/package.json', + content: JSON.stringify(updatedPackages['non-root/jobs/first-job/package.json']), + mode: '100644', + type: 'blob' + } + ], + base_tree: 'master-sha' + }) + .reply(201, { + sha: 'non-root/jobs/first-job/package.json-tree-sha' + }) + // commit for 1-job package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'new commit to update non-root/jobs/first-job/package.json', + tree: 'non-root/jobs/first-job/package.json-tree-sha', + parents: ['master-sha'] + }) + .reply(201, { + sha: 'non-root/jobs/first-job/package.json-commit-sha' + }) + // tree for 2-job package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'non-root/jobs/second-job/package.json', + content: JSON.stringify(updatedPackages['non-root/jobs/second-job/package.json']), + mode: '100644', + type: 'blob' + } + ], + base_tree: 'non-root/jobs/first-job/package.json-commit-sha' + }) + .reply(201, { + sha: 'non-root/jobs/second-job/package.json-tree-sha' + }) + // commit for 2-job package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'new commit to update non-root/jobs/second-job/package.json', + tree: 'non-root/jobs/second-job/package.json-tree-sha', + parents: ['non-root/jobs/first-job/package.json-commit-sha'] + }) + .reply(201, { + sha: 'non-root/jobs/second-job/package.json-commit-sha' + }) + // tree for docs. package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'non-root/docs/package.json', + content: JSON.stringify(updatedPackages['non-root/docs/package.json']), + mode: '100644', + type: 'blob' + } + ], + base_tree: 'non-root/jobs/second-job/package.json-commit-sha' + }) + .reply(201, { + sha: 'non-root/docs/package.json-tree-sha' + }) + // commit for docs/package.json + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'new commit to update non-root/docs/package.json', + tree: 'non-root/docs/package.json-tree-sha', + parents: ['non-root/jobs/second-job/package.json-commit-sha'] + }) + .reply(201, { + sha: 'non-root/docs/package.json-commit-sha' + }) + // tree and commit for yarn.lock + .post('/repos/espy/yarn-workspaces-lockfile/git/trees', { + tree: [ + { + path: 'non-root/yarn.lock', + content: '{"very-excellent-lockfile":"yes"}', + mode: '100644', + type: 'blob' + } + ], + base_tree: 'non-root/docs/package.json-commit-sha' + }) + .reply(201, { + sha: 'yarn.lock-tree-sha' + }) + .post('/repos/espy/yarn-workspaces-lockfile/git/commits', { + message: 'Updated lockfile non-root/yarn.lock, yay', + tree: 'yarn.lock-tree-sha', + parents: ['non-root/docs/package.json-commit-sha'] + }) + .reply(201, { + sha: 'yarn.lock-commit-sha' + }) + .post('/repos/espy/yarn-workspaces-lockfile/git/refs', { + ref: 'refs/heads/testBranch', + sha: 'yarn.lock-commit-sha' + }) + .reply(201) + + nock('http://localhost:1234') + .post('/', (body) => { + expect(typeof body.type).toBe('string') + expect(typeof body.workspaceRoot).toBe('string') + expect(typeof body.packageJson).toBe('undefined') + expect(typeof body.packages).toBe('object') + expect(typeof body.lock).toBe('string') + expect(body).toMatchSnapshot() + return true + }) + .reply(200, () => { + return { + ok: true, + contents: '{"very-excellent-lockfile":"yes"}' + } + }) + + const sha = await createBranch({ + installationId: '123', + owner: 'espy', + repoName: 'yarn-workspaces-lockfile', + branch: 'master', + newBranch: 'testBranch', + transforms: [ + { + path: 'non-root/jobs/first-job/package.json', + transform: oldPkg => JSON.stringify(updatedPackages['non-root/jobs/first-job/package.json']), + message: 'new commit to update non-root/jobs/first-job/package.json' + }, + { + path: 'non-root/jobs/second-job/package.json', + transform: oldPkg => JSON.stringify(updatedPackages['non-root/jobs/second-job/package.json']), + message: 'new commit to update non-root/jobs/second-job/package.json' + }, + { + path: 'non-root/docs/package.json', + transform: oldPkg => JSON.stringify(updatedPackages['non-root/docs/package.json']), + message: 'new commit to update non-root/docs/package.json' + } + ], + processLockfiles: true, + commitMessageTemplates: { 'lockfileUpdate': 'Updated lockfile ${lockfilePath}, yay' }, // eslint-disable-line no-template-curly-in-string + repoDoc: { + _id: 'yarn-workspaces-lockfile', + accountId: '123', + fullName: 'espy/yarn-workspaces-lockfile', + private: false, + files: { + 'package.json': ['non-root/package.json', 'non-root/jobs/first-job/package.json', 'non-root/jobs/second-job/package.json', 'non-root/docs/package.json', 'non-root/outside-workspace/package.json'], + 'package-lock.json': [], + 'npm-shrinkwrap.json': [], + 'yarn.lock': ['non-root/yarn.lock'] + }, + packages + } + }) + expect(sha).toEqual('yarn.lock-commit-sha') + expect(gitHubNock.isDone()).toBeTruthy() + }) +}) +// TODO repo has yarn.lock and package-lock.json? diff --git a/test/lib/get-files.js b/test/lib/get-files.js index 5d25fbe2..f24e9fd6 100644 --- a/test/lib/get-files.js +++ b/test/lib/get-files.js @@ -23,7 +23,7 @@ test('getFiles: with no fileList provided', async () => { const files = await getFiles({ installationId: '123', fullName: 'owner/repo', log }) // returns an Object with the 4 standard files - expect(Object.keys(files)).toHaveLength(4) + expect(Object.keys(files)).toHaveLength(5) }) test('getFiles: 2 package.json files', async () => { @@ -66,8 +66,8 @@ test('getFiles: 2 package.json files', async () => { ] const files = await getFiles({ installationId: '123', fullName: 'owner/repo', files: fileList, log }) - // returns an Object with the 4 file types - expect(Object.keys(files)).toHaveLength(4) + // returns an Object with the 5 file types + expect(Object.keys(files)).toHaveLength(5) // The Object has 2 files at the `package.json` key expect(files['package.json']).toHaveLength(2) expect(files['package.json'][0].path).toEqual('package.json') @@ -104,7 +104,7 @@ test('getFiles: 2 package.json files but one is not found on github', async () = const files = await getFiles({ installationId: '123', fullName: 'owner/repo', files: fileList, log }) - expect(Object.keys(files)).toHaveLength(4) + expect(Object.keys(files)).toHaveLength(5) // returns an Object with the key `package.json` expect(files['package.json']).toBeDefined() // The Object has 2 files at the `package.json` key diff --git a/test/lib/lockfile.js b/test/lib/lockfile.js index f85cf0f2..79521aeb 100644 --- a/test/lib/lockfile.js +++ b/test/lib/lockfile.js @@ -30,7 +30,7 @@ describe('getNewLockfile', async () => { } }) - await getNewLockfile({ packageJson, lock, isNpm: true }) + await getNewLockfile({ packageJson, lock, type: 'npm' }) }) test('with package-lock.json', async () => { @@ -46,7 +46,7 @@ describe('getNewLockfile', async () => { }) .reply(200, () => ({ ok: false })) - await getNewLockfile({ packageJson, lock, isNpm: true }) + await getNewLockfile({ packageJson, lock, type: 'npm' }) }) test('with package-lock.json with Network Error', async () => { @@ -61,7 +61,7 @@ describe('getNewLockfile', async () => { .reply(200, () => ({ ok: true })) const packageJson = '{"name": "greenkeeper","devDependencies": {"jest": "^22.4.2"}}' - await getNewLockfile({ packageJson, lock, isNpm: true }) + await getNewLockfile({ packageJson, lock, type: 'npm' }) expect(httpTraffic.isDone()).toBeTruthy() expect(httpTraffic.pendingMocks().length).toEqual(0) }) diff --git a/test/utils/utils.js b/test/utils/utils.js index be525ab4..0fea7edf 100644 --- a/test/utils/utils.js +++ b/test/utils/utils.js @@ -10,7 +10,8 @@ const { addNodeVersionToTravisYML, addNewLowestAndDeprecate, hasNodeVersion, - getLockfilePath + getLockfilePath, + getLicenseAndPublisherFromVersions } = require('../../utils/utils') const { cleanCache } = require('../helpers/module-cache-helpers') @@ -260,6 +261,102 @@ test('getOldVersionResolved', () => { expect(output).toEqual('9.3.1') }) +test('getLicenseAndPublisherFromVersions', () => { + const version = '2.2.2' + const oldVersionResolved = '1.1.1' + const versions = { + '1.1.1': { + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/cat/cat.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + }, + '2.2.2': { + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/best/best.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + } + } + const output = getLicenseAndPublisherFromVersions({ versions, version, oldVersionResolved }) + expect(output).toMatchObject({ license: 'MIT', licenseHasChanged: false, publisher: 'finn' }) +}) + +test('getLicenseAndPublisherFromVersions with changed license', () => { + const version = '2.2.2' + const oldVersionResolved = '1.1.1' + const versions = { + '1.1.1': { + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/cat/cat.git' + }, + 'license': 'MIT', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + }, + '2.2.2': { + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/best/best.git' + }, + 'license': 'kitty', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + } + } + const output = getLicenseAndPublisherFromVersions({ versions, version, oldVersionResolved }) + expect(output).toMatchObject({ license: 'kitty', licenseHasChanged: true, publisher: 'finn', previousLicense: 'MIT' }) +}) + +test('getLicenseAndPublisherFromVersions with no previous license', () => { + const version = '2.2.2' + const oldVersionResolved = '1.1.1' + const versions = { + '1.1.1': { + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/cat/cat.git' + }, + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + }, + '2.2.2': { + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/best/best.git' + }, + 'license': 'kitty', + '_npmUser': { + name: 'finn', + email: 'finn.pauls@gmail.com' + } + } + } + const output = getLicenseAndPublisherFromVersions({ versions, version, oldVersionResolved }) + expect(output).toMatchObject({ + license: 'kitty', + publisher: 'finn', + licenseHasChanged: undefined, + previousLicense: undefined }) +}) + test('Use default env.GITHUB_URL in github compare URL', () => { const fullName = 'hanshansen/mopeds' const branch = 'master' @@ -279,59 +376,7 @@ test('respect env.GITHUB_URL in github compare URL', () => { expect(url).toEqual('https://superprivategit.megacorp.com/hanshansen/mopeds/compare/dev...hanshansen:greenkeeper%2Ffrontend%2Fstandard-10.0.0') }) -test('get no lockfile in old syntax', () => { - process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' - const files = { - 'package.json': true, - 'package-lock.json': false, - 'yarn.lock': false, - 'shrinkwrap.json': false - } - const packageFileName = 'package.json' - const path = getLockfilePath(files, packageFileName) - expect(path).toBeFalsy() -}) - -test('get lockfile for package-lock in old syntax', () => { - process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' - const files = { - 'package.json': true, - 'package-lock.json': true, - 'yarn.lock': false, - 'shrinkwrap.json': false - } - const packageFileName = 'package.json' - const path = getLockfilePath(files, packageFileName) - expect(path).toEqual('package-lock.json') -}) - -test('get npm lockfile despite yarn.lock in old syntax', () => { - process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' - const files = { - 'package.json': true, - 'package-lock.json': true, - 'yarn.lock': true, - 'shrinkwrap.json': false - } - const packageFileName = 'package.json' - const path = getLockfilePath(files, packageFileName) - expect(path).toEqual('package-lock.json') -}) - -test('get yarn.lock in old syntax', () => { - process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' - const files = { - 'package.json': true, - 'package-lock.json': false, - 'yarn.lock': true, - 'shrinkwrap.json': false - } - const packageFileName = 'package.json' - const path = getLockfilePath(files, packageFileName) - expect(path).toEqual('yarn.lock') -}) - -test('get no lockfile in new syntax', () => { +test('get no lockfile', () => { process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' const files = { 'package.json': ['package.json'], @@ -344,7 +389,7 @@ test('get no lockfile in new syntax', () => { expect(path).toBeFalsy() }) -test('get lockfile for package-lock in new syntax', () => { +test('get lockfile for package-lock', () => { process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' const files = { 'package.json': ['package.json'], @@ -357,7 +402,7 @@ test('get lockfile for package-lock in new syntax', () => { expect(path).toEqual('package-lock.json') }) -test('get npm lockfile despite yarn.lock in new syntax', () => { +test('get npm lockfile despite yarn.lock', () => { process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' const files = { 'package.json': ['package.json'], @@ -370,7 +415,7 @@ test('get npm lockfile despite yarn.lock in new syntax', () => { expect(path).toEqual('package-lock.json') }) -test('get yarn.lock in new syntax', () => { +test('get yarn.lock', () => { process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' const files = { 'package.json': ['package.json'], @@ -383,7 +428,7 @@ test('get yarn.lock in new syntax', () => { expect(path).toEqual('yarn.lock') }) -test('get one of many yarn.lock in new syntax', () => { +test('get one of many yarn.lock', () => { process.env.GITHUB_URL = 'https://superprivategit.megacorp.com' const files = { 'package.json': ['package.json'], diff --git a/utils/utils.js b/utils/utils.js index 6b3d7c70..1e680d2d 100644 --- a/utils/utils.js +++ b/utils/utils.js @@ -4,6 +4,11 @@ const jsonInPlace = require('json-in-place') const semver = require('semver') const getRangedVersion = require('../lib/get-ranged-version') +// removes falsy values from the array +function compactArray (array) { + return array.filter(value => value) +} + function seperateNormalAndMonorepos (packageFiles) { const resultsByRepo = groupPackageFilesByRepo(packageFiles) @@ -148,17 +153,20 @@ function createTransformFunction (type, dependency, version, log) { // 'legacy' repoDocs have only true/false set at repository.files['yarn.lock'] ect // 'newer' repoDocs have an array (empty or with the paths) -// packageFilename: path of pckage.json +// packageFilename: path of package.json const getLockfilePath = function (files, packageFilename) { const convertedFiles = _.flatten(Object.keys(files).map(key => { if (files[key] === true) return key else return files[key] })) - const hasPackageLock = _.includes(convertedFiles, packageFilename.replace('package.json', 'package-lock.json')) + const hasPackageLock = convertedFiles.includes(packageFilename.replace('package.json', 'package-lock.json')) if (hasPackageLock) return packageFilename.replace('package.json', 'package-lock.json') - const hasYarnLock = _.includes(convertedFiles, packageFilename.replace('package.json', 'yarn.lock')) + const hasPnpmLock = convertedFiles.includes(packageFilename.replace('package.json', 'pnpm-lock.yaml')) + if (hasPnpmLock) return packageFilename.replace('package.json', 'pnpm-lock.yaml') + + const hasYarnLock = convertedFiles.includes(packageFilename.replace('package.json', 'yarn.lock')) if (hasYarnLock) return packageFilename.replace('package.json', 'yarn.lock') return null @@ -315,7 +323,31 @@ const hasTooManyPackageJSONs = function (repo) { return repo.packages && Object.keys(repo.packages).length > 300 } +const getLicenseAndPublisherFromVersions = function ({ versions, version, oldVersionResolved }) { + const newVersion = versions[version] + const oldVersion = versions[oldVersionResolved] + const publisher = _.get(newVersion, '_npmUser.name') + let license, previousLicense, licenseHasChanged + if (_.has(newVersion, 'license')) { + license = newVersion['license'] || 'No license' + + // only compare if we have a license field for both versions in the DB + if (_.has(oldVersion, 'license')) { + previousLicense = oldVersion['license'] || 'No license' + licenseHasChanged = previousLicense !== license + } + } + + return { + license, + previousLicense, + licenseHasChanged, + publisher + } +} + module.exports = { + compactArray, seperateNormalAndMonorepos, getJobsPerGroup, filterAndSortPackages, @@ -332,5 +364,6 @@ module.exports = { updateNodeVersionToNvmrc, addNewLowestAndDeprecate, hasTooManyPackageJSONs, - getLockfilePath + getLockfilePath, + getLicenseAndPublisherFromVersions }