Compare commits

..

3 Commits

Author SHA1 Message Date
b21b43df68 empathy for those new to typescript projects 2019-09-09 21:59:28 +09:00
4976c1864c call out cross platform support 2019-09-09 21:50:33 +09:00
a40b254993 node_modules 2019-09-09 21:38:33 +09:00
16 changed files with 48 additions and 376 deletions

1
.github/FUNDING.yml vendored
View File

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

2
.gitignore vendored
View File

@ -1,4 +1,2 @@
__tests__/runner/*
# actions requires a node_modules dir https://github.com/actions/toolkit/blob/master/docs/javascript-action.md#publish-a-releasesv1-action
# but its recommended not to check these in https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations
#node_modules

View File

@ -1,43 +1,9 @@
## 0.1.3
## 0.1.0
* 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)
---
* Initial release
## 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
---
## 0.1.0
* Initial release
You'll need to remove `docker://` prefix and use the `@v1` action tag

View File

@ -3,11 +3,10 @@
> A GitHub Action for creating GitHub Releases on Linux, Windows, and OSX virtual environments
![Screenshot](demo.png)
> **⚠️ 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
@ -28,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@master
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
@ -51,7 +50,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@master
- name: Release
uses: softprops/action-gh-release@v1
env:
@ -65,7 +64,7 @@ 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 newline-delimited list of glob expressions matching the files
Use the `with.files` input to declare a comma-separated 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`
@ -80,7 +79,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@master
- name: Build
run: echo ${{ github.sha }} > Release.txt
- name: Test
@ -94,36 +93,6 @@ 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
@ -140,7 +109,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@master
- name: Generate Changelog
run: echo "# Good things have arrived" > ${{ github.workflow }}-CHANGELOG.txt
- name: Release
@ -163,8 +132,7 @@ 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 |
| `prerelease`| Boolean | Indicator of whether or not is a prerelease |
| `files` | String | Newline-delimited globs of paths to assets to upload for release|
| `files` | String | Comma-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.
@ -177,7 +145,4 @@ 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

View File

@ -1 +0,0 @@
bar

View File

@ -1,92 +1,7 @@
import {
releaseBody,
isTag,
paths,
parseConfig,
parseInputFiles
} from "../src/util";
import { isTag, paths } 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

@ -13,13 +13,10 @@ inputs:
description: 'Gives the release a custom name. Defaults to tag name'
required: false
draft:
description: 'Creates a draft release. Defaults to false'
required: false
prerelease:
description: 'Identify the release as a prerelease. Defaults to false'
description: 'Creates a draft release'
required: false
files:
description: 'Newline-delimited list of path globs for asset files to upload'
description: 'Comma-delimited list of path globs for asset files to upload'
required: false
env:
'GITHUB_TOKEN': 'As provided by Github Actions'

BIN
demo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -8,33 +8,10 @@ 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) {
return this.github.paginate.iterator(this.github.repos.listReleases.endpoint.merge(params));
}
}
exports.GitHubReleaser = GitHubReleaser;
exports.asset = (path) => {
return {
name: path_1.basename(path),
@ -59,35 +36,11 @@ exports.upload = (gh, url, path) => __awaiter(void 0, void 0, void 0, function*
file
});
});
exports.release = (config, releaser) => __awaiter(void 0, void 0, void 0, function* () {
var e_1, _a;
exports.release = (config, gh) => __awaiter(void 0, void 0, void 0, function* () {
const [owner, repo] = config.github_repository.split("/");
const tag = config.github_ref.replace("refs/tags/", "");
try {
// 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({
let release = yield gh.repos.getReleaseByTag({
owner,
repo,
tag
@ -99,25 +52,23 @@ exports.release = (config, releaser) => __awaiter(void 0, void 0, void 0, functi
try {
const tag_name = tag;
const name = config.input_name || tag;
const body = util_1.releaseBody(config);
const body = config.input_body;
const draft = config.input_draft;
const prerelease = config.input_prerelease;
console.log(`👩‍🏭 Creating new GitHub release for tag ${tag_name}...`);
let release = yield releaser.createRelease({
let release = yield gh.repos.createRelease({
owner,
repo,
tag_name,
name,
body,
draft,
prerelease
draft
});
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, releaser);
return exports.release(config, gh);
}
}
else {

View File

@ -22,7 +22,7 @@ function run() {
throw new Error(`⚠️ GitHub Releases requires a tag`);
}
const gh = new github_2.GitHub(config.github_token);
let rel = yield github_1.release(config, new github_1.GitHubReleaser(gh));
let rel = yield github_1.release(config, 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);

View File

@ -9,17 +9,6 @@ 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 || "",
@ -28,9 +17,8 @@ exports.parseConfig = (env) => {
input_name: env.INPUT_NAME,
input_body: env.INPUT_BODY,
input_body_path: env.INPUT_BODY_PATH,
input_files: exports.parseInputFiles(env.INPUT_FILES || ""),
input_draft: env.INPUT_DRAFT === "true",
input_prerelease: env.INPUT_PRERELEASE == "true"
input_files: (env.INPUT_FILES || "").split(","),
input_draft: env.INPUT_DRAFT === "true"
};
};
exports.paths = (patterns) => {

21
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "action-gh-release",
"version": "0.1.3",
"version": "0.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -3437,12 +3437,6 @@
"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",
@ -4832,16 +4826,15 @@
"dev": true
},
"ts-jest": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.1.0.tgz",
"integrity": "sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ==",
"version": "24.0.2",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.0.2.tgz",
"integrity": "sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==",
"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",
@ -4891,9 +4884,9 @@
}
},
"typescript": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz",
"integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==",
"dev": true
},
"typescript-formatter": {

View File

@ -1,6 +1,6 @@
{
"name": "action-gh-release",
"version": "0.1.3",
"version": "0.1.1",
"private": true,
"description": "GitHub Action for creating GitHub Releases",
"main": "lib/main.js",
@ -26,14 +26,14 @@
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/jest": "^24.0.18",
"@types/jest": "^24.0.13",
"@types/mime": "^2.0.1",
"@types/node": "^12.7.4",
"jest": "^24.9.0",
"jest-circus": "^24.9.0",
"jest": "^24.8.0",
"jest-circus": "^24.7.1",
"prettier": "1.18.2",
"ts-jest": "^24.1.0",
"typescript": "^3.6.3",
"ts-jest": "^24.0.2",
"typescript": "^3.5.1",
"typescript-formatter": "^7.2.2"
}
}

View File

@ -1,5 +1,5 @@
import { GitHub } from "@actions/github";
import { Config, releaseBody } from "./util";
import { Config } from "./util";
import { lstatSync, readFileSync } from "fs";
import { getType } from "mime";
import { basename } from "path";
@ -14,66 +14,6 @@ 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[] }> {
return this.github.paginate.iterator(
this.github.repos.listReleases.endpoint.merge(params)
);
}
}
export const asset = (path: string): ReleaseAsset => {
@ -107,27 +47,11 @@ export const upload = async (
});
};
export const release = async (
config: Config,
releaser: Releaser
): Promise<Release> => {
export const release = async (config: Config, gh: GitHub): Promise<Release> => {
const [owner, repo] = config.github_repository.split("/");
const tag = config.github_ref.replace("refs/tags/", "");
try {
// 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({
let release = await gh.repos.getReleaseByTag({
owner,
repo,
tag
@ -138,18 +62,16 @@ export const release = async (
try {
const tag_name = tag;
const name = config.input_name || tag;
const body = releaseBody(config);
const body = config.input_body;
const draft = config.input_draft;
const prerelease = config.input_prerelease;
console.log(`👩‍🏭 Creating new GitHub release for tag ${tag_name}...`);
let release = await releaser.createRelease({
let release = await gh.repos.createRelease({
owner,
repo,
tag_name,
name,
body,
draft,
prerelease
draft
});
return release.data;
} catch (error) {
@ -157,7 +79,7 @@ export const release = async (
console.log(
`⚠️ GitHub release failed with status: ${error.status}, retrying...`
);
return release(config, releaser);
return release(config, gh);
}
} else {
console.log(

View File

@ -1,5 +1,5 @@
import { paths, parseConfig, isTag } from "./util";
import { release, upload, GitHubReleaser } from "./github";
import { release, upload } from "./github";
import { setFailed } from "@actions/core";
import { GitHub } from "@actions/github";
import { env } from "process";
@ -11,7 +11,7 @@ async function run() {
throw new Error(`⚠️ GitHub Releases requires a tag`);
}
const gh = new GitHub(config.github_token);
let rel = await release(config, new GitHubReleaser(gh));
let rel = await release(config, gh);
if (config.input_files) {
paths(config.input_files).forEach(async path => {
await upload(gh, rel.upload_url, path);

View File

@ -1,5 +1,5 @@
import * as glob from "glob";
import { lstatSync, readFileSync } from "fs";
import { lstatSync } from "fs";
export interface Config {
github_token: string;
@ -11,30 +11,10 @@ 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 || "",
@ -43,9 +23,8 @@ 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: parseInputFiles(env.INPUT_FILES || ""),
input_draft: env.INPUT_DRAFT === "true",
input_prerelease: env.INPUT_PRERELEASE == "true"
input_files: (env.INPUT_FILES || "").split(","),
input_draft: env.INPUT_DRAFT === "true"
};
};