mirror of
https://github.com/tauri-apps/gh-tangle-release.git
synced 2026-02-04 02:31:18 +01:00
Initial release
This commit is contained in:
24
.eslintrc
Normal file
24
.eslintrc
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"prettier"
|
||||
]
|
||||
}
|
||||
15
.github/workflows/ci.yml
vendored
Normal file
15
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: "Tests"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: # array of glob patterns matching against refs/heads. Optional; defaults to all
|
||||
- master # triggers on pushes that contain changes in master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
92
.gitignore
vendored
Normal file
92
.gitignore
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Misc
|
||||
.idea/
|
||||
.DS_Store
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource+actions/create-release@github.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 IOTA Stiftung
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
39
README.md
Normal file
39
README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# GitHub Action to Add Release Metadata to The IOTA Tangle
|
||||
|
||||
This GitHub Action will take the contents of your GitHub release and create an associated Transactions on the IOTA Tangle. This way the data associated with the release becomes immutable.
|
||||
|
||||
## Usage
|
||||
|
||||
Create a GitHub workflow in you repo e.g. `/.github/workflows/tangle-release.tml`
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
name: Create Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Create Tangle Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body: |
|
||||
Changes in this Release
|
||||
- First Change
|
||||
- Second Change
|
||||
draft: false
|
||||
prerelease: false
|
||||
```
|
||||
34
action.yml
Normal file
34
action.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
name: 'Create a Release'
|
||||
description: 'Create a release for a tag in your repository'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'The name of the tag. This should come from the webhook payload, `github.GITHUB_REF` when a user pushes a new tag'
|
||||
required: true
|
||||
release_name:
|
||||
description: 'The name of the release. For example, `Release v1.0.1`'
|
||||
required: true
|
||||
body:
|
||||
description: 'Text describing the contents of the tag.'
|
||||
required: false
|
||||
draft:
|
||||
description: '`true` to create a draft (unpublished) release, `false` to create a published one. Default: `false`'
|
||||
required: false
|
||||
default: false
|
||||
prerelease:
|
||||
description: '`true` to identify the release as a prerelease. `false` to identify the release as a full release. Default: `false`'
|
||||
required: false
|
||||
default: false
|
||||
outputs:
|
||||
id:
|
||||
description: 'The ID of the created Release'
|
||||
html_url:
|
||||
description: 'The URL users can navigate to in order to view the release'
|
||||
upload_url:
|
||||
description: 'The URL for uploading assets to the release'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'tag'
|
||||
color: 'gray-dark'
|
||||
11021
dist/index.js
vendored
Normal file
11021
dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6536
package-lock.json
generated
Normal file
6536
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
54
package.json
Normal file
54
package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "gh-tangle-release",
|
||||
"version": "1.0.0",
|
||||
"description": "Create a release and adds metadata to the tangle",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint 'src/**.js' 'tests/**.js' --fix",
|
||||
"test": "eslint 'src/**.js' 'tests/**.js' && jest --coverage",
|
||||
"build": "ncc build src/main.js",
|
||||
"precommit": "npm run build && git add dist/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/obany/gh-tangle-release"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
"node"
|
||||
],
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.0.0",
|
||||
"@actions/exec": "^1.0.0",
|
||||
"@actions/github": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@zeit/ncc": "^0.20.4",
|
||||
"eslint": "^5.13.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-config-prettier": "^3.6.0",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||
"eslint-plugin-prettier": "^2.7.0",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"jest": "^24.8.0",
|
||||
"prettier": "^1.16.4",
|
||||
"husky": "^3.0.5"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"collectCoverageFrom": [
|
||||
"src/create-release.js"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 80,
|
||||
"functions": 80,
|
||||
"lines": 80,
|
||||
"statements": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/create-release.js
Normal file
44
src/create-release.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const core = require('@actions/core');
|
||||
const { GitHub } = require('@actions/github');
|
||||
const fs = require('fs');
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
// Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage
|
||||
const github = new GitHub(process.env.GITHUB_TOKEN);
|
||||
|
||||
// Get the inputs from the workflow file: https://github.com/actions/toolkit/tree/master/packages/core#inputsoutputs
|
||||
const uploadUrl = core.getInput('upload_url', { required: true });
|
||||
const assetPath = core.getInput('asset_path', { required: true });
|
||||
const assetName = core.getInput('asset_name', { required: true });
|
||||
const assetContentType = core.getInput('asset_content_type', { required: true });
|
||||
|
||||
// Determine content-length for header to upload asset
|
||||
const contentLength = filePath => fs.statSync(filePath).size;
|
||||
|
||||
// Setup headers for API call, see Octokit Documentation: https://octokit.github.io/rest.js/#octokit-routes-repos-upload-release-asset for more information
|
||||
const headers = { 'content-type': assetContentType, 'content-length': contentLength(assetPath) };
|
||||
|
||||
// Upload a release asset
|
||||
// API Documentation: https://developer.github.com/v3/repos/releases/#upload-a-release-asset
|
||||
// Octokit Documentation: https://octokit.github.io/rest.js/#octokit-routes-repos-upload-release-asset
|
||||
const uploadAssetResponse = await github.repos.uploadReleaseAsset({
|
||||
url: uploadUrl,
|
||||
headers,
|
||||
name: assetName,
|
||||
file: fs.readFileSync(assetPath)
|
||||
});
|
||||
|
||||
// Get the browser_download_url for the uploaded release asset from the response
|
||||
const {
|
||||
data: { browser_download_url: browserDownloadUrl }
|
||||
} = uploadAssetResponse;
|
||||
|
||||
// Set the output variable for use by other actions: https://github.com/actions/toolkit/tree/master/packages/core#inputsoutputs
|
||||
core.setOutput('browser_download_url', browserDownloadUrl);
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = run;
|
||||
5
src/main.js
Normal file
5
src/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const run = require('./create-release');
|
||||
|
||||
if (require.main === module) {
|
||||
run();
|
||||
}
|
||||
165
tests/create-release.test.js
Normal file
165
tests/create-release.test.js
Normal file
@@ -0,0 +1,165 @@
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/github');
|
||||
|
||||
const core = require('@actions/core');
|
||||
const { GitHub, context } = require('@actions/github');
|
||||
const run = require('../src/create-release.js');
|
||||
|
||||
/* eslint-disable no-undef */
|
||||
describe('Create Release', () => {
|
||||
let createRelease;
|
||||
|
||||
beforeEach(() => {
|
||||
createRelease = jest.fn().mockReturnValueOnce({
|
||||
data: {
|
||||
id: 'releaseId',
|
||||
html_url: 'htmlUrl',
|
||||
upload_url: 'uploadUrl'
|
||||
}
|
||||
});
|
||||
|
||||
context.repo = {
|
||||
owner: 'owner',
|
||||
repo: 'repo'
|
||||
};
|
||||
|
||||
const github = {
|
||||
repos: {
|
||||
createRelease
|
||||
}
|
||||
};
|
||||
|
||||
GitHub.mockImplementation(() => github);
|
||||
});
|
||||
|
||||
test('Create release endpoint is called', async () => {
|
||||
core.getInput = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce('refs/tags/v1.0.0')
|
||||
.mockReturnValueOnce('myRelease')
|
||||
.mockReturnValueOnce('myBody')
|
||||
.mockReturnValueOnce('false')
|
||||
.mockReturnValueOnce('false');
|
||||
|
||||
await run();
|
||||
|
||||
expect(createRelease).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
tag_name: 'v1.0.0',
|
||||
name: 'myRelease',
|
||||
body: 'myBody',
|
||||
draft: false,
|
||||
prerelease: false
|
||||
});
|
||||
});
|
||||
|
||||
test('Draft release is created', async () => {
|
||||
core.getInput = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce('refs/tags/v1.0.0')
|
||||
.mockReturnValueOnce('myRelease')
|
||||
.mockReturnValueOnce('myBody')
|
||||
.mockReturnValueOnce('true')
|
||||
.mockReturnValueOnce('false');
|
||||
|
||||
await run();
|
||||
|
||||
expect(createRelease).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
tag_name: 'v1.0.0',
|
||||
name: 'myRelease',
|
||||
body: 'myBody',
|
||||
draft: true,
|
||||
prerelease: false
|
||||
});
|
||||
});
|
||||
|
||||
test('Pre-release release is created', async () => {
|
||||
core.getInput = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce('refs/tags/v1.0.0')
|
||||
.mockReturnValueOnce('myRelease')
|
||||
.mockReturnValueOnce('myBody')
|
||||
.mockReturnValueOnce('false')
|
||||
.mockReturnValueOnce('true');
|
||||
|
||||
await run();
|
||||
|
||||
expect(createRelease).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
tag_name: 'v1.0.0',
|
||||
name: 'myRelease',
|
||||
body: 'myBody',
|
||||
draft: false,
|
||||
prerelease: true
|
||||
});
|
||||
});
|
||||
|
||||
test('Release with empty body is created', async () => {
|
||||
core.getInput = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce('refs/tags/v1.0.0')
|
||||
.mockReturnValueOnce('myRelease')
|
||||
.mockReturnValueOnce('') // <-- The default value for body in action.yml
|
||||
.mockReturnValueOnce('false')
|
||||
.mockReturnValueOnce('false');
|
||||
|
||||
await run();
|
||||
|
||||
expect(createRelease).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
tag_name: 'v1.0.0',
|
||||
name: 'myRelease',
|
||||
body: '',
|
||||
draft: false,
|
||||
prerelease: false
|
||||
});
|
||||
});
|
||||
|
||||
test('Outputs are set', async () => {
|
||||
core.getInput = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce('refs/tags/v1.0.0')
|
||||
.mockReturnValueOnce('myRelease')
|
||||
.mockReturnValueOnce('myBody')
|
||||
.mockReturnValueOnce('false')
|
||||
.mockReturnValueOnce('false');
|
||||
|
||||
core.setOutput = jest.fn();
|
||||
|
||||
await run();
|
||||
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'id', 'releaseId');
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'html_url', 'htmlUrl');
|
||||
expect(core.setOutput).toHaveBeenNthCalledWith(3, 'upload_url', 'uploadUrl');
|
||||
});
|
||||
|
||||
test('Action fails elegantly', async () => {
|
||||
core.getInput = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce('refs/tags/v1.0.0')
|
||||
.mockReturnValueOnce('myRelease')
|
||||
.mockReturnValueOnce('myBody')
|
||||
.mockReturnValueOnce('false')
|
||||
.mockReturnValueOnce('false');
|
||||
|
||||
createRelease.mockRestore();
|
||||
createRelease.mockImplementation(() => {
|
||||
throw new Error('Error creating release');
|
||||
});
|
||||
|
||||
core.setOutput = jest.fn();
|
||||
|
||||
core.setFailed = jest.fn();
|
||||
|
||||
await run();
|
||||
|
||||
expect(createRelease).toHaveBeenCalled();
|
||||
expect(core.setFailed).toHaveBeenCalledWith('Error creating release');
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user