Skip to content

Commit

Permalink
Add action implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Obi-Dann committed Apr 12, 2021
1 parent a0de1e3 commit 7aac2c2
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 9 deletions.
18 changes: 16 additions & 2 deletions .github/workflows/test.yml
Expand Up @@ -15,10 +15,24 @@ jobs:
npm install
- run: |
npm run all
test: # make sure the action works on a clean machine without building
test: # make sure the action works on a clean machine without building, the action can only run on pull_requests
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' }}
steps:
- uses: actions/checkout@v2
- uses: ./
pr-build-test:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
milliseconds: 1000
node-version: '12'
- run: |
npm install
- run: |
npm run build
- run: |
npm run package
- uses: ./
23 changes: 23 additions & 0 deletions src/fetchChangelogs.ts
@@ -0,0 +1,23 @@
import {getManagerConfig, RenovateConfig} from 'renovate/dist/config'
import {getChangeLogJSON} from 'renovate/dist/workers/pr/changelog'
import {UpdatedDependency, UpdatedDependencyWithChangelog} from './types'

export async function fetchChangelogs(
config: RenovateConfig,
dependencies: UpdatedDependency[]
): Promise<UpdatedDependencyWithChangelog[]> {
const result: UpdatedDependencyWithChangelog[] = []
for (const updatedDependency of dependencies) {
const {dependency, update, manager} = updatedDependency
const logJSON = await getChangeLogJSON({
branchName: '',
...getManagerConfig(config, manager),
...dependency,
...update
})

result.push({...updatedDependency, changelog: logJSON})
}

return result
}
111 changes: 111 additions & 0 deletions src/getPrCommentBody.ts
@@ -0,0 +1,111 @@
import {PackageDependency} from 'renovate/dist/manager/types'
import {ChangeLogResult} from 'renovate/dist/workers/pr/changelog'
import {sanitizeMarkdown} from 'renovate/dist/util/markdown'
import {UpdatedDependencyWithChangelog} from './types'

export const commentTitle = '# Dependency updates summary'
const footer =
'\n---\n\nThis comment content is generated by [Renovate Bot](https://github.com/renovatebot/renovate)'

export function getPrCommentBody(
dependencies: UpdatedDependencyWithChangelog[]
): string {
const content = dependencies.map(getDependencyChangeContent)
return `${commentTitle}
This PR contains the following updates:
<table>
${content.map(x => x.tableRow).join('\n\n')}
</table>
---
### Release notes
${content.map(x => x.changelog).join('\n\n')}
${footer}`
}

function getDependencyChangeContent({
dependency,
update,
changelog
}: UpdatedDependencyWithChangelog): {tableRow: string; changelog: string} {
const dependencyLink = getDependencyNameLinked(dependency)
const type = dependency.prettyDepType ?? dependency.depType
const from = update.displayFrom ?? update.currentVersion
const to = update.displayTo ?? update.newVersion

const change = `<code>${from}</code> → <code>${to}</code>`

return {
tableRow: `<tr>
<td>${dependencyLink}</td>
<td>${type}</td>
<td>${change}</td>
</tr>
`,
changelog: `<details><summary>${dependency.depName}</summary>
${getReleaseNotes(dependencyLink, changelog)}</details>`
}
}

function getReleaseNotes(
dependencyLink: string,
changelog: ChangeLogResult | null
): string {
const releases =
changelog?.versions?.map(x => {
const versionWithPrefix = x.version.startsWith('v')
? x.version
: `v${x.version}`

const header = x.releaseNotes
? `### [\`${versionWithPrefix}\`](${x.releaseNotes.url})`
: `### \`${versionWithPrefix}\``

return `${header}
${x.compare.url ? `[Compare Source](${x.compare.url})` : ''}
${x.releaseNotes?.body ?? ''}`
}) ?? []

if (releases.length === 0) {
return `<blockquote></p><p>No changelog found, please review changelog from official resources of ${dependencyLink}</blockquote>`
}

return sanitizeMarkdown(`
<blockquote>
<p></p>
${releases.join('\n\n')}
</blockquote>`)
}

function getDependencyNameLinked({
depName,
homepage,
sourceUrl,
dependencyUrl,
changelogUrl
}: // eslint-disable-next-line @typescript-eslint/no-explicit-any
PackageDependency & Record<string, any>): string {
let depNameLinked = depName || ''
const primaryLink = homepage || sourceUrl || dependencyUrl
if (primaryLink) {
depNameLinked = `<a href="${primaryLink}">${depNameLinked}</a>`
}
const otherLinks = []
if (homepage && sourceUrl) {
otherLinks.push(`<a href="${sourceUrl}">source</a>`)
}
if (changelogUrl) {
otherLinks.push(`<a href="${changelogUrl}">changelog</a>`)
}
if (otherLinks.length) {
depNameLinked += ` (${otherLinks.join(', ')})`
}

return depNameLinked
}
54 changes: 54 additions & 0 deletions src/getRenovateConfig.ts
@@ -0,0 +1,54 @@
import {parseConfigs, RenovateConfig} from 'renovate/dist/config'
import {setUtilConfig} from 'renovate/dist/util'
import {getRepositoryConfig} from 'renovate/dist/workers/global'
import {globalInitialize} from 'renovate/dist/workers/global/initialize'
import {initRepo} from 'renovate/dist/workers/repository/init'

export async function getRenovateConfig({
token,
owner,
repo
}: {
token: string
owner: string
repo: string
}): Promise<RenovateConfig> {
const globalConfig = await parseConfigs(
{
...process.env,
GITHUB_COM_TOKEN: token
},
[
// this might prevent renovate from making changes to the repository
'--dry-run',
'true',
// this prevents renovate from creating the onboarding branch
'--onboarding',
'false',
// this prevents renovate from complaining that the onboarding branch does not exist
'--require-config',
'false',
'--token',
token
]
)

// not sure if it's necessary, but it probably is, renovate uses this setting to use the locked version as the current version
globalConfig.rangeStrategy = 'update-lockfile'
// username and gitAuthor are only necessary for writing data, we only use Renovate to read data
globalConfig.gitAuthor =
'github-actions <41898282+github-actions[bot]@users.noreply.github.com>'
globalConfig.username = 'github-actions[bot]'
// otherwise renovate will only be able to work with branch with `renovate/` prefix
globalConfig.branchPrefix = ''

// this is necessary to get only one update from renovate, so we can just replace the latest version with the verion from the branch
globalConfig.separateMajorMinor = false

let config = await globalInitialize(globalConfig)

config = await getRepositoryConfig(config, `${owner}/${repo}`)
await setUtilConfig(config)

return await initRepo(config)
}
69 changes: 69 additions & 0 deletions src/getUpdatedDependencies.ts
@@ -0,0 +1,69 @@
import {PackageDependency, PackageFile} from 'renovate/dist/manager/types'
import {UpdatedDependency} from './types'

export function* getUpdatedDependencies(
baseDependencies: Record<string, PackageFile[]>,
headDependencies: Record<string, PackageFile[]>
): IterableIterator<UpdatedDependency> {
for (const managerName in baseDependencies) {
const basePackageList = baseDependencies[managerName]
const headPackageList = headDependencies[managerName]

if (
!headPackageList ||
headPackageList.length === 0 ||
basePackageList.length === 0
) {
continue
}

for (const basePackage of basePackageList) {
const headPackage = headPackageList.find(
x => x.packageFile === basePackage.packageFile
)

if (!headPackage) {
// the package seems to be removed from the head
continue
}

for (const baseDependency of basePackage.deps) {
const headDependency = headPackage.deps.find(
x =>
x.depName === baseDependency.depName &&
x.depType === baseDependency.depType
)

if (!headDependency) {
// the dependency seems to be removed from the head
continue
}

if (!isSameVersion(baseDependency, headDependency)) {
if (!baseDependency.updates || baseDependency.updates.length === 0) {
continue
}

const [update] = baseDependency.updates // there should be a single update because we `fetchUpdates` on the base and use the rangeStrategy of 'update-lockfile'
yield {
manager: managerName,
packageFile: basePackage,
update,
dependency: baseDependency
}
}
}
}
}
}

function isSameVersion(
a: PackageDependency<Record<string, unknown>>,
b: PackageDependency<Record<string, unknown>>
): boolean {
if (a.lockedVersion && b.lockedVersion) {
return a.lockedVersion === b.lockedVersion
}

return a.currentValue === b.currentValue
}
35 changes: 35 additions & 0 deletions src/githubActionsBunyanStream.ts
@@ -0,0 +1,35 @@
import {ERROR, INFO, Stream, WARN} from 'bunyan'
import {BunyanRecord} from 'renovate/dist/logger/utils'
import * as core from '@actions/core'
import {Writable} from 'stream'

class GithubActionsStream extends Writable {
constructor() {
super({
objectMode: true
})
}

_write(rec: BunyanRecord, _: unknown, next: () => void): void {
if (rec.level < INFO) {
core.debug(rec.msg)
} else if (rec.level < WARN) {
core.info(rec.msg)
} else if (rec.level < ERROR) {
core.warning(rec.msg)
} else {
core.error(rec.msg)
}

next()
}
}

export function createGithubActionsBunyanStream(): Stream {
return {
name: 'github-actions',
level: 'debug',
stream: new GithubActionsStream(),
type: 'raw'
}
}

0 comments on commit 7aac2c2

Please sign in to comment.