@@ -0,0 +1,25 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
|
||||
{
|
||||
"name": "Node.js & TypeScript",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:16-bullseye",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/devcontainers-contrib/features/typescript:1": {}
|
||||
}
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "npm install",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"printWidth": 140,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
|
||||
@@ -60,8 +60,6 @@ Specify the action as part of your GitHub actions workflow:
|
||||
- name: "Build Changelog"
|
||||
id: build_changelog
|
||||
uses: mikepenz/release-changelog-builder-action@{latest-release}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
### Action outputs
|
||||
@@ -193,9 +191,11 @@ The action supports flexible configuration options to modify vast areas of its b
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
> **Note** Defaults for the configuration can be found in the [configuration.ts](https://github.com/mikepenz/release-changelog-builder-action/blob/develop/src/configuration.ts)
|
||||
|
||||
> **Warning** It is required to have a `checkout` step prior to the changelog step if `configuration` is used, to allow the action to discover the configuration file. Use `configurationJson` as alternative.
|
||||
|
||||
This configuration is a `.json` file in the following format.
|
||||
This configuration is a `.json` file in the following format. (The below shocases *example* configurations for all possible options. In most scenarios most of the settings will not be needed, and the defaults will be appropiate.)
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -253,6 +253,7 @@ This configuration is a `.json` file in the following format.
|
||||
"target": "- $4\n - $6"
|
||||
}
|
||||
],
|
||||
"trim_values": false,
|
||||
"max_tags_to_fetch": 200,
|
||||
"max_pull_requests": 200,
|
||||
"max_back_track_time_days": 365,
|
||||
@@ -276,8 +277,6 @@ Any section of the configuration can be omitted to have defaults apply.
|
||||
|
||||
> **Warning**: `ignore_labels` take precedence over category labels, allowing to specifically exclude certain PRs.
|
||||
|
||||
Defaults for the configuration can be found in the [configuration.ts](https://github.com/mikepenz/release-changelog-builder-action/blob/develop/src/configuration.ts)
|
||||
|
||||
Please see the [Configuration Specification](#configuration-specification) for detailed descriptions on the offered configuration options.
|
||||
|
||||
### Advanced workflow specification
|
||||
@@ -317,6 +316,8 @@ For advanced use cases additional settings can be provided to the action
|
||||
| `ignorePreReleases` | Allows to ignore pre-releases for changelog generation (E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick). Only used if `fromTag` was not specified. Default: false |
|
||||
| `failOnError` | Defines if the action will result in a build failure if problems occurred. Default: false |
|
||||
| `fetchReviewers` | Will enable fetching the users/reviewers who approved the PR. Default: false |
|
||||
| `fetchReleaseInformation` | Will enable fetching additional release information from tags. Default: false |
|
||||
| `fetchReviews` | Will enable fetching the reviews on of the PR. Default: false |
|
||||
| `commitMode` | Special configuration for projects which work without PRs. Uses commit messages as changelog. This mode looses access to information only available for PRs. Default: false |
|
||||
|
||||
> **Warning**: `${{ secrets.GITHUB_TOKEN }}` only grants rights to the current repository, for other repositories please use a PAT (Personal Access Token).
|
||||
@@ -409,6 +410,7 @@ Table of descriptions for the `configuration.json` options to configure the resu
|
||||
| tag_resolver.filter | Defines a regex which is used to filter out tags not matching. |
|
||||
| tag_resolver.transformer | Defines a regex transformer used to optionally transform the tag after the filter was applied. Allows to adjust the format to e.g. semver. |
|
||||
| base_branches | The target branches for the merged PR, ingnores PRs with different target branch. Values can be a `regex`. Default: allow all base branches |
|
||||
| trim_values | Defines if all values inserted in templates are `trimmed`. Default: false |
|
||||
|
||||
## Experimental 🧪
|
||||
|
||||
@@ -523,6 +525,7 @@ it('Test custom changelog builder', async () => {
|
||||
false, // ignorePrePrelease
|
||||
true, // enable to fetch reviewers
|
||||
false, // enable to fetch release information
|
||||
false, // enable to fetch reviews
|
||||
false, // commitMode
|
||||
configuration // configuration
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ it('Should have empty changelog (tags)', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -40,6 +41,7 @@ it('Should match generated changelog (tags)', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -65,6 +67,7 @@ it('Should match generated changelog (refs)', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -98,6 +101,7 @@ it('Should match generated changelog and replace all occurrences (refs)', async
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -133,6 +137,7 @@ it('Should match ordered ASC', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -153,6 +158,7 @@ it('Should match ordered DESC', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -173,6 +179,7 @@ it('Should match ordered by title ASC', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -195,6 +202,7 @@ it('Should match ordered by title DESC', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -217,6 +225,7 @@ it('Should ignore PRs not merged into develop branch', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -237,6 +246,7 @@ it('Should ignore PRs not merged into main branch', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -47,6 +48,7 @@ it('Should match generated changelog (unspecified tags)', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -71,6 +73,7 @@ it('Should use empty placeholder', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -95,6 +98,7 @@ it('Should fill empty placeholders', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -121,6 +125,7 @@ it('Should fill `template` placeholders', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -147,6 +152,7 @@ it('Should fill `template` placeholders, ignore', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -173,6 +179,7 @@ it('Uncategorized category', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false,
|
||||
configuration
|
||||
)
|
||||
@@ -199,6 +206,7 @@ it('Verify commit based changelog', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
true,
|
||||
configuration
|
||||
)
|
||||
@@ -225,6 +233,7 @@ it('Verify commit based changelog, with emoji categorisation', async () => {
|
||||
false,
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
true,
|
||||
configuration
|
||||
)
|
||||
@@ -251,6 +260,7 @@ it('Verify default inclusion of open PRs', async () => {
|
||||
false, // ignorePrePrelease
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false, // commitMode
|
||||
configuration // configuration
|
||||
)
|
||||
@@ -277,6 +287,7 @@ it('Verify custom categorisation of open PRs', async () => {
|
||||
false, // ignorePrePrelease
|
||||
false, // enable to fetch reviewers
|
||||
false, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false, // commitMode
|
||||
configuration // configuration
|
||||
)
|
||||
@@ -303,6 +314,7 @@ it('Verify reviewers who approved are fetched and also release information', asy
|
||||
false, // ignorePrePrelease
|
||||
true, // enable to fetch reviewers
|
||||
true, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false, // commitMode
|
||||
configuration // configuration
|
||||
)
|
||||
@@ -330,6 +342,7 @@ it('Fetch release information', async () => {
|
||||
false, // ignorePrePrelease
|
||||
false, // enable to fetch reviewers
|
||||
true, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false, // commitMode
|
||||
configuration // configuration
|
||||
)
|
||||
@@ -355,6 +368,7 @@ it('Fetch release information for non existing tag / release', async () => {
|
||||
false, // ignorePrePrelease
|
||||
false, // enable to fetch reviewers
|
||||
true, // enable to fetch tag release information
|
||||
false, // enable to fetch reviews
|
||||
false, // commitMode
|
||||
configuration // configuration
|
||||
)
|
||||
|
||||
@@ -140,6 +140,7 @@ it('Extract label from title, combined regex', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -169,6 +170,7 @@ it('Extract label from title and body, combined regex', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -201,6 +203,7 @@ it('Extract label from title, split regex', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -233,6 +236,7 @@ it('Extract label from title, match', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -260,6 +264,7 @@ it('Extract label from title, match multiple', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -288,6 +293,7 @@ it('Extract label from title, match multiple, custon non matching label', async
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration
|
||||
})
|
||||
@@ -403,6 +409,7 @@ it('Match multiple labels exhaustive for category', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -429,6 +436,7 @@ it('Deduplicate duplicated PRs', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -456,6 +464,7 @@ it('Deduplicate duplicated PRs DESC', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -488,6 +497,7 @@ it('Use empty_content for empty category', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -511,6 +521,7 @@ it('Commit SHA-1 in commitMode', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: true,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -531,6 +542,7 @@ it('Release Diff', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: true,
|
||||
fetchReviews: false,
|
||||
commitMode: true,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -570,6 +582,7 @@ it('Use exclude labels to not include a PR within a category.', async () => {
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration: customConfig
|
||||
})
|
||||
@@ -628,6 +641,7 @@ it('Extract custom placeholder from PR body and replace in global template', asy
|
||||
failOnError: false,
|
||||
fetchReviewers: false,
|
||||
fetchReleaseInformation: false,
|
||||
fetchReviews: false,
|
||||
commitMode: false,
|
||||
configuration: customConfig
|
||||
})
|
||||
|
||||
@@ -34,6 +34,9 @@ inputs:
|
||||
fetchReleaseInformation:
|
||||
description: 'Will enable fetching release information for the tags. E.g. creation date'
|
||||
default: "false"
|
||||
fetchReviews:
|
||||
description: 'Will enable fetching the reviews (comments) attached to the PR'
|
||||
default: "false"
|
||||
commitMode:
|
||||
description: 'Enables a `light` commit based mode. This mode generates changelogs based on the commits. Please note that this is not officially supported, and lacks a lot of features only possible with PRs.'
|
||||
default: "false"
|
||||
@@ -41,6 +44,7 @@ inputs:
|
||||
description: 'If defined, the changelog will get written to this file. (relative to the checkout dir)'
|
||||
token:
|
||||
description: 'Defines the token to use to execute the git API requests with, uses `env.GITHUB_TOKEN` by default'
|
||||
default: ${{ github.token }}
|
||||
baseUrl:
|
||||
description: 'Defines the base url for GitHub Enterprise authentication, uses `https://api.github.com` by default'
|
||||
outputs:
|
||||
|
||||
+229
-92
@@ -218,7 +218,8 @@ exports.DefaultConfiguration = {
|
||||
transformer: undefined // transforms the tag name using the regex, run after the filter
|
||||
},
|
||||
base_branches: [],
|
||||
custom_placeholders: []
|
||||
custom_placeholders: [],
|
||||
trim_values: false // defines if values are being trimmed prior to inserting
|
||||
};
|
||||
|
||||
|
||||
@@ -420,8 +421,9 @@ function run() {
|
||||
const failOnError = core.getInput('failOnError') === 'true';
|
||||
const fetchReviewers = core.getInput('fetchReviewers') === 'true';
|
||||
const fetchReleaseInformation = core.getInput('fetchReleaseInformation') === 'true';
|
||||
const fetchReviews = core.getInput('fetchReviews') === 'true';
|
||||
const commitMode = core.getInput('commitMode') === 'true';
|
||||
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, fetchReviewers, fetchReleaseInformation, commitMode, configuration).build();
|
||||
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, fetchReviewers, fetchReleaseInformation, fetchReviews, commitMode, configuration).build();
|
||||
core.setOutput('changelog', result);
|
||||
// write the result in changelog to file if possible
|
||||
const outputFile = core.getInput('outputFile');
|
||||
@@ -488,9 +490,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.compare = exports.sortPullRequests = exports.PullRequests = void 0;
|
||||
exports.compare = exports.sortPullRequests = exports.PullRequests = exports.EMPTY_COMMENT_INFO = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const moment_1 = __importDefault(__nccwpck_require__(9623));
|
||||
exports.EMPTY_COMMENT_INFO = {
|
||||
id: 0,
|
||||
htmlURL: '',
|
||||
submittedAt: undefined,
|
||||
author: '',
|
||||
body: '',
|
||||
state: undefined
|
||||
};
|
||||
class PullRequests {
|
||||
constructor(octokit) {
|
||||
this.octokit = octokit;
|
||||
@@ -512,7 +522,7 @@ class PullRequests {
|
||||
});
|
||||
}
|
||||
getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests) {
|
||||
var e_1, _a;
|
||||
var _a, e_1, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const mergedPRs = [];
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
@@ -520,32 +530,39 @@ class PullRequests {
|
||||
repo,
|
||||
state: 'closed',
|
||||
sort: 'merged',
|
||||
per_page: '100',
|
||||
per_page: `${Math.min(100, maxPullRequests)}`,
|
||||
direction: 'desc'
|
||||
});
|
||||
try {
|
||||
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
|
||||
const response = _c.value;
|
||||
const prs = response.data;
|
||||
for (const pr of prs.filter(p => !!p.merged_at)) {
|
||||
mergedPRs.push(mapPullRequest(pr, 'merged'));
|
||||
}
|
||||
const firstPR = prs[0];
|
||||
if (firstPR === undefined ||
|
||||
(firstPR.merged_at && fromDate.isAfter((0, moment_1.default)(firstPR.merged_at))) ||
|
||||
mergedPRs.length >= maxPullRequests) {
|
||||
if (mergedPRs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
|
||||
for (var _d = true, _e = __asyncValues(this.octokit.paginate.iterator(options)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
|
||||
_c = _f.value;
|
||||
_d = false;
|
||||
try {
|
||||
const response = _c;
|
||||
const prs = response.data;
|
||||
for (const pr of prs.filter(p => !!p.merged_at)) {
|
||||
mergedPRs.push(mapPullRequest(pr, 'merged'));
|
||||
}
|
||||
// bail out early to not keep iterating on PRs super old
|
||||
return sortPrs(mergedPRs);
|
||||
const firstPR = prs[0];
|
||||
if (firstPR === undefined ||
|
||||
(firstPR.merged_at && fromDate.isAfter((0, moment_1.default)(firstPR.merged_at))) ||
|
||||
mergedPRs.length >= maxPullRequests) {
|
||||
if (mergedPRs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
|
||||
}
|
||||
// bail out early to not keep iterating on PRs super old
|
||||
return sortPrs(mergedPRs);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
_d = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
||||
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
@@ -553,7 +570,7 @@ class PullRequests {
|
||||
});
|
||||
}
|
||||
getOpen(owner, repo, maxPullRequests) {
|
||||
var e_2, _a;
|
||||
var _a, e_2, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const openPrs = [];
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
@@ -565,26 +582,33 @@ class PullRequests {
|
||||
direction: 'desc'
|
||||
});
|
||||
try {
|
||||
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
|
||||
const response = _c.value;
|
||||
const prs = response.data;
|
||||
for (const pr of prs) {
|
||||
openPrs.push(mapPullRequest(pr, 'open'));
|
||||
}
|
||||
const firstPR = prs[0];
|
||||
if (firstPR === undefined || openPrs.length >= maxPullRequests) {
|
||||
if (openPrs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
|
||||
for (var _d = true, _e = __asyncValues(this.octokit.paginate.iterator(options)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
|
||||
_c = _f.value;
|
||||
_d = false;
|
||||
try {
|
||||
const response = _c;
|
||||
const prs = response.data;
|
||||
for (const pr of prs) {
|
||||
openPrs.push(mapPullRequest(pr, 'open'));
|
||||
}
|
||||
// bail out early to not keep iterating on PRs super old
|
||||
return sortPrs(openPrs);
|
||||
const firstPR = prs[0];
|
||||
if (firstPR === undefined || openPrs.length >= maxPullRequests) {
|
||||
if (openPrs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
|
||||
}
|
||||
// bail out early to not keep iterating on PRs super old
|
||||
return sortPrs(openPrs);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
_d = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
||||
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
||||
}
|
||||
finally { if (e_2) throw e_2.error; }
|
||||
}
|
||||
@@ -592,7 +616,7 @@ class PullRequests {
|
||||
});
|
||||
}
|
||||
getReviewers(owner, repo, pr) {
|
||||
var e_3, _a;
|
||||
var _a, e_3, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
@@ -600,23 +624,66 @@ class PullRequests {
|
||||
pull_number: pr.number
|
||||
});
|
||||
try {
|
||||
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
|
||||
const response = _c.value;
|
||||
const reviews = response.data;
|
||||
pr.approvedReviewers = reviews
|
||||
.filter(r => r.state === 'APPROVED')
|
||||
.map(r => { var _a; return (_a = r.user) === null || _a === void 0 ? void 0 : _a.login; })
|
||||
.filter(r => !!r);
|
||||
for (var _d = true, _e = __asyncValues(this.octokit.paginate.iterator(options)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
|
||||
_c = _f.value;
|
||||
_d = false;
|
||||
try {
|
||||
const response = _c;
|
||||
const reviews = response.data;
|
||||
pr.approvedReviewers = reviews
|
||||
.filter(r => r.state === 'APPROVED')
|
||||
.map(r => { var _a; return (_a = r.user) === null || _a === void 0 ? void 0 : _a.login; })
|
||||
.filter(r => !!r);
|
||||
}
|
||||
finally {
|
||||
_d = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
||||
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
||||
}
|
||||
finally { if (e_3) throw e_3.error; }
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
getReviews(owner, repo, pr) {
|
||||
var _a, e_4, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr.number,
|
||||
sort: 'created',
|
||||
direction: 'desc'
|
||||
});
|
||||
const prReviews = [];
|
||||
try {
|
||||
for (var _d = true, _e = __asyncValues(this.octokit.paginate.iterator(options)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
|
||||
_c = _f.value;
|
||||
_d = false;
|
||||
try {
|
||||
const response = _c;
|
||||
const comments = response.data;
|
||||
for (const comment of comments) {
|
||||
prReviews.push(mapComment(comment));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
_d = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
||||
}
|
||||
finally { if (e_4) throw e_4.error; }
|
||||
}
|
||||
pr.reviews = prReviews;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -684,7 +751,7 @@ const mapPullRequest = (pr, status = 'open') => {
|
||||
baseBranch: pr.base.ref,
|
||||
branch: pr.head.ref,
|
||||
createdAt: (0, moment_1.default)(pr.created_at),
|
||||
mergedAt: pr.merged_at ? (0, moment_1.default)(pr.merged_at) : null,
|
||||
mergedAt: pr.merged_at ? (0, moment_1.default)(pr.merged_at) : undefined,
|
||||
mergeCommitSha: pr.merge_commit_sha || '',
|
||||
author: ((_a = pr.user) === null || _a === void 0 ? void 0 : _a.login) || '',
|
||||
repoName: pr.base.repo.full_name,
|
||||
@@ -694,9 +761,21 @@ const mapPullRequest = (pr, status = 'open') => {
|
||||
assignees: ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.map(asignee => (asignee === null || asignee === void 0 ? void 0 : asignee.login) || '')) || [],
|
||||
requestedReviewers: ((_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(reviewer => (reviewer === null || reviewer === void 0 ? void 0 : reviewer.login) || '')) || [],
|
||||
approvedReviewers: [],
|
||||
reviews: undefined,
|
||||
status
|
||||
});
|
||||
};
|
||||
const mapComment = (comment) => {
|
||||
var _a;
|
||||
return ({
|
||||
id: comment.id,
|
||||
htmlURL: comment.html_url,
|
||||
submittedAt: comment.submitted_at ? (0, moment_1.default)(comment.submitted_at) : undefined,
|
||||
author: ((_a = comment.user) === null || _a === void 0 ? void 0 : _a.login) || '',
|
||||
body: comment.body,
|
||||
state: comment.state
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -812,8 +891,9 @@ class ReleaseNotes {
|
||||
});
|
||||
}
|
||||
getMergedPullRequests(octokit) {
|
||||
var _a, _b;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { owner, repo, includeOpen, fetchReviewers, configuration } = this.options;
|
||||
const { owner, repo, includeOpen, fetchReviewers, fetchReviews, configuration } = this.options;
|
||||
const diffInfo = yield this.getCommitHistory(octokit);
|
||||
const commits = diffInfo.commitInfo;
|
||||
if (commits.length === 0) {
|
||||
@@ -832,7 +912,7 @@ class ReleaseNotes {
|
||||
core.info(`ℹ️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`);
|
||||
const pullRequestsApi = new pullRequests_1.PullRequests(octokit);
|
||||
const pullRequests = yield pullRequestsApi.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests || configuration_1.DefaultConfiguration.max_pull_requests);
|
||||
core.info(`ℹ️ Retrieved ${pullRequests.length} merged PRs for ${owner}/${repo}`);
|
||||
core.info(`ℹ️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} in date range from API`);
|
||||
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches || configuration_1.DefaultConfiguration.exclude_merge_branches);
|
||||
core.info(`ℹ️ Retrieved ${prCommits.length} release commits for ${owner}/${repo}`);
|
||||
// create array of commits for this release
|
||||
@@ -843,6 +923,7 @@ class ReleaseNotes {
|
||||
const mergedPullRequests = pullRequests.filter(pr => {
|
||||
return releaseCommitHashes.includes(pr.mergeCommitSha);
|
||||
});
|
||||
core.info(`ℹ️ Retrieved ${mergedPullRequests.length} merged PRs for ${owner}/${repo}`);
|
||||
let allPullRequests = mergedPullRequests;
|
||||
if (includeOpen) {
|
||||
// retrieve all open pull requests
|
||||
@@ -866,6 +947,9 @@ class ReleaseNotes {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (baseBranches.length !== 0) {
|
||||
core.info(`ℹ️ Retrieved ${mergedPullRequests.length} PRs for ${owner}/${repo} filtered by the 'base_branches' configuration.`);
|
||||
}
|
||||
if (fetchReviewers) {
|
||||
core.info(`ℹ️ Fetching reviewers was enabled`);
|
||||
// update PR information with reviewers who approved
|
||||
@@ -879,6 +963,19 @@ class ReleaseNotes {
|
||||
else {
|
||||
core.debug(`ℹ️ Fetching reviewers was disabled`);
|
||||
}
|
||||
if (fetchReviews) {
|
||||
core.info(`ℹ️ Fetching reviews was enabled`);
|
||||
// update PR information with reviewers who approved
|
||||
for (const pr of finalPrs) {
|
||||
yield pullRequestsApi.getReviews(owner, repo, pr);
|
||||
if ((((_a = pr.reviews) === null || _a === void 0 ? void 0 : _a.length) || 0) > 0) {
|
||||
core.info(`ℹ️ Retrieved ${((_b = pr.reviews) === null || _b === void 0 ? void 0 : _b.length) || 0} review(s) for PR ${owner}/${repo}/#${pr.number}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.debug(`ℹ️ Fetching reviews was disabled`);
|
||||
}
|
||||
return [diffInfo, finalPrs];
|
||||
});
|
||||
}
|
||||
@@ -968,7 +1065,7 @@ const tags_1 = __nccwpck_require__(7532);
|
||||
const utils_1 = __nccwpck_require__(918);
|
||||
const https_proxy_agent_1 = __nccwpck_require__(7219);
|
||||
class ReleaseNotesBuilder {
|
||||
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchReviewers = false, fetchReleaseInformation = false, commitMode, configuration) {
|
||||
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchReviewers = false, fetchReleaseInformation = false, fetchReviews = false, commitMode, configuration) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.token = token;
|
||||
this.repositoryPath = repositoryPath;
|
||||
@@ -981,6 +1078,7 @@ class ReleaseNotesBuilder {
|
||||
this.ignorePreReleases = ignorePreReleases;
|
||||
this.fetchReviewers = fetchReviewers;
|
||||
this.fetchReleaseInformation = fetchReleaseInformation;
|
||||
this.fetchReviews = fetchReviews;
|
||||
this.commitMode = commitMode;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
@@ -1063,6 +1161,7 @@ class ReleaseNotesBuilder {
|
||||
failOnError: this.failOnError,
|
||||
fetchReviewers: this.fetchReviewers,
|
||||
fetchReleaseInformation: this.fetchReleaseInformation,
|
||||
fetchReviews: this.fetchReviews,
|
||||
commitMode: this.commitMode,
|
||||
configuration: this.configuration
|
||||
};
|
||||
@@ -1137,7 +1236,7 @@ class Tags {
|
||||
this.octokit = octokit;
|
||||
}
|
||||
getTags(owner, repo, maxTagsToFetch) {
|
||||
var e_1, _a;
|
||||
var _a, e_1, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const tagsInfo = [];
|
||||
const options = this.octokit.repos.listTags.endpoint.merge({
|
||||
@@ -1147,25 +1246,32 @@ class Tags {
|
||||
per_page: 100
|
||||
});
|
||||
try {
|
||||
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
|
||||
const response = _c.value;
|
||||
const tags = response.data;
|
||||
for (const tag of tags) {
|
||||
tagsInfo.push({
|
||||
name: tag.name,
|
||||
commit: tag.commit.sha
|
||||
});
|
||||
for (var _d = true, _e = __asyncValues(this.octokit.paginate.iterator(options)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
|
||||
_c = _f.value;
|
||||
_d = false;
|
||||
try {
|
||||
const response = _c;
|
||||
const tags = response.data;
|
||||
for (const tag of tags) {
|
||||
tagsInfo.push({
|
||||
name: tag.name,
|
||||
commit: tag.commit.sha
|
||||
});
|
||||
}
|
||||
// for performance only fetch newest maxTagsToFetch tags!!
|
||||
if (tagsInfo.length >= maxTagsToFetch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// for performance only fetch newest maxTagsToFetch tags!!
|
||||
if (tagsInfo.length >= maxTagsToFetch) {
|
||||
break;
|
||||
finally {
|
||||
_d = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
||||
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
@@ -1466,6 +1572,7 @@ const core = __importStar(__nccwpck_require__(2186));
|
||||
const configuration_1 = __nccwpck_require__(5527);
|
||||
const pullRequests_1 = __nccwpck_require__(4217);
|
||||
const utils_1 = __nccwpck_require__(918);
|
||||
const EMPTY_MAP = new Map();
|
||||
function buildChangelog(diffInfo, prs, options) {
|
||||
// sort to target order
|
||||
const config = options.configuration;
|
||||
@@ -1521,7 +1628,7 @@ function buildChangelog(diffInfo, prs, options) {
|
||||
const transformedMap = new Map();
|
||||
// convert PRs to their text representation
|
||||
for (const pr of prs) {
|
||||
transformedMap.set(pr, transform(fillPrTemplate(pr, config.pr_template || configuration_1.DefaultConfiguration.pr_template, placeholders, placeholderPrMap), validatedTransformers));
|
||||
transformedMap.set(pr, transform(fillPrTemplate(pr, config.pr_template || configuration_1.DefaultConfiguration.pr_template, placeholders, placeholderPrMap, config), validatedTransformers));
|
||||
}
|
||||
core.info(`ℹ️ Used ${validatedTransformers.length} transformers to adjust message`);
|
||||
core.info(`✒️ Wrote messages for ${prs.length} pull requests`);
|
||||
@@ -1662,9 +1769,9 @@ function buildChangelog(diffInfo, prs, options) {
|
||||
placeholderMap.set('COMMITS', diffInfo.commits.toString());
|
||||
fillAdditionalPlaceholders(options, placeholderMap);
|
||||
let transformedChangelog = config.template || configuration_1.DefaultConfiguration.template;
|
||||
transformedChangelog = replacePlaceholders(transformedChangelog, placeholderMap, placeholders, placeholderPrMap);
|
||||
transformedChangelog = replacePrPlaceholders(transformedChangelog, placeholderPrMap);
|
||||
transformedChangelog = cleanupPrPlaceHolders(transformedChangelog, placeholders);
|
||||
transformedChangelog = replacePlaceholders(transformedChangelog, EMPTY_MAP, placeholderMap, placeholders, placeholderPrMap, config);
|
||||
transformedChangelog = replacePrPlaceholders(transformedChangelog, placeholderPrMap, config);
|
||||
transformedChangelog = cleanupPrPlaceholders(transformedChangelog, placeholders);
|
||||
core.info(`ℹ️ Filled template`);
|
||||
return transformedChangelog;
|
||||
}
|
||||
@@ -1676,7 +1783,7 @@ function replaceEmptyTemplate(template, options) {
|
||||
}
|
||||
const placeholderMap = new Map();
|
||||
fillAdditionalPlaceholders(options, placeholderMap);
|
||||
return replacePlaceholders(template, placeholderMap, placeholders);
|
||||
return replacePlaceholders(template, new Map(), placeholderMap, placeholders, undefined, options.configuration);
|
||||
}
|
||||
exports.replaceEmptyTemplate = replaceEmptyTemplate;
|
||||
function fillAdditionalPlaceholders(options, placeholderMap /* placeholderKey and original value */) {
|
||||
@@ -1697,8 +1804,10 @@ function fillAdditionalPlaceholders(options, placeholderMap /* placeholderKey an
|
||||
}
|
||||
placeholderMap.set('RELEASE_DIFF', `https://github.com/${options.owner}/${options.repo}/compare/${options.fromTag.name}...${options.toTag.name}`);
|
||||
}
|
||||
function fillPrTemplate(pr, template, placeholders /* placeholders to apply */, placeholderPrMap /* map to keep replaced placeholder values with their key */) {
|
||||
function fillPrTemplate(pr, template, placeholders /* placeholders to apply */, placeholderPrMap /* map to keep replaced placeholder values with their key */, configuration) {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
const arrayPlaceholderMap = new Map();
|
||||
fillReviewPlaceholders(arrayPlaceholderMap, 'REVIEWS', pr.reviews || []);
|
||||
const placeholderMap = new Map();
|
||||
placeholderMap.set('NUMBER', pr.number.toString());
|
||||
placeholderMap.set('TITLE', pr.title);
|
||||
@@ -1711,54 +1820,82 @@ function fillPrTemplate(pr, template, placeholders /* placeholders to apply */,
|
||||
placeholderMap.set('LABELS', ((_c = (_b = [...pr.labels]) === null || _b === void 0 ? void 0 : _b.filter(l => !l.startsWith('--rcba-'))) === null || _c === void 0 ? void 0 : _c.join(', ')) || '');
|
||||
placeholderMap.set('MILESTONE', pr.milestone || '');
|
||||
placeholderMap.set('BODY', pr.body);
|
||||
fillArrayPlaceholders(arrayPlaceholderMap, 'ASSIGNEES', pr.assignees || []);
|
||||
placeholderMap.set('ASSIGNEES', ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.join(', ')) || '');
|
||||
fillArrayPlaceholders(arrayPlaceholderMap, 'REVIEWERS', pr.requestedReviewers || []);
|
||||
placeholderMap.set('REVIEWERS', ((_e = pr.requestedReviewers) === null || _e === void 0 ? void 0 : _e.join(', ')) || '');
|
||||
fillArrayPlaceholders(arrayPlaceholderMap, 'APPROVERS', pr.approvedReviewers || []);
|
||||
placeholderMap.set('APPROVERS', ((_f = pr.approvedReviewers) === null || _f === void 0 ? void 0 : _f.join(', ')) || '');
|
||||
placeholderMap.set('BRANCH', pr.branch || '');
|
||||
placeholderMap.set('BASE_BRANCH', pr.baseBranch);
|
||||
return replacePlaceholders(template, placeholderMap, placeholders, placeholderPrMap);
|
||||
return replacePlaceholders(template, arrayPlaceholderMap, placeholderMap, placeholders, placeholderPrMap, configuration);
|
||||
}
|
||||
function replacePlaceholders(template, placeholderMap /* placeholderKey and original value */, placeholders /* placeholders to apply */, placeholderPrMap /* map to keep replaced placeholder values with their key */) {
|
||||
function replacePlaceholders(template, arrayPlaceholderMap /* arrayPlaceholderKey and original value */, placeholderMap /* placeholderKey and original value */, placeholders /* placeholders to apply */, placeholderPrMap /* map to keep replaced placeholder values with their key */, configuration) {
|
||||
let transformed = template;
|
||||
// replace array placeholders first
|
||||
for (const [key, value] of arrayPlaceholderMap) {
|
||||
transformed = handlePlaceholder(transformed, key, value, placeholders, placeholderPrMap, configuration);
|
||||
}
|
||||
// replace traditional placeholders
|
||||
for (const [key, value] of placeholderMap) {
|
||||
transformed = transformed.replaceAll(`\${{${key}}}`, value);
|
||||
// replace custom placeholders
|
||||
const phs = placeholders.get(key);
|
||||
if (phs) {
|
||||
for (const placeholder of phs) {
|
||||
const transformer = validateTransformer(placeholder.transformer);
|
||||
if (transformer === null || transformer === void 0 ? void 0 : transformer.pattern) {
|
||||
const extractedValue = value.replace(transformer.pattern, transformer.target);
|
||||
// note: `.replace` will return the full string again if there was no match
|
||||
if (extractedValue && extractedValue !== value) {
|
||||
if (placeholderPrMap) {
|
||||
(0, utils_1.createOrSet)(placeholderPrMap, placeholder.name, extractedValue);
|
||||
}
|
||||
transformed = transformed.replaceAll(`\${{${placeholder.name}}}`, extractedValue);
|
||||
if (core.isDebug()) {
|
||||
core.debug(` Custom Placeholder successfully matched data - ${extractValues} (${placeholder.name})`);
|
||||
}
|
||||
transformed = handlePlaceholder(transformed, key, value, placeholders, placeholderPrMap, configuration);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
function handlePlaceholder(template, key, value, placeholders /* placeholders to apply */, placeholderPrMap /* map to keep replaced placeholder values with their key */, configuration) {
|
||||
let transformed = template.replaceAll(`\${{${key}}}`, configuration.trim_values ? value.trim() : value);
|
||||
// replace custom placeholders
|
||||
const phs = placeholders.get(key);
|
||||
if (phs) {
|
||||
for (const placeholder of phs) {
|
||||
const transformer = validateTransformer(placeholder.transformer);
|
||||
if (transformer === null || transformer === void 0 ? void 0 : transformer.pattern) {
|
||||
const extractedValue = value.replace(transformer.pattern, transformer.target);
|
||||
// note: `.replace` will return the full string again if there was no match
|
||||
if (extractedValue && (extractedValue !== value || (extractedValue === value && value.match(transformer.pattern)))) {
|
||||
if (placeholderPrMap) {
|
||||
(0, utils_1.createOrSet)(placeholderPrMap, placeholder.name, extractedValue);
|
||||
}
|
||||
else if (core.isDebug() && extractedValue === value) {
|
||||
core.debug(` Custom Placeholder did result in the full original value returned. Skipping. (${placeholder.name})`);
|
||||
transformed = transformed.replaceAll(`\${{${placeholder.name}}}`, configuration.trim_values ? extractedValue.trim() : extractedValue);
|
||||
if (core.isDebug()) {
|
||||
core.debug(` Custom Placeholder successfully matched data - ${extractValues} (${placeholder.name})`);
|
||||
}
|
||||
}
|
||||
else if (core.isDebug() && extractedValue === value) {
|
||||
core.debug(` Custom Placeholder did result in the full original value returned. Skipping. (${placeholder.name})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
function replacePrPlaceholders(template, placeholderPrMap /* map with all pr related custom placeholder values */) {
|
||||
function fillArrayPlaceholders(placeholderMap /* placeholderKey and original value */, key, values) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
placeholderMap.set(`\${{${key}[${i}]}}`, values[i]);
|
||||
}
|
||||
placeholderMap.set(`\${{${key}[*]}}`, values.join(', '));
|
||||
}
|
||||
function fillReviewPlaceholders(placeholderMap /* placeholderKey and original value */, parentKey, values) {
|
||||
var _a;
|
||||
// retrieve the keys from the CommentInfo object
|
||||
for (const childKey of Object.keys(pullRequests_1.EMPTY_COMMENT_INFO)) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
placeholderMap.set(`\${{${parentKey}[${i}].${childKey}}}`, ((_a = values[i][childKey]) === null || _a === void 0 ? void 0 : _a.toLocaleString('en')) || '');
|
||||
}
|
||||
placeholderMap.set(`\${{${parentKey}[*].${childKey}}}`, values.map(value => { var _a; return ((_a = value[childKey]) === null || _a === void 0 ? void 0 : _a.toLocaleString('en')) || ''; }).join(', '));
|
||||
}
|
||||
}
|
||||
function replacePrPlaceholders(template, placeholderPrMap /* map with all pr related custom placeholder values */, configuration) {
|
||||
let transformed = template;
|
||||
for (const [key, values] of placeholderPrMap) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
transformed = transformed.replaceAll(`\${{${key}[${i}]}}`, values[i]);
|
||||
transformed = transformed.replaceAll(`\${{${key}[${i}]}}`, configuration.trim_values ? values[i].trim() : values[i]);
|
||||
}
|
||||
transformed = transformed.replaceAll(`\${{${key}[*]}}`, values.join(''));
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
function cleanupPrPlaceHolders(template, placeholders /* placeholders to apply */) {
|
||||
function cleanupPrPlaceholders(template, placeholders) {
|
||||
let transformed = template;
|
||||
for (const [, phs] of placeholders) {
|
||||
for (const ph of phs) {
|
||||
@@ -1857,7 +1994,7 @@ function extractValuesFromString(value, extractor) {
|
||||
if (extractor.method === 'match') {
|
||||
const lables = value.match(extractor.pattern);
|
||||
if (lables !== null && lables.length > 0) {
|
||||
return lables.map(label => label.toLocaleLowerCase('en'));
|
||||
return lables.map(label => (label === null || label === void 0 ? void 0 : label.toLocaleLowerCase('en')) || '');
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Generated
+730
-699
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -40,22 +40,22 @@
|
||||
"moment": "^2.29.4",
|
||||
"semver": "^7.3.8",
|
||||
"tunnel": "^0.0.6",
|
||||
"webpack": "^5.74.0"
|
||||
"webpack": "^5.75.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/node": "^18.11.0",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/parser": "^5.40.0",
|
||||
"@vercel/ncc": "^0.34.0",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-plugin-github": "^4.4.0",
|
||||
"eslint-plugin-jest": "^27.1.2",
|
||||
"jest": "^29.2.0",
|
||||
"jest-circus": "^29.2.0",
|
||||
"@types/jest": "^29.2.4",
|
||||
"@types/node": "^18.11.12",
|
||||
"@types/semver": "^7.3.13",
|
||||
"@typescript-eslint/parser": "^5.46.0",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-plugin-github": "^4.6.0",
|
||||
"eslint-plugin-jest": "^27.1.6",
|
||||
"jest": "^29.3.1",
|
||||
"jest-circus": "^29.3.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "2.7.1",
|
||||
"prettier": "2.8.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface Configuration {
|
||||
tag_resolver: TagResolver
|
||||
base_branches: string[]
|
||||
custom_placeholders?: Placeholder[]
|
||||
trim_values: boolean
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
@@ -40,14 +41,7 @@ export interface Transformer extends Regex {
|
||||
}
|
||||
|
||||
export interface Extractor extends Transformer {
|
||||
on_property?:
|
||||
| ('title' | 'author' | 'milestone' | 'body' | 'branch')[]
|
||||
| 'title'
|
||||
| 'author'
|
||||
| 'milestone'
|
||||
| 'body'
|
||||
| 'branch'
|
||||
| undefined // retrieve the property to extract the value from
|
||||
on_property?: ('title' | 'author' | 'milestone' | 'body' | 'branch')[] | 'title' | 'author' | 'milestone' | 'body' | 'branch' | undefined // retrieve the property to extract the value from
|
||||
method?: 'replace' | 'match' | undefined // the method to use to extract the value, `match` will not use the `target` property
|
||||
on_empty?: string | undefined // in case the regex results in an empty string, this value is gonna be used instead (only for label_extractor currently)
|
||||
}
|
||||
@@ -102,5 +96,6 @@ export const DefaultConfiguration: Configuration = {
|
||||
transformer: undefined // transforms the tag name using the regex, run after the filter
|
||||
},
|
||||
base_branches: [], // target branches for the merged PR ignoring PRs with different target branch, by default it will get all PRs
|
||||
custom_placeholders: []
|
||||
custom_placeholders: [],
|
||||
trim_values: false // defines if values are being trimmed prior to inserting
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ async function run(): Promise<void> {
|
||||
const failOnError = core.getInput('failOnError') === 'true'
|
||||
const fetchReviewers = core.getInput('fetchReviewers') === 'true'
|
||||
const fetchReleaseInformation = core.getInput('fetchReleaseInformation') === 'true'
|
||||
const fetchReviews = core.getInput('fetchReviews') === 'true'
|
||||
const commitMode = core.getInput('commitMode') === 'true'
|
||||
|
||||
const result = await new ReleaseNotesBuilder(
|
||||
@@ -55,6 +56,7 @@ async function run(): Promise<void> {
|
||||
ignorePreReleases,
|
||||
fetchReviewers,
|
||||
fetchReleaseInformation,
|
||||
fetchReviews,
|
||||
commitMode,
|
||||
configuration
|
||||
).build()
|
||||
|
||||
+54
-9
@@ -11,7 +11,7 @@ export interface PullRequestInfo {
|
||||
baseBranch: string
|
||||
branch?: string
|
||||
createdAt: moment.Moment
|
||||
mergedAt: moment.Moment | null
|
||||
mergedAt: moment.Moment | undefined
|
||||
mergeCommitSha: string
|
||||
author: string
|
||||
repoName: string
|
||||
@@ -21,15 +21,36 @@ export interface PullRequestInfo {
|
||||
assignees: string[]
|
||||
requestedReviewers: string[]
|
||||
approvedReviewers: string[]
|
||||
reviews?: CommentInfo[]
|
||||
status: 'open' | 'merged'
|
||||
}
|
||||
|
||||
export interface CommentInfo {
|
||||
id: number
|
||||
htmlURL: string
|
||||
submittedAt: moment.Moment | undefined
|
||||
author: string
|
||||
body: string
|
||||
state: string | undefined
|
||||
}
|
||||
|
||||
export const EMPTY_COMMENT_INFO: CommentInfo = {
|
||||
id: 0,
|
||||
htmlURL: '',
|
||||
submittedAt: undefined,
|
||||
author: '',
|
||||
body: '',
|
||||
state: undefined
|
||||
}
|
||||
|
||||
type PullData = RestEndpointMethodTypes['pulls']['get']['response']['data']
|
||||
|
||||
type PullsListData = RestEndpointMethodTypes['pulls']['list']['response']['data']
|
||||
|
||||
type PullReviewData = RestEndpointMethodTypes['pulls']['listReviews']['response']['data']
|
||||
|
||||
type PullReviewsData = RestEndpointMethodTypes['pulls']['listReviews']['response']['data']
|
||||
|
||||
export class PullRequests {
|
||||
constructor(private octokit: Octokit) {}
|
||||
|
||||
@@ -61,7 +82,7 @@ export class PullRequests {
|
||||
repo,
|
||||
state: 'closed',
|
||||
sort: 'merged',
|
||||
per_page: '100',
|
||||
per_page: `${Math.min(100, maxPullRequests)}`,
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
@@ -122,7 +143,7 @@ export class PullRequests {
|
||||
return sortPrs(openPrs)
|
||||
}
|
||||
|
||||
async getReviewers(owner: string, repo: string, pr: PullRequestInfo): Promise<PullReviewData[]> {
|
||||
async getReviewers(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
@@ -136,8 +157,25 @@ export class PullRequests {
|
||||
.map(r => r.user?.login)
|
||||
.filter(r => !!r) as string[]
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
async getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr.number,
|
||||
sort: 'created',
|
||||
direction: 'desc'
|
||||
})
|
||||
const prReviews: CommentInfo[] = []
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const comments: PullReviewsData = response.data as PullReviewsData
|
||||
|
||||
for (const comment of comments) {
|
||||
prReviews.push(mapComment(comment))
|
||||
}
|
||||
}
|
||||
pr.reviews = prReviews
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,17 +232,14 @@ function attachSpeciaLabels(status: 'open' | 'merged', labels: Set<string>): Set
|
||||
return labels
|
||||
}
|
||||
|
||||
const mapPullRequest = (
|
||||
pr: PullData | Unpacked<PullsListData>,
|
||||
status: 'open' | 'merged' = 'open'
|
||||
): PullRequestInfo => ({
|
||||
const mapPullRequest = (pr: PullData | Unpacked<PullsListData>, status: 'open' | 'merged' = 'open'): PullRequestInfo => ({
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
htmlURL: pr.html_url,
|
||||
baseBranch: pr.base.ref,
|
||||
branch: pr.head.ref,
|
||||
createdAt: moment(pr.created_at),
|
||||
mergedAt: pr.merged_at ? moment(pr.merged_at) : null,
|
||||
mergedAt: pr.merged_at ? moment(pr.merged_at) : undefined,
|
||||
mergeCommitSha: pr.merge_commit_sha || '',
|
||||
author: pr.user?.login || '',
|
||||
repoName: pr.base.repo.full_name,
|
||||
@@ -214,5 +249,15 @@ const mapPullRequest = (
|
||||
assignees: pr.assignees?.map(asignee => asignee?.login || '') || [],
|
||||
requestedReviewers: pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [],
|
||||
approvedReviewers: [],
|
||||
reviews: undefined,
|
||||
status
|
||||
})
|
||||
|
||||
const mapComment = (comment: Unpacked<PullReviewsData>): CommentInfo => ({
|
||||
id: comment.id,
|
||||
htmlURL: comment.html_url,
|
||||
submittedAt: comment.submitted_at ? moment(comment.submitted_at) : undefined,
|
||||
author: comment.user?.login || '',
|
||||
body: comment.body,
|
||||
state: comment.state
|
||||
})
|
||||
|
||||
+25
-14
@@ -16,6 +16,7 @@ export interface ReleaseNotesOptions {
|
||||
failOnError: boolean // defines if we should fail the action in case of an error
|
||||
fetchReviewers: boolean // defines if the action should fetch the reviewers for PRs - approved reviewers are not included in the default PR listing
|
||||
fetchReleaseInformation: boolean // defines if the action should fetch the release information for the from and to tag - e.g. the creation date for the associated release
|
||||
fetchReviews: boolean // defines if the action should fetch the reviews for the PR.
|
||||
commitMode: boolean // defines if we use the alternative commit based mode. note: this is only partially supported
|
||||
configuration: Configuration // the configuration as defined in `configuration.ts`
|
||||
}
|
||||
@@ -61,10 +62,7 @@ export class ReleaseNotes {
|
||||
|
||||
if (mergedPullRequests.length === 0) {
|
||||
core.warning(`⚠️ No pull requests found`)
|
||||
return replaceEmptyTemplate(
|
||||
this.options.configuration.empty_template || DefaultConfiguration.empty_template,
|
||||
this.options
|
||||
)
|
||||
return replaceEmptyTemplate(this.options.configuration.empty_template || DefaultConfiguration.empty_template, this.options)
|
||||
}
|
||||
|
||||
core.startGroup('📦 Build changelog')
|
||||
@@ -94,7 +92,7 @@ export class ReleaseNotes {
|
||||
}
|
||||
|
||||
private async getMergedPullRequests(octokit: Octokit): Promise<[DiffInfo, PullRequestInfo[]]> {
|
||||
const {owner, repo, includeOpen, fetchReviewers, configuration} = this.options
|
||||
const {owner, repo, includeOpen, fetchReviewers, fetchReviews, configuration} = this.options
|
||||
|
||||
const diffInfo = await this.getCommitHistory(octokit)
|
||||
const commits = diffInfo.commitInfo
|
||||
@@ -125,12 +123,9 @@ export class ReleaseNotes {
|
||||
configuration.max_pull_requests || DefaultConfiguration.max_pull_requests
|
||||
)
|
||||
|
||||
core.info(`ℹ️ Retrieved ${pullRequests.length} merged PRs for ${owner}/${repo}`)
|
||||
core.info(`ℹ️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} in date range from API`)
|
||||
|
||||
const prCommits = filterCommits(
|
||||
commits,
|
||||
configuration.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches
|
||||
)
|
||||
const prCommits = filterCommits(commits, configuration.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches)
|
||||
|
||||
core.info(`ℹ️ Retrieved ${prCommits.length} release commits for ${owner}/${repo}`)
|
||||
|
||||
@@ -144,6 +139,8 @@ export class ReleaseNotes {
|
||||
return releaseCommitHashes.includes(pr.mergeCommitSha)
|
||||
})
|
||||
|
||||
core.info(`ℹ️ Retrieved ${mergedPullRequests.length} merged PRs for ${owner}/${repo}`)
|
||||
|
||||
let allPullRequests = mergedPullRequests
|
||||
if (includeOpen) {
|
||||
// retrieve all open pull requests
|
||||
@@ -177,6 +174,10 @@ export class ReleaseNotes {
|
||||
return true
|
||||
})
|
||||
|
||||
if (baseBranches.length !== 0) {
|
||||
core.info(`ℹ️ Retrieved ${mergedPullRequests.length} PRs for ${owner}/${repo} filtered by the 'base_branches' configuration.`)
|
||||
}
|
||||
|
||||
if (fetchReviewers) {
|
||||
core.info(`ℹ️ Fetching reviewers was enabled`)
|
||||
// update PR information with reviewers who approved
|
||||
@@ -190,6 +191,19 @@ export class ReleaseNotes {
|
||||
core.debug(`ℹ️ Fetching reviewers was disabled`)
|
||||
}
|
||||
|
||||
if (fetchReviews) {
|
||||
core.info(`ℹ️ Fetching reviews was enabled`)
|
||||
// update PR information with reviewers who approved
|
||||
for (const pr of finalPrs) {
|
||||
await pullRequestsApi.getReviews(owner, repo, pr)
|
||||
if ((pr.reviews?.length || 0) > 0) {
|
||||
core.info(`ℹ️ Retrieved ${pr.reviews?.length || 0} review(s) for PR ${owner}/${repo}/#${pr.number}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core.debug(`ℹ️ Fetching reviews was disabled`)
|
||||
}
|
||||
|
||||
return [diffInfo, finalPrs]
|
||||
}
|
||||
|
||||
@@ -202,10 +216,7 @@ export class ReleaseNotes {
|
||||
return [diffInfo, []]
|
||||
}
|
||||
|
||||
const prCommits = filterCommits(
|
||||
commits,
|
||||
configuration.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches
|
||||
)
|
||||
const prCommits = filterCommits(commits, configuration.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches)
|
||||
|
||||
core.info(`ℹ️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export class ReleaseNotesBuilder {
|
||||
private ignorePreReleases: boolean,
|
||||
private fetchReviewers: boolean = false,
|
||||
private fetchReleaseInformation: boolean = false,
|
||||
private fetchReviews: boolean = false,
|
||||
private commitMode: boolean,
|
||||
private configuration: Configuration
|
||||
) {}
|
||||
@@ -117,6 +118,7 @@ export class ReleaseNotesBuilder {
|
||||
failOnError: this.failOnError,
|
||||
fetchReviewers: this.fetchReviewers,
|
||||
fetchReleaseInformation: this.fetchReleaseInformation,
|
||||
fetchReviews: this.fetchReviews,
|
||||
commitMode: this.commitMode,
|
||||
configuration: this.configuration
|
||||
}
|
||||
|
||||
+2
-6
@@ -52,9 +52,7 @@ export class Tags {
|
||||
}
|
||||
}
|
||||
|
||||
core.info(
|
||||
`ℹ️ Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`
|
||||
)
|
||||
core.info(`ℹ️ Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`)
|
||||
return tagsInfo
|
||||
}
|
||||
|
||||
@@ -73,9 +71,7 @@ export class Tags {
|
||||
tagInfo.date = moment(release.created_at)
|
||||
core.info(`ℹ️ Retrieved information about the release associated with ${tagInfo.name} from the GitHub API`)
|
||||
} catch (error) {
|
||||
core.info(
|
||||
`⚠️ No release information found for ${tagInfo.name}, trying to retrieve tag creation time as fallback.`
|
||||
)
|
||||
core.info(`⚠️ No release information found for ${tagInfo.name}, trying to retrieve tag creation time as fallback.`)
|
||||
const gitHelper = await createCommandManager(repositoryPath)
|
||||
const creationTimeString = await gitHelper.tagCreation(tagInfo.name)
|
||||
const creationTime = moment(creationTimeString)
|
||||
|
||||
+95
-41
@@ -1,6 +1,6 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Category, DefaultConfiguration, Extractor, Placeholder, Transformer} from './configuration'
|
||||
import {PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||
import {Category, Configuration, DefaultConfiguration, Extractor, Placeholder, Transformer} from './configuration'
|
||||
import {CommentInfo, EMPTY_COMMENT_INFO, PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||
import {ReleaseNotesOptions} from './releaseNotes'
|
||||
import {DiffInfo} from './commits'
|
||||
import {createOrSet, haveCommonElements, haveEveryElements} from './utils'
|
||||
@@ -13,6 +13,8 @@ export interface RegexTransformer {
|
||||
onEmpty?: string | undefined
|
||||
}
|
||||
|
||||
const EMPTY_MAP = new Map<string, string>()
|
||||
|
||||
export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], options: ReleaseNotesOptions): string {
|
||||
// sort to target order
|
||||
const config = options.configuration
|
||||
@@ -74,7 +76,7 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
|
||||
transformedMap.set(
|
||||
pr,
|
||||
transform(
|
||||
fillPrTemplate(pr, config.pr_template || DefaultConfiguration.pr_template, placeholders, placeholderPrMap),
|
||||
fillPrTemplate(pr, config.pr_template || DefaultConfiguration.pr_template, placeholders, placeholderPrMap, config),
|
||||
validatedTransformers
|
||||
)
|
||||
)
|
||||
@@ -126,9 +128,7 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
|
||||
const prNum = pr.number
|
||||
const prLabels = pr.labels
|
||||
const excludeLabels = JSON.stringify(category.exclude_labels)
|
||||
core.debug(
|
||||
`PR ${prNum} with labels: ${prLabels} excluded from category via exclude label: ${excludeLabels}`
|
||||
)
|
||||
core.debug(`PR ${prNum} with labels: ${prLabels} excluded from category via exclude label: ${excludeLabels}`)
|
||||
}
|
||||
continue // one of the exclude labels matched, skip the PR for this category
|
||||
}
|
||||
@@ -253,9 +253,9 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
|
||||
fillAdditionalPlaceholders(options, placeholderMap)
|
||||
|
||||
let transformedChangelog = config.template || DefaultConfiguration.template
|
||||
transformedChangelog = replacePlaceholders(transformedChangelog, placeholderMap, placeholders, placeholderPrMap)
|
||||
transformedChangelog = replacePrPlaceholders(transformedChangelog, placeholderPrMap)
|
||||
transformedChangelog = cleanupPrPlaceHolders(transformedChangelog, placeholders)
|
||||
transformedChangelog = replacePlaceholders(transformedChangelog, EMPTY_MAP, placeholderMap, placeholders, placeholderPrMap, config)
|
||||
transformedChangelog = replacePrPlaceholders(transformedChangelog, placeholderPrMap, config)
|
||||
transformedChangelog = cleanupPrPlaceholders(transformedChangelog, placeholders)
|
||||
core.info(`ℹ️ Filled template`)
|
||||
return transformedChangelog
|
||||
}
|
||||
@@ -267,7 +267,7 @@ export function replaceEmptyTemplate(template: string, options: ReleaseNotesOpti
|
||||
}
|
||||
const placeholderMap = new Map<string, string>()
|
||||
fillAdditionalPlaceholders(options, placeholderMap)
|
||||
return replacePlaceholders(template, placeholderMap, placeholders)
|
||||
return replacePlaceholders(template, new Map<string, string>(), placeholderMap, placeholders, undefined, options.configuration)
|
||||
}
|
||||
|
||||
function fillAdditionalPlaceholders(
|
||||
@@ -297,8 +297,11 @@ function fillPrTemplate(
|
||||
pr: PullRequestInfo,
|
||||
template: string,
|
||||
placeholders: Map<string, Placeholder[]> /* placeholders to apply */,
|
||||
placeholderPrMap: Map<string, string[]> /* map to keep replaced placeholder values with their key */
|
||||
placeholderPrMap: Map<string, string[]> /* map to keep replaced placeholder values with their key */,
|
||||
configuration: Configuration
|
||||
): string {
|
||||
const arrayPlaceholderMap = new Map<string, string>()
|
||||
fillReviewPlaceholders(arrayPlaceholderMap, 'REVIEWS', pr.reviews || [])
|
||||
const placeholderMap = new Map<string, string>()
|
||||
placeholderMap.set('NUMBER', pr.number.toString())
|
||||
placeholderMap.set('TITLE', pr.title)
|
||||
@@ -311,46 +314,71 @@ function fillPrTemplate(
|
||||
placeholderMap.set('LABELS', [...pr.labels]?.filter(l => !l.startsWith('--rcba-'))?.join(', ') || '')
|
||||
placeholderMap.set('MILESTONE', pr.milestone || '')
|
||||
placeholderMap.set('BODY', pr.body)
|
||||
fillArrayPlaceholders(arrayPlaceholderMap, 'ASSIGNEES', pr.assignees || [])
|
||||
placeholderMap.set('ASSIGNEES', pr.assignees?.join(', ') || '')
|
||||
fillArrayPlaceholders(arrayPlaceholderMap, 'REVIEWERS', pr.requestedReviewers || [])
|
||||
placeholderMap.set('REVIEWERS', pr.requestedReviewers?.join(', ') || '')
|
||||
fillArrayPlaceholders(arrayPlaceholderMap, 'APPROVERS', pr.approvedReviewers || [])
|
||||
placeholderMap.set('APPROVERS', pr.approvedReviewers?.join(', ') || '')
|
||||
placeholderMap.set('BRANCH', pr.branch || '')
|
||||
placeholderMap.set('BASE_BRANCH', pr.baseBranch)
|
||||
return replacePlaceholders(template, placeholderMap, placeholders, placeholderPrMap)
|
||||
return replacePlaceholders(template, arrayPlaceholderMap, placeholderMap, placeholders, placeholderPrMap, configuration)
|
||||
}
|
||||
|
||||
function replacePlaceholders(
|
||||
template: string,
|
||||
arrayPlaceholderMap: Map<string, string> /* arrayPlaceholderKey and original value */,
|
||||
placeholderMap: Map<string, string> /* placeholderKey and original value */,
|
||||
placeholders: Map<string, Placeholder[]> /* placeholders to apply */,
|
||||
placeholderPrMap?: Map<string, string[]> /* map to keep replaced placeholder values with their key */
|
||||
placeholderPrMap: Map<string, string[]> | undefined /* map to keep replaced placeholder values with their key */,
|
||||
configuration: Configuration
|
||||
): string {
|
||||
let transformed = template
|
||||
|
||||
// replace array placeholders first
|
||||
for (const [key, value] of arrayPlaceholderMap) {
|
||||
transformed = handlePlaceholder(transformed, key, value, placeholders, placeholderPrMap, configuration)
|
||||
}
|
||||
|
||||
// replace traditional placeholders
|
||||
for (const [key, value] of placeholderMap) {
|
||||
transformed = transformed.replaceAll(`\${{${key}}}`, value)
|
||||
transformed = handlePlaceholder(transformed, key, value, placeholders, placeholderPrMap, configuration)
|
||||
}
|
||||
|
||||
// replace custom placeholders
|
||||
const phs = placeholders.get(key)
|
||||
if (phs) {
|
||||
for (const placeholder of phs) {
|
||||
const transformer = validateTransformer(placeholder.transformer)
|
||||
if (transformer?.pattern) {
|
||||
const extractedValue = value.replace(transformer.pattern, transformer.target)
|
||||
// note: `.replace` will return the full string again if there was no match
|
||||
if (extractedValue && extractedValue !== value) {
|
||||
if (placeholderPrMap) {
|
||||
createOrSet(placeholderPrMap, placeholder.name, extractedValue)
|
||||
}
|
||||
transformed = transformed.replaceAll(`\${{${placeholder.name}}}`, extractedValue)
|
||||
return transformed
|
||||
}
|
||||
|
||||
if (core.isDebug()) {
|
||||
core.debug(` Custom Placeholder successfully matched data - ${extractValues} (${placeholder.name})`)
|
||||
}
|
||||
} else if (core.isDebug() && extractedValue === value) {
|
||||
core.debug(
|
||||
` Custom Placeholder did result in the full original value returned. Skipping. (${placeholder.name})`
|
||||
)
|
||||
function handlePlaceholder(
|
||||
template: string,
|
||||
key: string,
|
||||
value: string,
|
||||
placeholders: Map<string, Placeholder[]> /* placeholders to apply */,
|
||||
placeholderPrMap: Map<string, string[]> | undefined /* map to keep replaced placeholder values with their key */,
|
||||
configuration: Configuration
|
||||
): string {
|
||||
let transformed = template.replaceAll(`\${{${key}}}`, configuration.trim_values ? value.trim() : value)
|
||||
// replace custom placeholders
|
||||
const phs = placeholders.get(key)
|
||||
if (phs) {
|
||||
for (const placeholder of phs) {
|
||||
const transformer = validateTransformer(placeholder.transformer)
|
||||
if (transformer?.pattern) {
|
||||
const extractedValue = value.replace(transformer.pattern, transformer.target)
|
||||
// note: `.replace` will return the full string again if there was no match
|
||||
if (extractedValue && (extractedValue !== value || (extractedValue === value && value.match(transformer.pattern)))) {
|
||||
if (placeholderPrMap) {
|
||||
createOrSet(placeholderPrMap, placeholder.name, extractedValue)
|
||||
}
|
||||
transformed = transformed.replaceAll(
|
||||
`\${{${placeholder.name}}}`,
|
||||
configuration.trim_values ? extractedValue.trim() : extractedValue
|
||||
)
|
||||
|
||||
if (core.isDebug()) {
|
||||
core.debug(` Custom Placeholder successfully matched data - ${extractValues} (${placeholder.name})`)
|
||||
}
|
||||
} else if (core.isDebug() && extractedValue === value) {
|
||||
core.debug(` Custom Placeholder did result in the full original value returned. Skipping. (${placeholder.name})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,24 +386,50 @@ function replacePlaceholders(
|
||||
return transformed
|
||||
}
|
||||
|
||||
function fillArrayPlaceholders(
|
||||
placeholderMap: Map<string, string> /* placeholderKey and original value */,
|
||||
key: string,
|
||||
values: string[]
|
||||
): void {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
placeholderMap.set(`\${{${key}[${i}]}}`, values[i])
|
||||
}
|
||||
placeholderMap.set(`\${{${key}[*]}}`, values.join(', '))
|
||||
}
|
||||
|
||||
function fillReviewPlaceholders(
|
||||
placeholderMap: Map<string, string> /* placeholderKey and original value */,
|
||||
parentKey: string,
|
||||
values: CommentInfo[]
|
||||
): void {
|
||||
// retrieve the keys from the CommentInfo object
|
||||
for (const childKey of Object.keys(EMPTY_COMMENT_INFO)) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
placeholderMap.set(`\${{${parentKey}[${i}].${childKey}}}`, values[i][childKey as keyof CommentInfo]?.toLocaleString('en') || '')
|
||||
}
|
||||
placeholderMap.set(
|
||||
`\${{${parentKey}[*].${childKey}}}`,
|
||||
values.map(value => value[childKey as keyof CommentInfo]?.toLocaleString('en') || '').join(', ')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function replacePrPlaceholders(
|
||||
template: string,
|
||||
placeholderPrMap: Map<string, string[]> /* map with all pr related custom placeholder values */
|
||||
placeholderPrMap: Map<string, string[]> /* map with all pr related custom placeholder values */,
|
||||
configuration: Configuration
|
||||
): string {
|
||||
let transformed = template
|
||||
for (const [key, values] of placeholderPrMap) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
transformed = transformed.replaceAll(`\${{${key}[${i}]}}`, values[i])
|
||||
transformed = transformed.replaceAll(`\${{${key}[${i}]}}`, configuration.trim_values ? values[i].trim() : values[i])
|
||||
}
|
||||
transformed = transformed.replaceAll(`\${{${key}[*]}}`, values.join(''))
|
||||
}
|
||||
return transformed
|
||||
}
|
||||
|
||||
function cleanupPrPlaceHolders(
|
||||
template: string,
|
||||
placeholders: Map<string, Placeholder[]> /* placeholders to apply */
|
||||
): string {
|
||||
function cleanupPrPlaceholders(template: string, placeholders: Map<string, Placeholder[]>): string {
|
||||
let transformed = template
|
||||
for (const [, phs] of placeholders) {
|
||||
for (const ph of phs) {
|
||||
@@ -480,7 +534,7 @@ function extractValuesFromString(value: string, extractor: RegexTransformer): st
|
||||
if (extractor.method === 'match') {
|
||||
const lables = value.match(extractor.pattern)
|
||||
if (lables !== null && lables.length > 0) {
|
||||
return lables.map(label => label.toLocaleLowerCase('en'))
|
||||
return lables.map(label => label?.toLocaleLowerCase('en') || '')
|
||||
}
|
||||
} else {
|
||||
const label = value.replace(extractor.pattern, extractor.target)
|
||||
|
||||
Reference in New Issue
Block a user