Compare commits

...

47 Commits

Author SHA1 Message Date
9a2319ecaf release prep 2019-10-20 18:15:51 -04:00
7363c39621 Merge pull request #26 from softprops/retry-api
retry api requests
2019-10-20 18:11:01 -04:00
742de9953a rebuild 2019-10-20 18:09:27 -04:00
e234343e3e rebuild and fmt 2019-10-20 18:04:32 -04:00
c99850e5d1 back to minimal set 2019-10-20 18:03:30 -04:00
845942e04a setup integration test 2019-10-20 17:50:51 -04:00
1a522d88d8 Add the release HTML URL to outputs
This will allow subsequent actions to get access to the HTML URL for the
release created with this. Handy for composing multiple actions together
that are related to the release.

In my case I wanted to get the  URL into a slack message posted to the
team when a release is published.

The output can be referenced by using the `steps.release.ouput.url` in
the workflow yaml:

    - name: Release
      id: release
      uses: softprops/action-gh-release@v1
      with:
        name: "My Release"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: Slack
      uses: csexton/slack-message-action@v1
      with:
        message: New release posted at ${{ steps.release.outputs.url}}
2019-10-20 17:50:51 -04:00
56b4c8a8ef Merge pull request #28 from csexton/output-url
Add the release HTML URL to outputs
2019-10-18 10:15:52 -04:00
723bed634e Add the release HTML URL to outputs
This will allow subsequent actions to get access to the HTML URL for the
release created with this. Handy for composing multiple actions together
that are related to the release.

In my case I wanted to get the  URL into a slack message posted to the
team when a release is published.

The output can be referenced by using the `steps.release.ouput.url` in
the workflow yaml:

    - name: Release
      id: release
      uses: softprops/action-gh-release@v1
      with:
        name: "My Release"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: Slack
      uses: csexton/slack-message-action@v1
      with:
        message: New release posted at ${{ steps.release.outputs.url}}
2019-10-18 10:09:12 -04:00
2e7c800734 retry requests 2019-10-02 20:51:12 -04:00
6247f9299a update readme 2019-09-29 08:51:03 -04:00
5d578441a6 update changelog 2019-09-29 08:46:18 -04:00
42a316b84f sync body_path fmt 2019-09-29 08:45:47 -04:00
6ecc92f5ad bump version and update changelog 2019-09-29 08:25:51 -04:00
e7b71cc1a7 Merge pull request #23 from softprops/body_path_not_used
honor body_path input when provided.
2019-09-29 08:23:25 -04:00
f26a08096a commit latest build artifacts 2019-09-29 08:16:42 -04:00
ba42ad9139 honor body_path input when provided. fixes #22 2019-09-29 02:15:58 -04:00
07c8c20669 upgrade deps 2019-09-29 02:04:47 -04:00
00c56dd770 funding 2019-09-18 23:14:09 +09:00
50c843ac1c set a better example. dont refer to @master 2019-09-18 13:38:56 +09:00
ef036888ec update changelog 2019-09-18 13:26:04 +09:00
2758344bdf bump version 2019-09-18 13:25:04 +09:00
18d8be76a0 Merge pull request #19 from softprops/pre-releases
add support for prereleases
2019-09-17 23:35:48 +09:00
2984051a42 update readme 2019-09-17 23:33:44 +09:00
a95bad53b2 add support for prereleases. fixes #17 2019-09-17 23:30:36 +09:00
bd839f3b8f Merge pull request #18 from softprops/newline-delimited-assets
add support for multi-line delimited assets
2019-09-17 23:23:56 +09:00
e812c4aca6 tabs to spaces 2019-09-17 23:22:34 +09:00
615fb448a2 changelog 2019-09-17 23:21:28 +09:00
77f9a4f575 update inputs.files.description 2019-09-17 23:16:32 +09:00
3af8783d41 support multi-line delimited assets. fixes #15 2019-09-17 23:14:30 +09:00
a6281eb824 Merge pull request #16 from softprops/draft-merger
Draft merger
2019-09-17 19:39:58 +09:00
fbe4f1f3ad update changelog 2019-09-17 19:38:31 +09:00
916d072e3d declare interface impl 2019-09-17 19:33:38 +09:00
ca93e708e3 typo in type name 2019-09-17 19:27:32 +09:00
85bb079141 ignore git modules again 2019-09-17 19:25:00 +09:00
46f6b8d27b rm node modules 2019-09-17 19:24:44 +09:00
fcf37b79bd iterating over responses not releases 2019-09-17 18:04:39 +09:00
a31211f9ff debug what comes back from our iterator 2019-09-17 17:53:48 +09:00
f3c225f2b8 regenerate js, log on tag miss for debugging 2019-09-17 17:12:53 +09:00
5f2e9fb50f revert me: integration testing 2019-09-17 13:10:47 +09:00
9f9f2a3ed2 merge drafts workaround. fixes #14 2019-09-16 23:51:16 +09:00
e2bd814f0a de-empahsize the past 2019-09-14 23:46:26 +09:00
6bdc1677a5 add demo screenshot 2019-09-11 19:23:42 +09:00
2a55ae4b12 add screenshot 2019-09-11 19:23:05 +09:00
d2403f49f5 empathy for those new to typescript projects 2019-09-09 22:00:45 +09:00
1b3edca894 call out cross platform support 2019-09-09 22:00:45 +09:00
23b784b656 Merge pull request #8 from softprops/cross-platform
refactor for cross platform use
2019-09-09 21:39:45 +09:00
16 changed files with 486 additions and 55 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
ko_fi: softprops

View File

@ -1,9 +1,48 @@
## 0.1.0
## 0.1.4
* Initial release
* Steps can now access the url of releases with the `url` output of this Action [#28](https://github.com/softprops/action-gh-release/pull/28)
* Added basic GitHub API retry support to manage API turbulance [#26](https://github.com/softprops/action-gh-release/pull/26)
## 0.1.3
* Fixed where `with: body_path` was not being used in generated GitHub releases
## 0.1.2
* Add support for merging draft releases [#16](https://github.com/softprops/action-gh-release/pull/16)
GitHub's api doesn't explicitly have a way of fetching a draft release by tag name which caused draft releases to appear as separate releases when used in a build matrix.
This is now fixed.
* Add support for newline-delimited asset list [#18](https://github.com/softprops/action-gh-release/pull/18)
GitHub actions inputs don't inherently support lists of things and one might like to append a list of files to include in a release. Previously this was possible using a comma-delimited list of asset path patterns to upload. You can now provide these as a newline delimieted list for better readability
```yaml
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
filea.txt
fileb.txt
filec.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
* Add support for prerelease annotated GitHub releases with the new input field `with.prerelease: true` [#19](https://github.com/softprops/action-gh-release/pull/19)
---
## 0.1.1
* Add support for publishing releases on all supported virtual hosts
You'll need to remove `docker://` prefix and use the `@v1` action tag
You'll need to remove `docker://` prefix and use the `@v1` action tag
---
## 0.1.0
* Initial release

18
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,18 @@
## bootstrapping
This a [JavaScript](https://help.github.com/en/articles/about-actions#types-of-actions) action but uses [TypeScript](https://www.typescriptlang.org/docs/home.html) to generate that JavaScript.
You can bootstrap your envrinment with a modern version of npm and by running `npm i` at the root of this repo.
## testing
Tests can be found under under `__tests__` directory and are runnable with the `npm t` command
## source code
Source code can be found under the `src` directory. Running `npm run build` will generate the JavaScript that will run within GitHub workflows.
## formatting
A minimal attempt at keeping a consistent code style is can be applied by running `npm run fmt`

View File

@ -1,12 +1,32 @@
# action gh-release [![](https://github.com/softprops/action-gh-release/workflows/Main/badge.svg)](https://github.com/softprops/action-gh-release/actions)
<div align="center">
📦 :octocat:
</div>
<h1 align="center">
action gh-release
</h1>
<p align="center">
A GitHub Action for creating GitHub Releases on Linux, Windows, and OSX virtual environments
</p>
<div align="center">
<img src="demo.png"/>
</div>
<div align="center">
<a href="https://github.com/softprops/action-gh-release/actions">
<img src="https://github.com/softprops/action-gh-release/workflows/Main/badge.svg"/>
</a>
</div>
<br />
> A GitHub Action for creating GitHub Releases
> **⚠️ Note:** To use this action, you must have access to the [GitHub Actions](https://github.com/features/actions) feature. GitHub Actions are currently only available in public beta. You can [apply for the GitHub Actions beta here](https://github.com/features/actions/signup/).
> **⚠️ Note:** This action was previously implemented as a docker container, limiting its use to GitHub Actions Linux virtual environments only. With recent releases, we now support cross platform usage. You'll need to remove the `docker://` prefix in these versions
## 🤸 Usage
### 🚥 Limit releases to pushes to tags
@ -27,7 +47,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
uses: actions/checkout@v1
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
@ -50,21 +70,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
uses: actions/checkout@v1
- name: Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
### ⬆️ Uploading release assets
You can can configure a number of options for your
GitHub release and all are optional.
A common case for GitHub releases is to upload your binary after its been validated and packaged.
Use the `with.files` input to declare a comma-separated list of glob expressions matching the files
Use the `with.files` input to declare a newline-delimited list of glob expressions matching the files
you wish to upload to GitHub releases. If you'd like you can just list the files by name directly.
Below is an example of uploading a single asset named `Release.txt`
@ -79,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
uses: actions/checkout@v1
- name: Build
run: echo ${{ github.sha }} > Release.txt
- name: Test
@ -93,6 +112,36 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
Below is an example of uploading more than one asset with a GitHub release
```yaml
name: Main
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Build
run: echo ${{ github.sha }} > Release.txt
- name: Test
run: cat Release.txt
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
Release.txt
LICENSE
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
> **⚠️ Note:** Notice the `|` in the yaml syntax above ☝️. That let's you effectively declare a multi-line yaml string. You can learn more about multi-line yaml syntax [here](https://yaml-multiline.info)
### 📝 External release notes
Many systems exist that can help generate release notes for you. This action supports
@ -109,7 +158,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
uses: actions/checkout@v1
- name: Generate Changelog
run: echo "# Good things have arrived" > ${{ github.workflow }}-CHANGELOG.txt
- name: Release
@ -132,11 +181,23 @@ The following are optional as `step.with` keys
| `body` | String | Text communicating notable changes in this release |
| `body_path` | String | Path to load text communicating notable changes in this release |
| `draft` | Boolean | Indicator of whether or not this release is a draft |
| `files` | String | Comma-delimited globs of paths to assets to upload for release |
| `prerelease`| Boolean | Indicator of whether or not is a prerelease |
| `files` | String | Newline-delimited globs of paths to assets to upload for release|
| `name` | String | Name of the release. defaults to tag name |
💡When providing a `body` and `body_path` at the same time, `body_path` will be attempted first, then falling back on `body` if the path can not be read from.
#### outputs
The following outputs can be accessed via `${{ steps.<step-id>.outputs }}` from this action
| Name | Type | Description |
|-------------|---------|-----------------------------------------------------------------|
| `url` | String | Github.com URL for the release |
#### environment variables
The following are *required* as `step.env` keys
@ -145,4 +206,7 @@ The following are *required* as `step.env` keys
|----------------|--------------------------------------|
| `GITHUB_TOKEN` | GITHUB_TOKEN as provided by `secrets`|
> **⚠️ Note:** This action was previously implemented as a docker container, limiting its use to GitHub Actions Linux virtual environments only. With recent releases, we now support cross platform usage. You'll need to remove the `docker://` prefix in these versions
Doug Tangren (softprops) 2019

1
__tests__/release.txt Normal file
View File

@ -0,0 +1 @@
bar

View File

@ -1,7 +1,92 @@
import { isTag, paths } from "../src/util";
import {
releaseBody,
isTag,
paths,
parseConfig,
parseInputFiles
} from "../src/util";
import * as assert from "assert";
describe("util", () => {
describe("parseInputFiles", () => {
it("parses empty strings", () => {
assert.deepStrictEqual(parseInputFiles(""), []);
});
it("parses comma-delimited strings", () => {
assert.deepStrictEqual(parseInputFiles("foo,bar"), ["foo", "bar"]);
});
it("parses newline and comma-delimited (and then some)", () => {
assert.deepStrictEqual(
parseInputFiles("foo,bar\nbaz,boom,\n\ndoom,loom "),
["foo", "bar", "baz", "boom", "doom", "loom"]
);
});
});
describe("releaseBody", () => {
it("uses input body", () => {
assert.equal(
"foo",
releaseBody({
github_ref: "",
github_repository: "",
github_token: "",
input_body: "foo",
input_body_path: undefined,
input_draft: false,
input_prerelease: false,
input_files: [],
input_name: undefined
})
);
});
it("uses input body path", () => {
assert.equal(
"bar",
releaseBody({
github_ref: "",
github_repository: "",
github_token: "",
input_body: undefined,
input_body_path: "__tests__/release.txt",
input_draft: false,
input_prerelease: false,
input_files: [],
input_name: undefined
})
);
});
it("defaults to body when both body and body path are provided", () => {
assert.equal(
"foo",
releaseBody({
github_ref: "",
github_repository: "",
github_token: "",
input_body: "foo",
input_body_path: "__tests__/release.txt",
input_draft: false,
input_prerelease: false,
input_files: [],
input_name: undefined
})
);
});
});
describe("parseConfig", () => {
it("parses basic config", () => {
assert.deepStrictEqual(parseConfig({}), {
github_ref: "",
github_repository: "",
github_token: "",
input_body: undefined,
input_body_path: undefined,
input_draft: false,
input_prerelease: false,
input_files: [],
input_name: undefined
});
});
});
describe("isTag", () => {
it("returns true for tags", async () => {
assert.equal(isTag("refs/tags/foo"), true);

View File

@ -6,20 +6,26 @@ inputs:
body:
description: 'Note-worthy description of changes in release'
required: false
body-path:
body_path:
description: 'Path to load note-worthy description of changes in release from'
required: false
name:
description: 'Gives the release a custom name. Defaults to tag name'
required: false
draft:
description: 'Creates a draft release'
description: 'Creates a draft release. Defaults to false'
required: false
prerelease:
description: 'Identify the release as a prerelease. Defaults to false'
required: false
files:
description: 'Comma-delimited list of path globs for asset files to upload'
description: 'Newline-delimited list of path globs for asset files to upload'
required: false
env:
'GITHUB_TOKEN': 'As provided by Github Actions'
outputs:
url:
description: 'URL to the Release HTML Page'
runs:
using: 'node12'
main: 'lib/main.js'

BIN
demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -8,10 +8,34 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const fs_1 = require("fs");
const mime_1 = require("mime");
const path_1 = require("path");
class GitHubReleaser {
constructor(github) {
this.github = github;
}
getReleaseByTag(params) {
return this.github.repos.getReleaseByTag(params);
}
createRelease(params) {
return this.github.repos.createRelease(params);
}
allReleases(params) {
const updatedParams = Object.assign({ per_page: 100 }, params);
return this.github.paginate.iterator(this.github.repos.listReleases.endpoint.merge(updatedParams));
}
}
exports.GitHubReleaser = GitHubReleaser;
exports.asset = (path) => {
return {
name: path_1.basename(path),
@ -36,11 +60,35 @@ exports.upload = (gh, url, path) => __awaiter(void 0, void 0, void 0, function*
file
});
});
exports.release = (config, gh) => __awaiter(void 0, void 0, void 0, function* () {
exports.release = (config, releaser) => __awaiter(void 0, void 0, void 0, function* () {
var e_1, _a;
const [owner, repo] = config.github_repository.split("/");
const tag = config.github_ref.replace("refs/tags/", "");
try {
let release = yield gh.repos.getReleaseByTag({
// you can't get a an existing draft by tag
// so we must find one in the list of all releases
if (config.input_draft) {
try {
for (var _b = __asyncValues(releaser.allReleases({
owner,
repo
})), _c; _c = yield _b.next(), !_c.done;) {
const response = _c.value;
let release = response.data.find(release => release.tag_name === tag);
if (release) {
return release;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
}
let release = yield releaser.getReleaseByTag({
owner,
repo,
tag
@ -52,23 +100,25 @@ exports.release = (config, gh) => __awaiter(void 0, void 0, void 0, function* ()
try {
const tag_name = tag;
const name = config.input_name || tag;
const body = config.input_body;
const body = util_1.releaseBody(config);
const draft = config.input_draft;
const prerelease = config.input_prerelease;
console.log(`👩‍🏭 Creating new GitHub release for tag ${tag_name}...`);
let release = yield gh.repos.createRelease({
let release = yield releaser.createRelease({
owner,
repo,
tag_name,
name,
body,
draft
draft,
prerelease
});
return release.data;
}
catch (error) {
// presume a race with competing metrix runs
console.log(`⚠️ GitHub release failed with status: ${error.status}, retrying...`);
return exports.release(config, gh);
return exports.release(config, releaser);
}
}
else {

View File

@ -21,14 +21,29 @@ function run() {
if (!util_1.isTag(config.github_ref)) {
throw new Error(`⚠️ GitHub Releases requires a tag`);
}
const gh = new github_2.GitHub(config.github_token);
let rel = yield github_1.release(config, gh);
github_2.GitHub.plugin(require("@octokit/plugin-throttling"));
const gh = new github_2.GitHub(config.github_token, {
onRateLimit: (retryAfter, options) => {
console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
if (options.request.retryCount === 0) {
// only retries once
console.log(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
onAbuseLimit: (retryAfter, options) => {
// does not retry, only logs a warning
console.warn(`Abuse detected for request ${options.method} ${options.url}`);
}
});
let rel = yield github_1.release(config, new github_1.GitHubReleaser(gh));
if (config.input_files) {
util_1.paths(config.input_files).forEach((path) => __awaiter(this, void 0, void 0, function* () {
yield github_1.upload(gh, rel.upload_url, path);
}));
}
console.log(`🎉 Release ready at ${rel.html_url}`);
core_1.setOutput("url", rel.html_url);
}
catch (error) {
core_1.setFailed(error.message);

View File

@ -9,6 +9,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
const glob = __importStar(require("glob"));
const fs_1 = require("fs");
exports.releaseBody = (config) => {
return (config.input_body ||
(config.input_body_path &&
fs_1.readFileSync(config.input_body_path).toString("utf8")));
};
exports.parseInputFiles = (files) => {
return files.split(/\r?\n/).reduce((acc, line) => acc
.concat(line.split(","))
.filter(pat => pat)
.map(pat => pat.trim()), []);
};
exports.parseConfig = (env) => {
return {
github_token: env.GITHUB_TOKEN || "",
@ -17,8 +28,9 @@ exports.parseConfig = (env) => {
input_name: env.INPUT_NAME,
input_body: env.INPUT_BODY,
input_body_path: env.INPUT_BODY_PATH,
input_files: (env.INPUT_FILES || "").split(","),
input_draft: env.INPUT_DRAFT === "true"
input_files: exports.parseInputFiles(env.INPUT_FILES || ""),
input_draft: env.INPUT_DRAFT === "true",
input_prerelease: env.INPUT_PRERELEASE == "true"
};
};
exports.paths = (patterns) => {

34
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "action-gh-release",
"version": "0.1.1",
"version": "0.1.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -440,6 +440,14 @@
"universal-user-agent": "^2.0.3"
}
},
"@octokit/plugin-throttling": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-2.6.0.tgz",
"integrity": "sha512-E0xQrcD36sVEeBhut6j9nWX38vm/1LKMRSUqjvJ/mqGLXfHr4jYMsrR3I/nT2QC0eJL1/SKMt7zxOt7pZiFhDA==",
"requires": {
"bottleneck": "^2.15.3"
}
},
"@octokit/request": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.1.0.tgz",
@ -943,6 +951,11 @@
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz",
"integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A=="
},
"bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -3437,6 +3450,12 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
@ -4826,15 +4845,16 @@
"dev": true
},
"ts-jest": {
"version": "24.0.2",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.0.2.tgz",
"integrity": "sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==",
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.1.0.tgz",
"integrity": "sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ==",
"dev": true,
"requires": {
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"json5": "2.x",
"lodash.memoize": "4.x",
"make-error": "1.x",
"mkdirp": "0.x",
"resolve": "1.x",
@ -4884,9 +4904,9 @@
}
},
"typescript": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz",
"integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==",
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
"dev": true
},
"typescript-formatter": {

View File

@ -1,6 +1,6 @@
{
"name": "action-gh-release",
"version": "0.1.1",
"version": "0.1.4",
"private": true,
"description": "GitHub Action for creating GitHub Releases",
"main": "lib/main.js",
@ -21,19 +21,20 @@
"dependencies": {
"@actions/core": "^1.1.0",
"@actions/github": "^1.1.0",
"@octokit/plugin-throttling": "^2.6.0",
"glob": "^7.1.4",
"mime": "^2.4.4"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/jest": "^24.0.13",
"@types/jest": "^24.0.18",
"@types/mime": "^2.0.1",
"@types/node": "^12.7.4",
"jest": "^24.8.0",
"jest-circus": "^24.7.1",
"jest": "^24.9.0",
"jest-circus": "^24.9.0",
"prettier": "1.18.2",
"ts-jest": "^24.0.2",
"typescript": "^3.5.1",
"ts-jest": "^24.1.0",
"typescript": "^3.6.3",
"typescript-formatter": "^7.2.2"
}
}

View File

@ -1,5 +1,5 @@
import { GitHub } from "@actions/github";
import { Config } from "./util";
import { Config, releaseBody } from "./util";
import { lstatSync, readFileSync } from "fs";
import { getType } from "mime";
import { basename } from "path";
@ -14,6 +14,67 @@ export interface ReleaseAsset {
export interface Release {
upload_url: string;
html_url: string;
tag_name: string;
}
export interface Releaser {
getReleaseByTag(params: {
owner: string;
repo: string;
tag: string;
}): Promise<{ data: Release }>;
createRelease(params: {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
}): Promise<{ data: Release }>;
allReleases(params: {
owner: string;
repo: string;
}): AsyncIterableIterator<{ data: Release[] }>;
}
export class GitHubReleaser implements Releaser {
github: GitHub;
constructor(github: GitHub) {
this.github = github;
}
getReleaseByTag(params: {
owner: string;
repo: string;
tag: string;
}): Promise<{ data: Release }> {
return this.github.repos.getReleaseByTag(params);
}
createRelease(params: {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
}): Promise<{ data: Release }> {
return this.github.repos.createRelease(params);
}
allReleases(params: {
owner: string;
repo: string;
}): AsyncIterableIterator<{ data: Release[] }> {
const updatedParams = { per_page: 100, ...params };
return this.github.paginate.iterator(
this.github.repos.listReleases.endpoint.merge(updatedParams)
);
}
}
export const asset = (path: string): ReleaseAsset => {
@ -47,11 +108,27 @@ export const upload = async (
});
};
export const release = async (config: Config, gh: GitHub): Promise<Release> => {
export const release = async (
config: Config,
releaser: Releaser
): Promise<Release> => {
const [owner, repo] = config.github_repository.split("/");
const tag = config.github_ref.replace("refs/tags/", "");
try {
let release = await gh.repos.getReleaseByTag({
// you can't get a an existing draft by tag
// so we must find one in the list of all releases
if (config.input_draft) {
for await (const response of releaser.allReleases({
owner,
repo
})) {
let release = response.data.find(release => release.tag_name === tag);
if (release) {
return release;
}
}
}
let release = await releaser.getReleaseByTag({
owner,
repo,
tag
@ -62,16 +139,18 @@ export const release = async (config: Config, gh: GitHub): Promise<Release> => {
try {
const tag_name = tag;
const name = config.input_name || tag;
const body = config.input_body;
const body = releaseBody(config);
const draft = config.input_draft;
const prerelease = config.input_prerelease;
console.log(`👩‍🏭 Creating new GitHub release for tag ${tag_name}...`);
let release = await gh.repos.createRelease({
let release = await releaser.createRelease({
owner,
repo,
tag_name,
name,
body,
draft
draft,
prerelease
});
return release.data;
} catch (error) {
@ -79,7 +158,7 @@ export const release = async (config: Config, gh: GitHub): Promise<Release> => {
console.log(
`⚠️ GitHub release failed with status: ${error.status}, retrying...`
);
return release(config, gh);
return release(config, releaser);
}
} else {
console.log(

View File

@ -1,6 +1,6 @@
import { paths, parseConfig, isTag } from "./util";
import { release, upload } from "./github";
import { setFailed } from "@actions/core";
import { release, upload, GitHubReleaser } from "./github";
import { setFailed, setOutput } from "@actions/core";
import { GitHub } from "@actions/github";
import { env } from "process";
@ -10,14 +10,33 @@ async function run() {
if (!isTag(config.github_ref)) {
throw new Error(`⚠️ GitHub Releases requires a tag`);
}
const gh = new GitHub(config.github_token);
let rel = await release(config, gh);
GitHub.plugin(require("@octokit/plugin-throttling"));
const gh = new GitHub(config.github_token, {
onRateLimit: (retryAfter, options) => {
console.warn(
`Request quota exhausted for request ${options.method} ${options.url}`
);
if (options.request.retryCount === 0) {
// only retries once
console.log(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
onAbuseLimit: (retryAfter, options) => {
// does not retry, only logs a warning
console.warn(
`Abuse detected for request ${options.method} ${options.url}`
);
}
});
let rel = await release(config, new GitHubReleaser(gh));
if (config.input_files) {
paths(config.input_files).forEach(async path => {
await upload(gh, rel.upload_url, path);
});
}
console.log(`🎉 Release ready at ${rel.html_url}`);
setOutput("url", rel.html_url);
} catch (error) {
setFailed(error.message);
}

View File

@ -1,5 +1,5 @@
import * as glob from "glob";
import { lstatSync } from "fs";
import { lstatSync, readFileSync } from "fs";
export interface Config {
github_token: string;
@ -11,10 +11,30 @@ export interface Config {
input_body_path?: string;
input_files?: string[];
input_draft?: boolean;
input_prerelease?: boolean;
}
export const releaseBody = (config: Config): string | undefined => {
return (
config.input_body ||
(config.input_body_path &&
readFileSync(config.input_body_path).toString("utf8"))
);
};
type Env = { [key: string]: string | undefined };
export const parseInputFiles = (files: string): string[] => {
return files.split(/\r?\n/).reduce<string[]>(
(acc, line) =>
acc
.concat(line.split(","))
.filter(pat => pat)
.map(pat => pat.trim()),
[]
);
};
export const parseConfig = (env: Env): Config => {
return {
github_token: env.GITHUB_TOKEN || "",
@ -23,8 +43,9 @@ export const parseConfig = (env: Env): Config => {
input_name: env.INPUT_NAME,
input_body: env.INPUT_BODY,
input_body_path: env.INPUT_BODY_PATH,
input_files: (env.INPUT_FILES || "").split(","),
input_draft: env.INPUT_DRAFT === "true"
input_files: parseInputFiles(env.INPUT_FILES || ""),
input_draft: env.INPUT_DRAFT === "true",
input_prerelease: env.INPUT_PRERELEASE == "true"
};
};