Add logic to discern tags from branches in dependencies
This commit is contained in:
parent
aaed828003
commit
0653b8f8e3
|
@ -1,3 +1,73 @@
|
||||||
|
import { Context } from 'probot/lib/context'
|
||||||
|
|
||||||
|
export function getDependenciesFromJSON (dependenciesJSON: any): Dependency[] {
|
||||||
|
const dependencies: Dependency[] = []
|
||||||
|
|
||||||
|
for (const name in dependenciesJSON) {
|
||||||
|
if (dependenciesJSON.hasOwnProperty(name)) {
|
||||||
|
dependencies.push(new Dependency(name, dependenciesJSON[name]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkDependenciesAsync (context: Context, contents: string, dependencies: Dependency[], filename: string, result: AnalysisResult) {
|
||||||
|
if (!dependencies || dependencies.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlRegex = /^(http:\/\/|https:\/\/|git\+http:\/\/|git\+https:\/\/|ssh:\/\/|git\+ssh:\/\/|github:)([a-zA-Z0-9_\-./]+)(#(.*))?$/gm
|
||||||
|
const requiredProtocol = 'git+https://'
|
||||||
|
|
||||||
|
result.checkedDependencyCount += dependencies.length
|
||||||
|
|
||||||
|
for (const dependency of dependencies) {
|
||||||
|
const url = dependency.url
|
||||||
|
const match = urlRegex.exec(url)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const protocol = match[1]
|
||||||
|
const address = protocol === 'github:' ? `github.com/${match[2]}` : match[2]
|
||||||
|
const tag = match.length > 4 ? match[4] : ''
|
||||||
|
const { line } = findLineColumn(contents, contents.indexOf(url))
|
||||||
|
const optimalAddress = address.endsWith('.git') ? address : address.concat('.git')
|
||||||
|
const isTag = await isTagAsync(context, address, tag)
|
||||||
|
const optimalTag = isTag ? tag : '#<release-tag>'
|
||||||
|
const suggestedUrl = `${requiredProtocol}${optimalAddress}${optimalTag}`
|
||||||
|
|
||||||
|
const annotationSource: annotationSource = {
|
||||||
|
dependency: dependency,
|
||||||
|
filename: filename,
|
||||||
|
line: line
|
||||||
|
}
|
||||||
|
if (protocol !== requiredProtocol) {
|
||||||
|
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'warning',
|
||||||
|
`Found protocol ${protocol} being used in dependency`,
|
||||||
|
`Protocol should be ${requiredProtocol}.`))
|
||||||
|
}
|
||||||
|
if (protocol !== 'github:' && !address.endsWith('.git')) {
|
||||||
|
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'warning',
|
||||||
|
'Address should end with .git for consistency.',
|
||||||
|
'Android builds have been known to fail when dependency addresses don\'t end with .git.'
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if (!tag) {
|
||||||
|
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'failure',
|
||||||
|
'Dependency is not locked with a tag/release.',
|
||||||
|
`${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
|
||||||
|
))
|
||||||
|
} else if (!isTag) {
|
||||||
|
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'failure',
|
||||||
|
'Dependency is locked with a branch, instead of a tag/release.',
|
||||||
|
`${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class AnnotationResult {
|
export class AnnotationResult {
|
||||||
title?: string
|
title?: string
|
||||||
message: string
|
message: string
|
||||||
|
@ -53,73 +123,6 @@ type annotationSource = {
|
||||||
line: number
|
line: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDependenciesFromJSON (dependenciesJSON: any): Dependency[] {
|
|
||||||
const dependencies: Dependency[] = []
|
|
||||||
|
|
||||||
for (const name in dependenciesJSON) {
|
|
||||||
if (dependenciesJSON.hasOwnProperty(name)) {
|
|
||||||
dependencies.push(new Dependency(name, dependenciesJSON[name]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkDependencies (contents: string, dependencies: Dependency[], filename: string, result: AnalysisResult) {
|
|
||||||
if (!dependencies || dependencies.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlRegex = /^(http:\/\/|https:\/\/|git\+http:\/\/|git\+https:\/\/|ssh:\/\/|git\+ssh:\/\/|github:)([a-zA-Z0-9_\-./]+)(#(.*))?$/gm
|
|
||||||
const requiredProtocol = 'git+https://'
|
|
||||||
|
|
||||||
result.checkedDependencyCount += dependencies.length
|
|
||||||
|
|
||||||
for (const dependency of dependencies) {
|
|
||||||
const url = dependency.url
|
|
||||||
const match = urlRegex.exec(url)
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const protocol = match[1]
|
|
||||||
const address = match[2]
|
|
||||||
const tag = match.length > 4 ? match[4] : ''
|
|
||||||
const { line } = findLineColumn(contents, contents.indexOf(url))
|
|
||||||
const optimalAddress = address.endsWith('.git') ? address : address.concat('.git')
|
|
||||||
const optimalTag = isTag(tag) ? tag : '#<release-tag>'
|
|
||||||
const suggestedUrl = `${requiredProtocol}${optimalAddress}${optimalTag}`
|
|
||||||
|
|
||||||
const annotationSource: annotationSource = {
|
|
||||||
dependency: dependency,
|
|
||||||
filename: filename,
|
|
||||||
line: line
|
|
||||||
}
|
|
||||||
if (protocol !== requiredProtocol) {
|
|
||||||
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'warning',
|
|
||||||
`Found protocol ${protocol} being used in dependency`,
|
|
||||||
`Protocol should be ${requiredProtocol}.`))
|
|
||||||
}
|
|
||||||
if (protocol !== 'github:' && !address.endsWith('.git')) {
|
|
||||||
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'warning',
|
|
||||||
'Address should end with .git for consistency.',
|
|
||||||
'Android builds have been known to fail when dependency addresses don\'t end with .git.'
|
|
||||||
))
|
|
||||||
}
|
|
||||||
if (!tag) {
|
|
||||||
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'failure',
|
|
||||||
'Dependency is not locked with a tag/release.',
|
|
||||||
`${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
|
|
||||||
))
|
|
||||||
} else if (!isTag(tag)) {
|
|
||||||
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'failure',
|
|
||||||
'Dependency is locked with a branch, instead of a tag/release.',
|
|
||||||
`${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findLineColumn (contents: string, index: number) {
|
function findLineColumn (contents: string, index: number) {
|
||||||
const lines = contents.split('\n')
|
const lines = contents.split('\n')
|
||||||
const line = contents.substr(0, index).split('\n').length
|
const line = contents.substr(0, index).split('\n').length
|
||||||
|
@ -155,7 +158,25 @@ function createAnnotation (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTag (tag: string): boolean {
|
async function isTagAsync (context: Context, address: string, tag: string): Promise<boolean> {
|
||||||
// TODO: We need to check the actual repo to see if it is a branch or a tag
|
if (!tag) {
|
||||||
return tag !== '' && tag !== 'master' && tag !== 'develop'
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'github.com/status-im/bignumber.js'
|
||||||
|
const parts = address.split('/')
|
||||||
|
if (parts[0] === 'github.com') {
|
||||||
|
try {
|
||||||
|
const getRefResponse = await context.github.gitdata.getRef({ owner: parts[1], repo: parts[2].replace('.git', ''), ref: `tags/${tag}` })
|
||||||
|
if (getRefResponse.status === 200) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
context.log.error(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Educated guess
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { Application, Context } from 'probot' // eslint-disable-line no-unused-vars
|
import { Application, Context } from 'probot' // eslint-disable-line no-unused-vars
|
||||||
import Octokit from '@octokit/rest'
|
import Octokit from '@octokit/rest'
|
||||||
import Humanize from 'humanize-plus'
|
import Humanize from 'humanize-plus'
|
||||||
import { checkDependencies, getDependenciesFromJSON, AnalysisResult, AnnotationResult } from './dependency-check'
|
import { checkDependenciesAsync, getDependenciesFromJSON, AnalysisResult, AnnotationResult } from './dependency-check'
|
||||||
|
|
||||||
const pendingChecks: any = []
|
const pendingChecks: any = []
|
||||||
|
|
||||||
|
@ -184,9 +184,9 @@ async function checkPackageFileAsync (analysisResult: AnalysisResult, context: C
|
||||||
const contents = Buffer.from(contentsResponse.data.content, 'base64').toString('utf8')
|
const contents = Buffer.from(contentsResponse.data.content, 'base64').toString('utf8')
|
||||||
const contentsJSON = JSON.parse(contents)
|
const contentsJSON = JSON.parse(contents)
|
||||||
|
|
||||||
checkDependencies(contents, getDependenciesFromJSON(contentsJSON.dependencies), filename, analysisResult)
|
await checkDependenciesAsync(context, contents, getDependenciesFromJSON(contentsJSON.dependencies), filename, analysisResult)
|
||||||
checkDependencies(contents, getDependenciesFromJSON(contentsJSON.devDependencies), filename, analysisResult)
|
await checkDependenciesAsync(context, contents, getDependenciesFromJSON(contentsJSON.devDependencies), filename, analysisResult)
|
||||||
checkDependencies(contents, getDependenciesFromJSON(contentsJSON.optionalDependencies), filename, analysisResult)
|
await checkDependenciesAsync(context, contents, getDependenciesFromJSON(contentsJSON.optionalDependencies), filename, analysisResult)
|
||||||
|
|
||||||
return analysisResult.checkedDependencyCount
|
return analysisResult.checkedDependencyCount
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue