@@ -8,9 +8,13 @@
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"filenames/match-regex": "off",
|
||||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"import/named": "off",
|
||||
"no-unused-vars": "off",
|
||||
"sort-imports": "off",
|
||||
"i18n-text/no-en": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
|
||||
@@ -138,9 +138,9 @@ The action supports flexible configuration options to modify vast areas of its b
|
||||
|
||||
⚠️ Please note: It is required to have a `checkout` step prior to the changelog step, to allow the action to discover the configuration file.
|
||||
|
||||
⚠️ When running this action for a non tags trigger the `toTag` can't be automatically resolved, as such it is either required to provide `toTag` directly, or to specify a different `fetch-depth:` with the checkout action to include tags.
|
||||
⚠️ When running this action for a non tags trigger the `toTag` will be automatically resolved using the latest tag as retrieved by the git API.
|
||||
|
||||
💡 By default not specifying `fromTag` or `toTag` will resolve `toTag` from either the `ref` or alternatively fallback to the latest tag from git. `fromTag` is resolved by sorting tags using [semver](https://semver.org/). Check the [configuration](#configuration-specification) for alternatives.
|
||||
💡 By default not specifying `fromTag` or `toTag` will resolve `toTag` from either the `ref` or alternatively fallback to the latest tag from the git API. `fromTag` is resolved by sorting tags using [semver](https://semver.org/). Check the [configuration](#configuration-specification) for alternatives.
|
||||
|
||||
This configuration is a `.json` file in the following format.
|
||||
|
||||
|
||||
@@ -28,6 +28,26 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('Should match generated changelog (unspecified tags)', async () => {
|
||||
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
||||
const releaseNotesBuilder = new ReleaseNotesBuilder(
|
||||
null,
|
||||
'.',
|
||||
'mikepenz',
|
||||
'action-junit-report-legacy',
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
|
||||
const changeLog = await releaseNotesBuilder.build()
|
||||
console.log(changeLog)
|
||||
expect(changeLog).toStrictEqual(`## 🐛 Fixes\n\n- Stacktrace Data can be an array\n - PR: #39\n\n`)
|
||||
})
|
||||
|
||||
it('Should use empty placeholder', async () => {
|
||||
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
||||
const releaseNotesBuilder = new ReleaseNotesBuilder(
|
||||
|
||||
+1377
-58
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+943
-1401
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -35,19 +35,19 @@
|
||||
"@actions/core": "^1.4.0",
|
||||
"@actions/exec": "^1.1.0",
|
||||
"@actions/github": "^5.0.0",
|
||||
"@octokit/rest": "^18.7.2",
|
||||
"@octokit/rest": "^18.9.0",
|
||||
"@types/semver": "^7.3.8",
|
||||
"moment": "^2.29.1",
|
||||
"semver": "^7.3.5",
|
||||
"webpack": "^5.47.1"
|
||||
"webpack": "^5.50.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^16.4.7",
|
||||
"@typescript-eslint/parser": "^4.28.5",
|
||||
"@vercel/ncc": "^0.29.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-plugin-github": "^4.1.5",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"@typescript-eslint/parser": "^4.29.1",
|
||||
"@vercel/ncc": "^0.29.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-github": "^4.2.0",
|
||||
"eslint-plugin-jest": "^24.4.0",
|
||||
"jest": "^27.0.6",
|
||||
"jest-circus": "^27.0.6",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import moment from 'moment'
|
||||
import * as core from '@actions/core'
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import moment from 'moment'
|
||||
|
||||
export interface CommitInfo {
|
||||
sha: string
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import {
|
||||
retrieveRepositoryPath,
|
||||
resolveConfiguration,
|
||||
retrieveRepositoryPath,
|
||||
writeOutput
|
||||
} from './utils'
|
||||
import * as github from '@actions/github'
|
||||
import {ReleaseNotesBuilder} from './releaseNotesBuilder'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import moment from 'moment'
|
||||
|
||||
import * as core from '@actions/core'
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import {Unpacked} from './utils'
|
||||
import moment from 'moment'
|
||||
|
||||
export interface PullRequestInfo {
|
||||
number: number
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
import {Octokit} from '@octokit/rest'
|
||||
import {Commits, CommitInfo, filterCommits} from './commits'
|
||||
import {PullRequestInfo, PullRequests} from './pullRequests'
|
||||
import {buildChangelog} from './transform'
|
||||
import * as core from '@actions/core'
|
||||
import {CommitInfo, Commits, filterCommits} from './commits'
|
||||
import {Configuration, DefaultConfiguration} from './configuration'
|
||||
import {PullRequestInfo, PullRequests} from './pullRequests'
|
||||
import {Octokit} from '@octokit/rest'
|
||||
import {buildChangelog} from './transform'
|
||||
import {failOrError} from './utils'
|
||||
|
||||
export interface ReleaseNotesOptions {
|
||||
|
||||
+37
-57
@@ -1,11 +1,9 @@
|
||||
import {Configuration, DefaultConfiguration} from './configuration'
|
||||
import * as github from '@actions/github'
|
||||
import * as core from '@actions/core'
|
||||
import {createCommandManager} from './gitHelper'
|
||||
import {failOrError} from './utils'
|
||||
import {Configuration, DefaultConfiguration} from './configuration'
|
||||
import {Octokit} from '@octokit/rest'
|
||||
import {Tags} from './tags'
|
||||
import {ReleaseNotes} from './releaseNotes'
|
||||
import {Tags} from './tags'
|
||||
import {failOrError} from './utils'
|
||||
import {fillAdditionalPlaceholders} from './transform'
|
||||
|
||||
export class ReleaseNotesBuilder {
|
||||
@@ -23,25 +21,6 @@ export class ReleaseNotesBuilder {
|
||||
) {}
|
||||
|
||||
async build(): Promise<string | null> {
|
||||
// ensure to resolve the toTag if it was not provided
|
||||
if (!this.toTag) {
|
||||
// if not specified try to retrieve tag from github.context.ref
|
||||
if (github.context.ref.startsWith('refs/tags/')) {
|
||||
this.toTag = github.context.ref.replace('refs/tags/', '')
|
||||
core.info(
|
||||
`🔖 Resolved current tag (${this.toTag}) from the 'github.context.ref'`
|
||||
)
|
||||
} else {
|
||||
// if not specified try to retrieve tag from git
|
||||
const gitHelper = await createCommandManager(this.repositoryPath)
|
||||
const latestTag = await gitHelper.latestTag()
|
||||
this.toTag = latestTag
|
||||
core.info(
|
||||
`🔖 Resolved current tag (${this.toTag}) from 'git rev-list --tags --skip=0 --max-count=1'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.owner) {
|
||||
failOrError(`💥 Missing or couldn't resolve 'owner'`, this.failOnError)
|
||||
return null
|
||||
@@ -57,14 +36,6 @@ export class ReleaseNotesBuilder {
|
||||
core.setOutput('repo', this.repo)
|
||||
core.debug(`Resolved 'repo' as ${this.repo}`)
|
||||
}
|
||||
|
||||
if (!this.toTag) {
|
||||
failOrError(`💥 Missing or couldn't resolve 'toTag'`, this.failOnError)
|
||||
return null
|
||||
} else {
|
||||
core.setOutput('toTag', this.toTag)
|
||||
core.debug(`Resolved 'toTag' as ${this.toTag}`)
|
||||
}
|
||||
core.endGroup()
|
||||
|
||||
// load octokit instance
|
||||
@@ -72,34 +43,43 @@ export class ReleaseNotesBuilder {
|
||||
auth: `token ${this.token || process.env.GITHUB_TOKEN}`
|
||||
})
|
||||
|
||||
// ensure to resolve the fromTag if it was not provided specifically
|
||||
if (!this.fromTag) {
|
||||
core.startGroup(`🔖 Resolve previous tag`)
|
||||
core.debug(`fromTag undefined, trying to resolve via API`)
|
||||
const tagsApi = new Tags(octokit)
|
||||
// ensure proper from <-> to tag range
|
||||
core.startGroup(`🔖 Resolve tags`)
|
||||
const tagsApi = new Tags(octokit)
|
||||
const tagRange = await tagsApi.retrieveRange(
|
||||
this.repositoryPath,
|
||||
this.owner,
|
||||
this.repo,
|
||||
this.fromTag,
|
||||
this.toTag,
|
||||
this.ignorePreReleases,
|
||||
this.configuration.max_tags_to_fetch ||
|
||||
DefaultConfiguration.max_tags_to_fetch,
|
||||
this.configuration.tag_resolver || DefaultConfiguration.tag_resolver
|
||||
)
|
||||
|
||||
const previousTag = await tagsApi.findPredecessorTag(
|
||||
this.repositoryPath,
|
||||
this.owner,
|
||||
this.repo,
|
||||
this.toTag,
|
||||
this.ignorePreReleases,
|
||||
this.configuration.max_tags_to_fetch ||
|
||||
DefaultConfiguration.max_tags_to_fetch,
|
||||
this.configuration.tag_resolver || DefaultConfiguration.tag_resolver
|
||||
)
|
||||
if (previousTag == null) {
|
||||
failOrError(
|
||||
`💥 Unable to retrieve previous tag given ${this.toTag}`,
|
||||
this.failOnError
|
||||
)
|
||||
return null
|
||||
}
|
||||
this.fromTag = previousTag.name
|
||||
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`)
|
||||
core.endGroup()
|
||||
const thisTag = tagRange.to?.name
|
||||
if (!thisTag) {
|
||||
failOrError(`💥 Missing or couldn't resolve 'toTag'`, this.failOnError)
|
||||
return null
|
||||
} else {
|
||||
this.toTag = thisTag
|
||||
core.setOutput('toTag', thisTag)
|
||||
core.debug(`Resolved 'toTag' as ${thisTag}`)
|
||||
}
|
||||
|
||||
const previousTag = tagRange.from?.name
|
||||
if (previousTag == null) {
|
||||
failOrError(
|
||||
`💥 Unable to retrieve previous tag given ${this.toTag}`,
|
||||
this.failOnError
|
||||
)
|
||||
return null
|
||||
}
|
||||
this.fromTag = previousTag
|
||||
core.debug(`fromTag resolved via previousTag as: ${previousTag}`)
|
||||
core.endGroup()
|
||||
|
||||
const options = {
|
||||
owner: this.owner,
|
||||
repo: this.repo,
|
||||
|
||||
+96
-11
@@ -1,10 +1,16 @@
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import * as semver from 'semver'
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import {SemVer} from 'semver'
|
||||
import {TagResolver} from './configuration'
|
||||
import {createCommandManager} from './gitHelper'
|
||||
|
||||
export interface TagResult {
|
||||
from: TagInfo | null
|
||||
to: TagInfo | null
|
||||
}
|
||||
|
||||
export interface TagInfo {
|
||||
name: string
|
||||
commit: string
|
||||
@@ -51,19 +57,12 @@ export class Tags {
|
||||
}
|
||||
|
||||
async findPredecessorTag(
|
||||
sortedTags: TagInfo[],
|
||||
repositoryPath: string,
|
||||
owner: string,
|
||||
repo: string,
|
||||
tag: string,
|
||||
ignorePreReleases: boolean,
|
||||
maxTagsToFetch: number,
|
||||
tagResolver: TagResolver
|
||||
ignorePreReleases: boolean
|
||||
): Promise<TagInfo | null> {
|
||||
const tags = sortTags(
|
||||
await this.getTags(owner, repo, maxTagsToFetch),
|
||||
tagResolver
|
||||
)
|
||||
|
||||
const tags = sortedTags
|
||||
try {
|
||||
const length = tags.length
|
||||
if (tags.length > 1) {
|
||||
@@ -102,6 +101,92 @@ export class Tags {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async retrieveRange(
|
||||
repositoryPath: string,
|
||||
owner: string,
|
||||
repo: string,
|
||||
fromTag: string | null,
|
||||
toTag: string | null,
|
||||
ignorePreReleases: boolean,
|
||||
maxTagsToFetch: number,
|
||||
tagResolver: TagResolver
|
||||
): Promise<TagResult> {
|
||||
const tags = sortTags(
|
||||
await this.getTags(owner, repo, maxTagsToFetch),
|
||||
tagResolver
|
||||
)
|
||||
|
||||
let resultToTag: TagInfo | null
|
||||
let resultFromTag: TagInfo | null
|
||||
|
||||
// ensure to resolve the toTag if it was not provided
|
||||
if (!toTag) {
|
||||
// if not specified try to retrieve tag from github.context.ref
|
||||
if (github.context.ref?.startsWith('refs/tags/') === true) {
|
||||
toTag = github.context.ref.replace('refs/tags/', '')
|
||||
core.info(
|
||||
`🔖 Resolved current tag (${toTag}) from the 'github.context.ref'`
|
||||
)
|
||||
resultToTag = {
|
||||
name: toTag,
|
||||
commit: toTag
|
||||
}
|
||||
} else if (tags.length > 1) {
|
||||
resultToTag = tags[0]
|
||||
core.info(
|
||||
`🔖 Resolved current tag (${resultToTag.name}) from the tags git API`
|
||||
)
|
||||
} else {
|
||||
// if not specified try to retrieve tag from git
|
||||
const gitHelper = await createCommandManager(repositoryPath)
|
||||
const latestTag = await gitHelper.latestTag()
|
||||
core.info(
|
||||
`🔖 Resolved current tag (${latestTag}) from 'git rev-list --tags --skip=0 --max-count=1'`
|
||||
)
|
||||
resultToTag = {
|
||||
name: latestTag,
|
||||
commit: latestTag
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resultToTag = {
|
||||
name: toTag,
|
||||
commit: toTag
|
||||
}
|
||||
}
|
||||
|
||||
// ensure toTag is specified
|
||||
toTag = resultToTag.name
|
||||
|
||||
// resolve the fromTag if not defined
|
||||
if (!fromTag) {
|
||||
core.debug(`fromTag undefined, trying to resolve via API`)
|
||||
|
||||
resultFromTag = await this.findPredecessorTag(
|
||||
tags,
|
||||
repositoryPath,
|
||||
toTag,
|
||||
ignorePreReleases
|
||||
)
|
||||
|
||||
if (resultFromTag != null) {
|
||||
core.info(
|
||||
`🔖 Resolved previous tag (${resultFromTag.name}) from the tags git API`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
resultFromTag = {
|
||||
name: fromTag,
|
||||
commit: fromTag
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
from: resultFromTag,
|
||||
to: resultToTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
import {PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||
import * as core from '@actions/core'
|
||||
import {ReleaseNotesOptions} from './releaseNotes'
|
||||
import {
|
||||
Extractor,
|
||||
Category,
|
||||
Configuration,
|
||||
Transformer,
|
||||
DefaultConfiguration
|
||||
DefaultConfiguration,
|
||||
Extractor,
|
||||
Transformer
|
||||
} from './configuration'
|
||||
import {PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||
import {ReleaseNotesOptions} from './releaseNotes'
|
||||
|
||||
export function buildChangelog(
|
||||
prs: PullRequestInfo[],
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import * as fs from 'fs'
|
||||
import {Configuration, DefaultConfiguration} from './configuration'
|
||||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import {Configuration, DefaultConfiguration} from './configuration'
|
||||
|
||||
/**
|
||||
* Resolves the repository path, relatively to the GITHUB_WORKSPACE
|
||||
|
||||
Reference in New Issue
Block a user