Compare commits
304 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9128f238ee | ||
|
|
7922049688 | ||
|
|
0683ea3557 | ||
|
|
01c9fac73b | ||
|
|
90dc22b77f | ||
|
|
10585cb926 | ||
|
|
1896b4fc46 | ||
|
|
6996c1bc95 | ||
|
|
c07d979560 | ||
|
|
38da029bb7 | ||
|
|
bf17a40975 | ||
|
|
fa4749311e | ||
|
|
36e78ab629 | ||
|
|
6c1f9baed2 | ||
|
|
db62f3f3ad | ||
|
|
e6992e14b1 | ||
|
|
440c8c1cb0 | ||
|
|
e805702217 | ||
|
|
bd572a1942 | ||
|
|
706b28e44c | ||
|
|
411ac011df | ||
|
|
a8c5a7f252 | ||
|
|
62f16c02e7 | ||
|
|
00e83e3d35 | ||
|
|
906fc77113 | ||
|
|
17b559883e | ||
|
|
33bf18d283 | ||
|
|
009df9782d | ||
|
|
4b84ad5c60 | ||
|
|
34dae7e8c0 | ||
|
|
9fc271e109 | ||
|
|
3cfa40eca4 | ||
|
|
cdcc88a9ac | ||
|
|
033b82b409 | ||
|
|
e774b3eb81 | ||
|
|
80732ccbb8 | ||
|
|
8b72352976 | ||
|
|
e1e10133db | ||
|
|
7fdd38c8a8 | ||
|
|
fb175cca64 | ||
|
|
c4d491c3c9 | ||
|
|
aa3b2fa675 | ||
|
|
215ba89ed6 | ||
|
|
a8bcd956fb | ||
|
|
bb3709bff2 | ||
|
|
e9a92928b6 | ||
|
|
765cc979be | ||
|
|
00fc285722 | ||
|
|
1cbdc80532 | ||
|
|
4b91e3add1 | ||
|
|
b6d6ead779 | ||
|
|
1da2ee60a2 | ||
|
|
4f8867fa5e | ||
|
|
f4b7f82f58 | ||
|
|
4074199da7 | ||
|
|
578e4141d1 | ||
|
|
a36404d62c | ||
|
|
7588f628d1 | ||
|
|
1e3e9c6637 | ||
|
|
c03240caba | ||
|
|
66b1844f0b | ||
|
|
a8aa04e74a | ||
|
|
39c57feb7b | ||
|
|
02a91b5ddd | ||
|
|
0300282a53 | ||
|
|
c4a7702e18 | ||
|
|
4f53bc4bbe | ||
|
|
37813b746a | ||
|
|
b878ec7b82 | ||
|
|
3453d72d33 | ||
|
|
712dbe3d31 | ||
|
|
a454b0181b | ||
|
|
454de296e8 | ||
|
|
823125756f | ||
|
|
53f1c32531 | ||
|
|
7b2b452a12 | ||
|
|
a7c3669135 | ||
|
|
26cdd97dfd | ||
|
|
5a5f227332 | ||
|
|
9324133e56 | ||
|
|
25918b039f | ||
|
|
c18db47aba | ||
|
|
d968c2d7a7 | ||
|
|
e386e41d9f | ||
|
|
476ee61234 | ||
|
|
6db48ced34 | ||
|
|
7ce102b15b | ||
|
|
ddbfce0d45 | ||
|
|
b4b70f8196 | ||
|
|
2600a44651 | ||
|
|
eb7dfdf4be | ||
|
|
e2ce571994 | ||
|
|
69d25a3184 | ||
|
|
b36d9409a9 | ||
|
|
6c75be85e5 | ||
|
|
2d19fb459b | ||
|
|
9cfd0835c6 | ||
|
|
37c87f6b53 | ||
|
|
7288236d4e | ||
|
|
384ae1fad6 | ||
|
|
9347837845 | ||
|
|
893ce36b2e | ||
|
|
c07e9f4dff | ||
|
|
cb77c9ee9c | ||
|
|
8c78ca2662 | ||
|
|
2b385230bf | ||
|
|
3ff6c7eb03 | ||
|
|
3aa9307b7d | ||
|
|
c13ca79f19 | ||
|
|
e9a4ba6596 | ||
|
|
ef679c1c54 | ||
|
|
eb05307dce | ||
|
|
5a8bbe8ea7 | ||
|
|
a48ee3f85e | ||
|
|
bf3b51b3f3 | ||
|
|
67d60d576c | ||
|
|
0371ac0a22 | ||
|
|
231dd3fbcd | ||
|
|
d51f1872a4 | ||
|
|
324f6f4a19 | ||
|
|
ef3e5b02bd | ||
|
|
215101a6e6 | ||
|
|
a5996b3a73 | ||
|
|
8fd54fa522 | ||
|
|
c358143117 | ||
|
|
a2e71bdd4e | ||
|
|
78ea7b5528 | ||
|
|
3898f7abfd | ||
|
|
2afcaec515 | ||
|
|
2954d914c0 | ||
|
|
0bf6967166 | ||
|
|
3dd806f2bf | ||
|
|
443f2c0c79 | ||
|
|
94ba3874ff | ||
|
|
c30ce84c21 | ||
|
|
1830b037be | ||
|
|
3bacd9e49a | ||
|
|
3ac4132803 | ||
|
|
b072aaafe1 | ||
|
|
f3ea29dca7 | ||
|
|
889eb279f8 | ||
|
|
5b3ed26ce2 | ||
|
|
fc324198c9 | ||
|
|
7018cf7fe6 | ||
|
|
c0a948e0a1 | ||
|
|
64d30d31e9 | ||
|
|
0776e821ce | ||
|
|
21fb52b82e | ||
|
|
994ff9f3e4 | ||
|
|
ca859d0f82 | ||
|
|
5ffcfca321 | ||
|
|
3d2de22e3d | ||
|
|
353daac015 | ||
|
|
17085ec3c4 | ||
|
|
2d5b7ef7e5 | ||
|
|
a14d847ec5 | ||
|
|
f24914729f | ||
|
|
d7ebb9b36b | ||
|
|
8a61907f5d | ||
|
|
c9494285dc | ||
|
|
3a295d0589 | ||
|
|
c99c52b068 | ||
|
|
1298899b9a | ||
|
|
15c5838b25 | ||
|
|
eebdfaf405 | ||
|
|
1cbbf13906 | ||
|
|
421213779e | ||
|
|
7719db6a69 | ||
|
|
0e0b15bbc5 | ||
|
|
29a6629a49 | ||
|
|
18990f60e0 | ||
|
|
d37e2be74a | ||
|
|
eed740324c | ||
|
|
4f104f8afe | ||
|
|
700d6bf347 | ||
|
|
58ae73b360 | ||
|
|
ffcd2d4c10 | ||
|
|
c309084e8b | ||
|
|
819589c886 | ||
|
|
6297172c9f | ||
|
|
918a7af1fe | ||
|
|
6ba4f20c9a | ||
|
|
97a630fc75 | ||
|
|
3f844db37a | ||
|
|
9e1799683e | ||
|
|
6553b535a3 | ||
|
|
132ba9b615 | ||
|
|
99014f6c59 | ||
|
|
e663819a51 | ||
|
|
69242acd76 | ||
|
|
09a8e66990 | ||
|
|
6f8234ce3e | ||
|
|
8131ae1c5d | ||
|
|
2c158d72a4 | ||
|
|
404de39364 | ||
|
|
b9600947f7 | ||
|
|
e31291f6a4 | ||
|
|
e5cc18bd59 | ||
|
|
6cddb7ed31 | ||
|
|
8d640a794c | ||
|
|
268f2377f2 | ||
|
|
40bb172bd0 | ||
|
|
66aaa1eb00 | ||
|
|
8aada7312c | ||
|
|
ec76ac98f7 | ||
|
|
e709279ee8 | ||
|
|
b7a6e45e99 | ||
|
|
986121c24a | ||
|
|
2be34cfeec | ||
|
|
592ac51c49 | ||
|
|
01e1051edf | ||
|
|
15ede536e2 | ||
|
|
3e88dac4cf | ||
|
|
95aae1ba96 | ||
|
|
821cbcc349 | ||
|
|
4531373c80 | ||
|
|
be8fb09624 | ||
|
|
0d72fe9393 | ||
|
|
045b513398 | ||
|
|
b57e76011b | ||
|
|
62f9f43ceb | ||
|
|
61fe7e2b7b | ||
|
|
b37dba0653 | ||
|
|
cba6a1b4f8 | ||
|
|
e97ceb3e08 | ||
|
|
c18d31567a | ||
|
|
91233e9c66 | ||
|
|
a4548f83be | ||
|
|
2162d1bc14 | ||
|
|
f597c8ad62 | ||
|
|
a4f828a4e5 | ||
|
|
4fd91f8b23 | ||
|
|
b60bd83d11 | ||
|
|
8f144e5a49 | ||
|
|
e05d100273 | ||
|
|
7461bfe839 | ||
|
|
dee715a153 | ||
|
|
b102518a6c | ||
|
|
508dab8aab | ||
|
|
39a94f584a | ||
|
|
6b6b0348be | ||
|
|
441e966601 | ||
|
|
92352a1956 | ||
|
|
ea4c59cdea | ||
|
|
f1984b06a2 | ||
|
|
92158b2aee | ||
|
|
289da33bfb | ||
|
|
793ce4c6b0 | ||
|
|
7bba931a39 | ||
|
|
6fb3846972 | ||
|
|
f2bb959dcd | ||
|
|
73272faa39 | ||
|
|
403e4fc8af | ||
|
|
971e60a37e | ||
|
|
a5a057b707 | ||
|
|
d1125b4510 | ||
|
|
15abc13cc4 | ||
|
|
616708020f | ||
|
|
085dc21232 | ||
|
|
06fefc702f | ||
|
|
a84d04e910 | ||
|
|
ec90733eaa | ||
|
|
839c2ee3df | ||
|
|
a43fb1aa82 | ||
|
|
8f0b206fd3 | ||
|
|
9b14e2e2d3 | ||
|
|
af980963d6 | ||
|
|
5b1448e480 | ||
|
|
f7b694c6d5 | ||
|
|
e78d6f8d64 | ||
|
|
c3a232b7b2 | ||
|
|
a9ffe7f597 | ||
|
|
09eb035337 | ||
|
|
970fd8405b | ||
|
|
299f7ef088 | ||
|
|
b2c7be9b04 | ||
|
|
840524d784 | ||
|
|
83aacbf1b5 | ||
|
|
7ca6e41462 | ||
|
|
067437b167 | ||
|
|
94366fd224 | ||
|
|
485ab5eef1 | ||
|
|
23ebf75c7e | ||
|
|
62a7618b4e | ||
|
|
6f236bd2ff | ||
|
|
05b0499270 | ||
|
|
8954a2e764 | ||
|
|
93adae701b | ||
|
|
71ce705419 | ||
|
|
a8943aa989 | ||
|
|
c4e12dcecf | ||
|
|
791611e03e | ||
|
|
2ea88b1f32 | ||
|
|
d57d57cad2 | ||
|
|
767a5edcf4 | ||
|
|
cffd02c48e | ||
|
|
04d64ce68e | ||
|
|
c8e9e33d90 | ||
|
|
8f1cb7790e | ||
|
|
40267624b8 | ||
|
|
cb3417d207 | ||
|
|
5ce722314c | ||
|
|
734853d8c8 | ||
|
|
b25a3c63b3 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
32
.github/workflows/build.yml
vendored
Normal file
32
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: "PR Checks"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
check_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: "yarn install"
|
||||
run: yarn install
|
||||
|
||||
- name: "yarn build"
|
||||
run: yarn build
|
||||
|
||||
- name: "check for uncommitted changes"
|
||||
# Ensure no changes, but ignore node_modules dir since dev/fresh ci deps installed.
|
||||
run: |
|
||||
git diff --exit-code --stat -- . ':!node_modules' \
|
||||
|| (echo "##[error] found changed files after build. please 'yarn build && npm run format'" \
|
||||
"and check in all changes" \
|
||||
&& exit 1)
|
||||
25
.github/workflows/checkin.yml
vendored
25
.github/workflows/checkin.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: "PR Checks"
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
check_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: "yarn install"
|
||||
run: yarn install
|
||||
|
||||
- name: "yarn build"
|
||||
run: yarn build
|
||||
|
||||
- name: "yarn test"
|
||||
run: yarn test
|
||||
|
||||
- name: "check for uncommitted changes"
|
||||
# Ensure no changes, but ignore node_modules dir since dev/fresh ci deps installed.
|
||||
run: |
|
||||
git diff --exit-code --stat -- . ':!node_modules' \
|
||||
|| (echo "##[error] found changed files after build. please 'yarn build && npm run format'" \
|
||||
"and check in all changes" \
|
||||
&& exit 1)
|
||||
22
.github/workflows/release.yml
vendored
Normal file
22
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
create_release:
|
||||
if: github.ref != 'refs/tags/v1'
|
||||
runs-on: ubuntu-latest
|
||||
name: Create Release
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
draft: true
|
||||
generateReleaseNotes: true
|
||||
18
.github/workflows/test.yml
vendored
Normal file
18
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: "Test"
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: "yarn install"
|
||||
run: yarn install
|
||||
|
||||
- name: "yarn test"
|
||||
run: yarn test
|
||||
100
.gitignore
vendored
100
.gitignore
vendored
@@ -1,2 +1,102 @@
|
||||
node_modules/
|
||||
__tests__/runner/*
|
||||
.DS_Store
|
||||
|
||||
# Created by https://www.gitignore.io/api/webstorm
|
||||
# Edit at https://www.gitignore.io/?templates=webstorm
|
||||
|
||||
### WebStorm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### WebStorm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# End of https://www.gitignore.io/api/webstorm
|
||||
|
||||
# Coverage
|
||||
coverage
|
||||
|
||||
# Ignore lib, it contains intermediates
|
||||
/lib
|
||||
|
||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
6
.idea/biome.xml
generated
Normal file
6
.idea/biome.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="BiomeSettings">
|
||||
<option name="enableLspFormat" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
242
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
242
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,242 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="nonThreadSafeClasses">
|
||||
<value />
|
||||
</option>
|
||||
<option name="nonThreadSafeTypes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintIconExpectedSize" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintRtlHardcoded" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="AndroidLintRtlSymmetry" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="AndroidLintStopShip" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintTypographyQuotes" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTransformationOfOriginalParameter" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BadExceptionCaught" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="exceptionsString" value="" />
|
||||
<option name="exceptions">
|
||||
<value />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BooleanMethodIsAlwaysInverted" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Tests" level="WARNING" enabled="false" />
|
||||
<option name="onlyReportMultiple" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BooleanVariableAlwaysNegated" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToSimpleGetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateGetter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToSimpleSetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateSetter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_CLASSES" value="true" />
|
||||
<option name="REPORT_METHODS" value="true" />
|
||||
<option name="REPORT_FIELDS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassReferencesSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="ignoreSimpleAssignmentsAndReturns" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConfusingElse" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="reportWhenNoStatementFollow" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConstantAssertCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ConstantIfStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ConstantValueVariableUse" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreOnVolatileVariables" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DoubleNegation" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DynamicRegexReplaceableByCompiledPattern" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyCatchBlock" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeComments" value="false" />
|
||||
<option name="m_ignoreTestCases" value="true" />
|
||||
<option name="m_ignoreIgnoreParameter" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EmptyInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="ignoreSwitchStatementsWithDefault" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EqualsAndHashcode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionFromCatchWhichDoesntWrap" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetMessage" value="false" />
|
||||
<option name="ignoreCantWrap" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldAccessNotGuarded" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="countGettersAndSetters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FinalMethodInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ForLoopThatDoesntUseLoopVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InfiniteLoopStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceGuardedByStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JavaDoc" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="TOP_LEVEL_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="public" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="INNER_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="protected" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="METHOD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="protected" />
|
||||
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="FIELD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="protected" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="IGNORE_DEPRECATED" value="true" />
|
||||
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
|
||||
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
|
||||
<option name="IGNORE_POINT_TO_ITSELF" value="true" />
|
||||
<option name="myAdditionalJavadocTags" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JavadocReference" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ListIndexOfReplaceableByContains" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LiteralAsArgToStringEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LoopStatementsThatDontLoop" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="MagicNumber" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Tests" level="WARNING" enabled="false" />
|
||||
<scope name="LevelUp-Test-Projects" level="WARNING" enabled="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodMayBeSynchronized" enabled="false" level="INFO" enabled_by_default="false" />
|
||||
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreObjectMethods" value="true" />
|
||||
<option name="ignoreAnonymousClassMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_requireAnnotationsFirst" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NegatedIfElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
<option name="m_ignoreNegatedZeroComparison" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NegativelyNamedBooleanVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldInImmutable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalGuard" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectAllocationInLoop" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ObjectEquality" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreEnums" value="true" />
|
||||
<option name="m_ignoreClassObjects" value="false" />
|
||||
<option name="m_ignorePrivateConstructors" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRequiredObsoleteCollectionTypes" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Tests" level="WARNING" enabled="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PointlessBooleanExpression" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_ignoreExpressionsContainingConstants" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RawUseOfParameterizedType" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreObjectConstruction" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantThrowsDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReuseOfLocalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SimplifiableConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SimplifiableEqualsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnLoop" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestOnlyProblems" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadRun" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThrowablePrintStackTrace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThrowsRuntimeException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TodoComment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadCatch" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnRootExceptions" value="true" />
|
||||
<option name="ignoreThrown" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="TypeMayBeWeakened" enabled="false" level="INFO" enabled_by_default="false">
|
||||
<option name="useRighthandTypeAsWeakestTypeInAssignments" value="true" />
|
||||
<option name="useParameterizedTypeForCollectionMethods" value="true" />
|
||||
<option name="doNotWeakenToJavaLangObject" value="true" />
|
||||
<option name="onlyWeakentoInterface" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnknownGuard" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryBlockStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSwitchBranches" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryCallToStringValueOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryContinue" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessaryFinalOnLocalVariableOrParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryLabelOnBreakStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessaryLabelOnContinueStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessaryParentheses" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreClarifyingParentheses" value="true" />
|
||||
<option name="ignoreParenthesesOnConditionals" value="true" />
|
||||
<option name="ignoreParenthesesOnLambdaParameter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryQualifierForThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryReturn" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryThis" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassesWithOnlyMain" value="false" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/release-action.iml" filepath="$PROJECT_DIR$/.idea/release-action.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/release-action.iml
generated
Normal file
12
.idea/release-action.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
75
README.md
75
README.md
@@ -1,22 +1,59 @@
|
||||
# Release Action
|
||||
|
||||
This action will create a github release and optionally upload an artifact to it.
|
||||
This action will create a GitHub release and optionally upload an artifact to it.
|
||||
|
||||
<div align="center">
|
||||
<strong>
|
||||
<samp>
|
||||
|
||||
[English](README.md) · [简体中文](README.zh-Hans.md)
|
||||
|
||||
</samp>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
## Action Inputs
|
||||
- **allowUpdates**: An optional flag which indicates if we should update a release if it already exists. Defaults to false.
|
||||
- **artifact**: An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs).
|
||||
- **artifacts**: An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs).
|
||||
- **artifactContentType**: The content type of the artifact. Defaults to raw.
|
||||
- **body**: An optional body for the release.
|
||||
- **bodyFile**: An optional body file for the release. This should be the path to the file.
|
||||
- **commit**: An optional commit reference. This will be used to create the tag if it does not exist.
|
||||
- **draft**: Optionally marks this release as a draft release. Set to `true` to enable.
|
||||
- **name**: An optional name for the release. If this is omitted the tag will be used.
|
||||
- **tag**: An optional tag for the release. If this is omitted the git ref will be used (if it is a tag).
|
||||
- **token**: (**Required**) The Github token. Typically this will be `${{ secrets.GITHUB_TOKEN }}`.
|
||||
| Input name | Description | Required | Default Value |
|
||||
|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|----------------------|
|
||||
| allowUpdates | An optional flag which indicates if we should update a release if it already exists. Defaults to false. | false | "" |
|
||||
| artifactErrorsFailBuild | An optional flag which indicates if artifact read or upload errors should fail the build. | false | "" |
|
||||
| artifacts | An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs) | false | "" |
|
||||
| artifactContentType | The content type of the artifact. Defaults to raw | false | "" |
|
||||
| body | An optional body for the release. Note: This input will have white space trimmed before and after it. Use `bodyFile` if you need a non-trivial markdown body. | false | "" |
|
||||
| bodyFile | An optional body file for the release. This should be the path to the file. | false | "" |
|
||||
| commit | An optional commit reference. This will be used to create the tag if it does not exist. | false | "" |
|
||||
| discussionCategory | When provided this will generate a discussion of the specified category. The category must exist otherwise this will cause the action to fail. This isn't used with draft releases. The default "Announcements" category is not supported via the API and will cause an error if used here. | false | "" |
|
||||
| draft | Optionally marks this release as a draft release. Set to true to enable. | false | "" |
|
||||
| generateReleaseNotes | Indicates if release notes should be automatically generated. | false | false |
|
||||
| makeLatest | Indicates if the release should be the "latest" release or not. legacy specifies that the latest release should be determined based on the release creation date and higher semantic version. | false | "legacy" |
|
||||
| name | An optional name for the release. If this is omitted the tag will be used. | false | "" |
|
||||
| omitBody | Indicates if the release body should be omitted. | false | false |
|
||||
| omitBodyDuringUpdate | Indicates if the release body should be omitted during updates. The body will still be applied for newly created releases. This will preserve the existing body during updates. | false | false |
|
||||
| omitDraftDuringUpdate | Indicates if the draft flag should be omitted during updates. The draft flag will still be applied for newly created releases. This will preserve the existing draft state during updates. | false | false |
|
||||
| omitName | Indicates if the release name should be omitted. | false | false |
|
||||
| omitNameDuringUpdate | Indicates if the release name should be omitted during updates. The name will still be applied for newly created releases. This will preserve the existing name during updates. | false | false |
|
||||
| omitPrereleaseDuringUpdate | Indicates if the prerelease flag should be omitted during updates. The prerelease flag will still be applied for newly created releases. This will preserve the existing prerelease state during updates. | false | false |
|
||||
| owner | Optionally specify the owner of the repo where the release should be generated. Defaults to current repo's owner. | false | "current repo owner" |
|
||||
| prerelease | Optionally marks this release as prerelease. Set to true to enable. | false | "" |
|
||||
| removeArtifacts | Indicates if existing release artifacts should be removed. | false | false |
|
||||
| replacesArtifacts | Indicates if existing release artifacts should be replaced. | false | true |
|
||||
| repo | Optionally specify the repo where the release should be generated. | false | current repo |
|
||||
| skipIfReleaseExists | When skipIfReleaseExists is enabled the action will be skipped if a non-draft release already exists for the provided tag. | false | false |
|
||||
| tag | An optional tag for the release. If this is omitted the git ref will be used (if it is a tag). | false | "" |
|
||||
| token | The GitHub token. This will default to the GitHub app token. This is primarily useful if you want to use your personal token (for targeting other repos, etc). If you are using a personal access token it should have access to the `repo` scope. | false | github.token |
|
||||
| updateOnlyUnreleased | When allowUpdates is enabled, this will fail the action if the release it is updating is not a draft or a prerelease. | false | false |
|
||||
|
||||
## Action Outputs
|
||||
| Output name | Description |
|
||||
|-------------|-----------------------------------------------|
|
||||
| id | The identifier of the created release. |
|
||||
| html_url | The HTML URL of the release. |
|
||||
| upload_url | The URL for [uploading assets](https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset) to the release. |
|
||||
| tarball_url | The URL for downloading the release as a tarball (.tar.gz). |
|
||||
| zipball_url | The URL for downloading the release as a zipball (.zip). |
|
||||
|
||||
## Example
|
||||
This example will create a release when tag is pushed:
|
||||
This example will create a release when a tag is pushed:
|
||||
|
||||
```yml
|
||||
name: Releases
|
||||
@@ -30,16 +67,18 @@ jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "release.tar.gz,foo/*.txt"
|
||||
bodyFile: "body.md"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
```
|
||||
|
||||
## Notes
|
||||
- You must provide a tag either via the action input or the git ref (i.e push / create a tag). If you do not the action will fail.
|
||||
- If the tag of the release you are creating does not exist yet, you should set both the `tag` and `commit` action inputs. `commit` can point to a commit hash or a branch name (ex - `master`).
|
||||
- You must provide a tag either via the action input or the git ref (i.e push / create a tag). If you do not provide a tag the action will fail.
|
||||
- If the tag of the release you are creating does not yet exist, you should set both the `tag` and `commit` action inputs. `commit` can point to a commit hash or a branch name (ex - `main`).
|
||||
- In the example above only required permissions for the action specified (which is `contents: write`). If you add other actions to the same workflow you should expand `permissions` block accordingly.
|
||||
- More info about why the default discussion category "Announcements" does not work with this action may be found here: https://github.com/goreleaser/goreleaser/issues/2304.
|
||||
86
README.zh-Hans.md
Normal file
86
README.zh-Hans.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Release Action
|
||||
|
||||
此操作将创建一个 GitHub Release,并可选择将产出文件上传到其中。
|
||||
|
||||
<div align="center">
|
||||
<strong>
|
||||
<samp>
|
||||
|
||||
[English](README.md) · [简体中文](README.zh-Hans.md)
|
||||
|
||||
</samp>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
## Action 输入
|
||||
|
||||
| 输入名称 | 描述 | 必选 | 默认值 |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -------------------- |
|
||||
| allowUpdates | 一个可选标志,表示如果版本已经存在,我们是否应该更新它。默认值为 false。 | false | "" |
|
||||
| artifactErrorsFailBuild | 一个可选标志,表示读取或上传产出文件错误时是否应该使构建失败。 | false | "" |
|
||||
| artifacts | 一组可选的路径,表示要上传到版本的产出文件。 这可能是单个路径或以逗号分隔的路径列表(或 globs) | false | "" |
|
||||
| artifactContentType | 产出文件的内容类型。 默认为 raw | false | "" |
|
||||
| body | 发布的可选主体。注意:此处输入的首尾空格会被去除。如果您需要特殊的 markdown 内容,请使用 `bodyFile`。 | false | "" |
|
||||
| bodyFile | 发布的可选正文文件。 这应该是文件的路径。 | false | "" |
|
||||
| commit | 一个可选的提交 ref。 如果标签不存在,将用于创建标签。 | false | "" |
|
||||
| discussionCategory | 当提供该选项时,将生成指定类别的 discussion。指定的类别必须存在,否则将导致 Action 失败。这对草案状态的发布不生效。API 不支持默认的 Announcement 分类,使用这个分类会引发错误。 | false | "" |
|
||||
| draft | 可选择将此版本标记为草稿版本。 设置为 true 以启用。 | false | "" |
|
||||
| generateReleaseNotes | 指示是否应自动生成发行说明。 | false | false |
|
||||
| makeLatest | 指示是否将这个发布设置为 latest。使用"lagecy"值则会根据发布时间和语义化版本号自动决定。 | false | "legacy" |
|
||||
| name | 版本的可选名称。 如果省略,将使用标签。 | false | "" |
|
||||
| omitBody | 指示是否应省略发布主体。 | false | false |
|
||||
| omitBodyDuringUpdate | 指示在更新期间是否应省略发布主体。 正文仍将应用于新创建的版本。 这将在更新期间保留现有正文。 | false | false |
|
||||
| omitDraftDuringUpdate | 指示是否应在更新期间省略草稿标志。 草稿标志仍将应用于新创建的版本。 这将在更新期间保留现有的草稿状态。 | false | false |
|
||||
| omitName | 指示是否应省略版本名称。 | false | false |
|
||||
| omitNameDuringUpdate | 指示在更新期间是否应省略版本名称。 该名称仍将应用于新创建的版本。 这将在更新期间保留现有名称。 | false | false |
|
||||
| omitPrereleaseDuringUpdate | 指示在更新期间是否应省略预发布标志。 预发布标志仍将应用于新创建的版本。 这将在更新期间保留现有的预发布状态。 | false | false |
|
||||
| owner | (可选)指定应在其中生成版本的存储库的所有者。 默认为当前存储库的所有者。 | false | "current repo owner" |
|
||||
| prerelease | 可选择将此版本标记为预发布。 设置为 true 以启用。 | false | "" |
|
||||
| removeArtifacts | 指示是否应删除现有的发布产出文件。 | false | false |
|
||||
| replacesArtifacts | 指示是否应替换现有的发布产出文件。 | false | true |
|
||||
| repo | (可选)指定应在其中生成版本的存储库。 | false | current repo |
|
||||
| skipIfReleaseExists | 当该选项启用时,如果提供的标签已存在一个对应的非草案状态的发布,则 action 将会被跳过。 | false | false |
|
||||
| tag | 发布的可选标签。 如果省略,将使用 git ref (如果它是标签)。 | false | "" |
|
||||
| token | GitHub 令牌。 这将默认为 GitHub 应用程序令牌。 如果您想使用您的个人令牌(用于定位其他存储库等),这主要是有用的。 如果您使用的是个人访问令牌,它应该可以访问 `repo` 范围。 | false | github.token |
|
||||
| updateOnlyUnreleased | 启用 allowUpdates 后,如果它正在更新的版本不是草稿或预发布,则该操作将失败。 | false | false |
|
||||
|
||||
## Action 输出
|
||||
|
||||
| 输出名称 | 描述 |
|
||||
| ---------- | ------------------------ |
|
||||
| id | 创建的版本的标识符。 |
|
||||
| html_url | 版本的 HTML URL。 |
|
||||
| upload_url | 将资产上传到版本的 URL。 |
|
||||
|
||||
## 示例
|
||||
|
||||
此示例将在推送一个标签时创建一个 Release:
|
||||
|
||||
```yml
|
||||
name: Releases
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "release.tar.gz,foo/*.txt"
|
||||
bodyFile: "body.md"
|
||||
```
|
||||
|
||||
## 注意
|
||||
|
||||
- 您必须通过 Action 输入或 git ref 提供一个标签(即推送/创建标签)。如果不提供标签,Action 将会失败。
|
||||
- 如果您正在创建的版本的标签不存在,您应该同时设置标签和提交 Action 输入。 commit 可以指向提交 Hash 或分支名称(例如 - main)。
|
||||
- 在上面的示例中,只需要指定操作的权限(即 contents: write)。 如果您将其他操作添加到同一工作流程,则应相应地扩展权限。
|
||||
- 有关于此 action 无法支持 discussion 的 Announcement 分类的原因的更多信息,请参见此处:https://github.com/goreleaser/goreleaser/issues/2304
|
||||
@@ -1,55 +1,180 @@
|
||||
import { Action } from "../src/Action";
|
||||
import { Artifact } from "../src/Artifact";
|
||||
import { Inputs } from "../src/Inputs";
|
||||
import { Releases } from "../src/Releases";
|
||||
import { ArtifactUploader } from "../src/ArtifactUploader";
|
||||
import { Action } from "../src/Action"
|
||||
import type { ActionSkipper } from "../src/ActionSkipper"
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import type { ArtifactDestroyer } from "../src/ArtifactDestroyer"
|
||||
import type { ArtifactUploader } from "../src/ArtifactUploader"
|
||||
import type { Inputs } from "../src/Inputs"
|
||||
import type { Outputs } from "../src/Outputs"
|
||||
import type { Releases } from "../src/Releases"
|
||||
|
||||
const TEST_URLS = {
|
||||
UPLOAD_URL: "http://api.example.com",
|
||||
HTML_URL: "https://github.com/owner/repo/releases/tag/v1.0.0",
|
||||
TARBALL_URL: "https://api.github.com/repos/owner/repo/tarball/v1.0.0",
|
||||
ZIPBALL_URL: "https://api.github.com/repos/owner/repo/zipball/v1.0.0",
|
||||
} as const
|
||||
|
||||
const applyReleaseDataMock = jest.fn()
|
||||
const artifactDestroyMock = jest.fn()
|
||||
const createMock = jest.fn()
|
||||
const deleteMock = jest.fn()
|
||||
const getMock = jest.fn()
|
||||
const listArtifactsMock = jest.fn()
|
||||
const listMock = jest.fn()
|
||||
const shouldSkipMock = jest.fn()
|
||||
const updateMock = jest.fn()
|
||||
const uploadMock = jest.fn()
|
||||
const genReleaseNotesMock = jest.fn()
|
||||
|
||||
const artifacts = [
|
||||
new Artifact('a/art1'),
|
||||
new Artifact('b/art2')
|
||||
]
|
||||
const artifactData = Buffer.from('blob', 'utf-8')
|
||||
const body = 'body'
|
||||
const commit = 'commit'
|
||||
const draft = true
|
||||
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
|
||||
|
||||
const createBody = "createBody"
|
||||
const createDraft = true
|
||||
const createName = "createName"
|
||||
const commit = "commit"
|
||||
const discussionCategory = "discussionCategory"
|
||||
const generateReleaseNotes = true
|
||||
const id = 100
|
||||
const name = 'name'
|
||||
const tag = 'tag'
|
||||
const token = 'token'
|
||||
const url = 'http://api.example.com'
|
||||
const createPrerelease = true
|
||||
const releaseId = 101
|
||||
const replacesArtifacts = true
|
||||
const tag = "tag"
|
||||
const token = "token"
|
||||
const updateBody = "updateBody"
|
||||
const updateDraft = false
|
||||
const updateName = "updateName"
|
||||
const updatePrerelease = false
|
||||
const updateOnlyUnreleased = false
|
||||
const url = TEST_URLS.UPLOAD_URL
|
||||
const makeLatest = "legacy"
|
||||
const generatedReleaseBody = "test release notes"
|
||||
|
||||
describe("Action", () => {
|
||||
beforeEach(() => {
|
||||
createMock.mockClear()
|
||||
getMock.mockClear()
|
||||
listMock.mockClear()
|
||||
shouldSkipMock.mockClear()
|
||||
updateMock.mockClear()
|
||||
uploadMock.mockClear()
|
||||
})
|
||||
|
||||
it('creates release but does not upload if no artifact', async () => {
|
||||
it("creates release but does not upload if no artifact", async () => {
|
||||
const action = createAction(false, false)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).not.toBeCalled()
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it('creates release then uploads artifact', async () => {
|
||||
it("creates release if no release exists to update", async () => {
|
||||
const action = createAction(true, true)
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("creates release if no draft releases", async () => {
|
||||
const action = createAction(true, true)
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({
|
||||
data: [{ id: id, draft: false, tag_name: tag }],
|
||||
})
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("creates release then uploads artifact", async () => {
|
||||
const action = createAction(false, true)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).toBeCalledWith(artifacts, url)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it('throws error when create fails', async () => {
|
||||
it("removes all artifacts when artifact destroyer is enabled", async () => {
|
||||
const action = createAction(false, true, true)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("removes no artifacts when artifact destroyer is disabled", async () => {
|
||||
const action = createAction(false, true)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(artifactDestroyMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("skips action", async () => {
|
||||
const action = createAction(false, false, false)
|
||||
shouldSkipMock.mockResolvedValue(true)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).not.toHaveBeenCalled()
|
||||
expect(updateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws error when create fails", async () => {
|
||||
const action = createAction(false, true)
|
||||
createMock.mockRejectedValue("error")
|
||||
|
||||
@@ -60,20 +185,30 @@ describe("Action", () => {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).not.toBeCalled()
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('throws error when get fails', async () => {
|
||||
const action = createAction(true, false)
|
||||
it("throws error when get fails", async () => {
|
||||
const action = createAction(true, true)
|
||||
const error = {
|
||||
errors: [
|
||||
{
|
||||
code: 'already_exists'
|
||||
}
|
||||
]
|
||||
code: "already_exists",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
createMock.mockRejectedValue(error)
|
||||
getMock.mockRejectedValue("error")
|
||||
expect.hasAssertions()
|
||||
@@ -82,145 +217,343 @@ describe("Action", () => {
|
||||
} catch (error) {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).not.toBeCalled()
|
||||
|
||||
|
||||
expect(getMock).toHaveBeenCalledWith(tag)
|
||||
expect(updateMock).not.toHaveBeenCalled()
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('throws error when update fails', async () => {
|
||||
const action = createAction(true, false)
|
||||
const error = {
|
||||
errors: [
|
||||
{
|
||||
code: 'already_exists'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
createMock.mockRejectedValue(error)
|
||||
updateMock.mockRejectedValue("error")
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).not.toBeCalled()
|
||||
|
||||
})
|
||||
|
||||
it('throws error when upload fails', async () => {
|
||||
const action = createAction(false, true)
|
||||
uploadMock.mockRejectedValue("error")
|
||||
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).toBeCalledWith(artifacts, url)
|
||||
})
|
||||
|
||||
it('updates release but does not upload if no artifact', async () => {
|
||||
const action = createAction(true, false)
|
||||
const error = {
|
||||
errors: [
|
||||
{
|
||||
code: 'already_exists'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
createMock.mockRejectedValue(error)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).not.toBeCalled()
|
||||
|
||||
})
|
||||
|
||||
it('updates release then uploads artifact', async () => {
|
||||
it("throws error when list has no data", async () => {
|
||||
const action = createAction(true, true)
|
||||
getMock.mockRejectedValue({ status: 404 })
|
||||
const error = {
|
||||
errors: [
|
||||
{
|
||||
code: 'already_exists'
|
||||
}
|
||||
]
|
||||
code: "already_exists",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
createMock.mockRejectedValue(error)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).toBeCalledWith(artifacts, url)
|
||||
|
||||
createMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({})
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
expect(error).toEqual(Error("No releases found. Response: {}"))
|
||||
}
|
||||
|
||||
expect(listMock).toHaveBeenCalled()
|
||||
expect(createMock).not.toHaveBeenCalled()
|
||||
expect(updateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
function createAction(allowUpdates: boolean, hasArtifact: boolean): Action {
|
||||
it("throws error when update fails", async () => {
|
||||
const action = createAction(true, true)
|
||||
|
||||
updateMock.mockRejectedValue("error")
|
||||
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws error when upload fails", async () => {
|
||||
const action = createAction(false, true)
|
||||
const expectedError = { status: 404 }
|
||||
uploadMock.mockRejectedValue(expectedError)
|
||||
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
expect(error).toEqual(expectedError)
|
||||
}
|
||||
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
})
|
||||
|
||||
it("updates draft release", async () => {
|
||||
const action = createAction(true, true)
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({
|
||||
data: [
|
||||
{ id: 123, draft: false, tag_name: tag },
|
||||
{ id: id, draft: true, tag_name: tag },
|
||||
],
|
||||
})
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("updates draft release with static body", async () => {
|
||||
const action = createAction(true, true, false, false)
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({
|
||||
data: [
|
||||
{ id: 123, draft: false, tag_name: tag },
|
||||
{ id: id, draft: true, tag_name: tag },
|
||||
],
|
||||
})
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
updateBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("updates release but does not upload if no artifact", async () => {
|
||||
const action = createAction(true, false)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("updates release then uploads artifact", async () => {
|
||||
const action = createAction(true, true)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("updates release with static body when generateReleaseNotes is true but omitBodyDuringUpdate is true", async () => {
|
||||
const action = createAction(true, true, false, true, true)
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({
|
||||
data: [
|
||||
{ id: 123, draft: false, tag_name: tag },
|
||||
{ id: id, draft: true, tag_name: tag },
|
||||
],
|
||||
})
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
updateBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
function assertOutputApplied() {
|
||||
expect(applyReleaseDataMock).toHaveBeenCalledWith({
|
||||
id: releaseId,
|
||||
upload_url: url,
|
||||
html_url: TEST_URLS.HTML_URL,
|
||||
tarball_url: TEST_URLS.TARBALL_URL,
|
||||
zipball_url: TEST_URLS.ZIPBALL_URL,
|
||||
})
|
||||
}
|
||||
|
||||
function createAction(
|
||||
allowUpdates: boolean,
|
||||
hasArtifact: boolean,
|
||||
removeArtifacts = false,
|
||||
generateReleaseNotes = true,
|
||||
omitBodyDuringUpdate = false
|
||||
): Action {
|
||||
let inputArtifact: Artifact[]
|
||||
|
||||
if (hasArtifact) {
|
||||
inputArtifact = artifacts
|
||||
} else {
|
||||
inputArtifact = []
|
||||
}
|
||||
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
return {
|
||||
create: createMock,
|
||||
deleteArtifact: deleteMock,
|
||||
getByTag: getMock,
|
||||
listArtifactsForRelease: listArtifactsMock,
|
||||
listReleases: listMock,
|
||||
update: updateMock,
|
||||
uploadArtifact: uploadMock
|
||||
uploadArtifact: jest.fn(),
|
||||
generateReleaseNotes: genReleaseNotesMock,
|
||||
}
|
||||
})
|
||||
|
||||
createMock.mockResolvedValue({
|
||||
data: {
|
||||
upload_url: url
|
||||
}
|
||||
id: releaseId,
|
||||
upload_url: url,
|
||||
html_url: TEST_URLS.HTML_URL,
|
||||
tarball_url: TEST_URLS.TARBALL_URL,
|
||||
zipball_url: TEST_URLS.ZIPBALL_URL,
|
||||
},
|
||||
})
|
||||
|
||||
genReleaseNotesMock.mockResolvedValue({
|
||||
data: {
|
||||
body: generatedReleaseBody,
|
||||
},
|
||||
})
|
||||
getMock.mockResolvedValue({
|
||||
data: {
|
||||
id: id
|
||||
}
|
||||
id: id,
|
||||
},
|
||||
})
|
||||
listMock.mockResolvedValue({
|
||||
data: [],
|
||||
})
|
||||
shouldSkipMock.mockResolvedValue(false)
|
||||
updateMock.mockResolvedValue({
|
||||
data: {
|
||||
upload_url: url
|
||||
}
|
||||
id: releaseId,
|
||||
upload_url: url,
|
||||
html_url: TEST_URLS.HTML_URL,
|
||||
tarball_url: TEST_URLS.TARBALL_URL,
|
||||
zipball_url: TEST_URLS.ZIPBALL_URL,
|
||||
},
|
||||
})
|
||||
uploadMock.mockResolvedValue({})
|
||||
|
||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||
return {
|
||||
allowUpdates: allowUpdates,
|
||||
allowUpdates,
|
||||
artifactErrorsFailBuild: true,
|
||||
artifacts: inputArtifact,
|
||||
body: body,
|
||||
commit: commit,
|
||||
draft: draft,
|
||||
name: name,
|
||||
tag: tag,
|
||||
token: token,
|
||||
readArtifact: () => artifactData
|
||||
createdDraft: createDraft,
|
||||
createdReleaseBody: createBody,
|
||||
createdReleaseName: createName,
|
||||
commit,
|
||||
discussionCategory,
|
||||
generateReleaseNotes,
|
||||
makeLatest: makeLatest,
|
||||
owner: "owner",
|
||||
createdPrerelease: createPrerelease,
|
||||
replacesArtifacts,
|
||||
removeArtifacts,
|
||||
repo: "repo",
|
||||
skipIfReleaseExists: false,
|
||||
tag,
|
||||
token,
|
||||
updatedDraft: updateDraft,
|
||||
updatedReleaseBody: updateBody,
|
||||
updatedReleaseName: updateName,
|
||||
updatedPrerelease: updatePrerelease,
|
||||
updateOnlyUnreleased: updateOnlyUnreleased,
|
||||
omitBodyDuringUpdate,
|
||||
}
|
||||
})
|
||||
const MockOutputs = jest.fn<Outputs, any>(() => {
|
||||
return {
|
||||
applyReleaseData: applyReleaseDataMock,
|
||||
}
|
||||
})
|
||||
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
|
||||
return {
|
||||
uploadArtifacts: uploadMock
|
||||
uploadArtifacts: uploadMock,
|
||||
}
|
||||
})
|
||||
const MockArtifactDestroyer = jest.fn<ArtifactDestroyer, any>(() => {
|
||||
return {
|
||||
destroyArtifacts: artifactDestroyMock,
|
||||
}
|
||||
})
|
||||
|
||||
const MockActionSkipper = jest.fn<ActionSkipper, any>(() => {
|
||||
return {
|
||||
shouldSkip: shouldSkipMock,
|
||||
}
|
||||
})
|
||||
|
||||
const inputs = new MockInputs()
|
||||
const outputs = new MockOutputs()
|
||||
const releases = new MockReleases()
|
||||
const uploader = new MockUploader()
|
||||
const artifactDestroyer = new MockArtifactDestroyer()
|
||||
const actionSkipper = new MockActionSkipper()
|
||||
|
||||
return new Action(inputs, releases, uploader)
|
||||
return new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
45
__tests__/ActionSkipper.test.ts
Normal file
45
__tests__/ActionSkipper.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ReleaseActionSkipper } from "../src/ActionSkipper"
|
||||
import type { Releases } from "../src/Releases"
|
||||
|
||||
describe("shouldSkip", () => {
|
||||
const getMock = jest.fn()
|
||||
const tag = "tag"
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
deleteArtifact: jest.fn(),
|
||||
getByTag: getMock,
|
||||
listArtifactsForRelease: jest.fn(),
|
||||
listReleases: jest.fn(),
|
||||
update: jest.fn(),
|
||||
uploadArtifact: jest.fn(),
|
||||
generateReleaseNotes: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
it("should return false when skipIfReleaseExists is false", async () => {
|
||||
const actionSkipper = new ReleaseActionSkipper(false, MockReleases(), tag)
|
||||
expect(await actionSkipper.shouldSkip()).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when error occurs", async () => {
|
||||
getMock.mockRejectedValue(new Error())
|
||||
|
||||
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
|
||||
expect(await actionSkipper.shouldSkip()).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when release does not exist", async () => {
|
||||
getMock.mockResolvedValue({})
|
||||
|
||||
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
|
||||
expect(await actionSkipper.shouldSkip()).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when release does exist", async () => {
|
||||
getMock.mockResolvedValue({ data: {} })
|
||||
|
||||
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
|
||||
expect(await actionSkipper.shouldSkip()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,38 +1,40 @@
|
||||
import { Artifact } from "../src/Artifact";
|
||||
import { Artifact } from "../src/Artifact"
|
||||
|
||||
const fileContents = Buffer.from('artful facts', 'utf-8')
|
||||
const contentLength = 42
|
||||
const fakeReadStream = {}
|
||||
|
||||
jest.mock('fs', () => {
|
||||
jest.mock("fs", () => {
|
||||
return {
|
||||
readFileSync: () => fileContents,
|
||||
statSync: () => { return { size: contentLength } }
|
||||
};
|
||||
createReadStream: () => fakeReadStream,
|
||||
statSync: () => {
|
||||
return { size: contentLength }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe("Artifact", () => {
|
||||
it('defaults contentType to raw', () => {
|
||||
const artifact = new Artifact('')
|
||||
expect(artifact.contentType).toBe('raw')
|
||||
it("defaults contentType to raw", () => {
|
||||
const artifact = new Artifact("")
|
||||
expect(artifact.contentType).toBe("raw")
|
||||
})
|
||||
|
||||
it('generates name from path', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.name).toBe('artifact')
|
||||
it("generates name from path", () => {
|
||||
const artifact = new Artifact("some/artifact")
|
||||
expect(artifact.name).toBe("artifact")
|
||||
})
|
||||
|
||||
it('provides contentLength', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
it("provides contentLength", () => {
|
||||
const artifact = new Artifact("some/artifact")
|
||||
expect(artifact.contentLength).toBe(contentLength)
|
||||
})
|
||||
|
||||
it('provides path', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.path).toBe('some/artifact')
|
||||
it("provides path", () => {
|
||||
const artifact = new Artifact("some/artifact")
|
||||
expect(artifact.path).toBe("some/artifact")
|
||||
})
|
||||
|
||||
it('reads artifact', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.readFile()).toBe(fileContents)
|
||||
it("reads artifact", () => {
|
||||
const artifact = new Artifact("some/artifact")
|
||||
expect(artifact.readFile()).toBe(fakeReadStream)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
87
__tests__/ArtifactDestroyer.test.ts
Normal file
87
__tests__/ArtifactDestroyer.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer"
|
||||
import type { Releases } from "../src/Releases"
|
||||
|
||||
const releaseId = 100
|
||||
|
||||
const deleteMock = jest.fn()
|
||||
const listArtifactsMock = jest.fn()
|
||||
|
||||
describe("ArtifactDestroyer", () => {
|
||||
beforeEach(() => {
|
||||
deleteMock.mockClear()
|
||||
listArtifactsMock.mockClear()
|
||||
})
|
||||
|
||||
it("destroys all artifacts", async () => {
|
||||
mockListWithAssets()
|
||||
mockDeleteSuccess()
|
||||
const destroyer = createDestroyer()
|
||||
|
||||
await destroyer.destroyArtifacts(releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("destroys nothing when no artifacts found", async () => {
|
||||
mockListWithoutAssets()
|
||||
const destroyer = createDestroyer()
|
||||
|
||||
await destroyer.destroyArtifacts(releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("throws when delete call fails", async () => {
|
||||
mockListWithAssets()
|
||||
mockDeleteError()
|
||||
const destroyer = createDestroyer()
|
||||
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await destroyer.destroyArtifacts(releaseId)
|
||||
} catch (error) {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
})
|
||||
|
||||
function createDestroyer(): GithubArtifactDestroyer {
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
deleteArtifact: deleteMock,
|
||||
getByTag: jest.fn(),
|
||||
listArtifactsForRelease: listArtifactsMock,
|
||||
listReleases: jest.fn(),
|
||||
update: jest.fn(),
|
||||
uploadArtifact: jest.fn(),
|
||||
generateReleaseNotes: jest.fn(),
|
||||
}
|
||||
})
|
||||
return new GithubArtifactDestroyer(new MockReleases())
|
||||
}
|
||||
|
||||
function mockDeleteError(): any {
|
||||
deleteMock.mockRejectedValue("error")
|
||||
}
|
||||
|
||||
function mockDeleteSuccess(): any {
|
||||
deleteMock.mockResolvedValue({})
|
||||
}
|
||||
|
||||
function mockListWithAssets() {
|
||||
listArtifactsMock.mockResolvedValue([
|
||||
{
|
||||
name: "art1",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
name: "art2",
|
||||
id: 2,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
function mockListWithoutAssets() {
|
||||
listArtifactsMock.mockResolvedValue([])
|
||||
}
|
||||
})
|
||||
@@ -1,39 +1,101 @@
|
||||
const warnMock = jest.fn()
|
||||
|
||||
import { FileArtifactGlobber } from "../src/ArtifactGlobber"
|
||||
import { Globber } from "../src/Globber";
|
||||
import { Artifact } from "../src/Artifact";
|
||||
import { Globber } from "../src/Globber"
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import untildify = require("untildify")
|
||||
|
||||
const contentType = "raw"
|
||||
const globMock = jest.fn()
|
||||
const globResults = ["file1", "file2"]
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return { warning: warnMock }
|
||||
})
|
||||
|
||||
jest.mock("fs", () => {
|
||||
return {
|
||||
statSync: () => {
|
||||
return {
|
||||
isDirectory(): boolean {
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
realpathSync: () => {
|
||||
return false
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe("ArtifactGlobber", () => {
|
||||
beforeEach(() => {
|
||||
globMock.mockClear()
|
||||
})
|
||||
|
||||
it("expands paths in which start with a ~", () => {
|
||||
const globber = createArtifactGlobber()
|
||||
|
||||
const expectedArtifacts = globResults.map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString("~/path", "raw", false)).toEqual(expectedArtifacts)
|
||||
expect(globMock).toHaveBeenCalledWith(untildify("~/path"))
|
||||
expect(warnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("globs simple path", () => {
|
||||
const globber = createArtifactGlobber()
|
||||
|
||||
const expectedArtifacts =
|
||||
globResults.map((path) => new Artifact(path, contentType))
|
||||
const expectedArtifacts = globResults.map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString('path', 'raw'))
|
||||
.toEqual(expectedArtifacts)
|
||||
expect(globber.globArtifactString("path", "raw", false)).toEqual(expectedArtifacts)
|
||||
expect(globMock).toHaveBeenCalledWith("path")
|
||||
expect(warnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("splits multiple paths", () => {
|
||||
it("splits multiple paths with comma separator", () => {
|
||||
const globber = createArtifactGlobber()
|
||||
|
||||
const expectedArtifacts =
|
||||
globResults
|
||||
.concat(globResults)
|
||||
.map((path) => new Artifact(path, contentType))
|
||||
const expectedArtifacts = globResults.concat(globResults).map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString('path1,path2', 'raw'))
|
||||
.toEqual(expectedArtifacts)
|
||||
expect(globber.globArtifactString("path1,path2", "raw", false)).toEqual(expectedArtifacts)
|
||||
expect(globMock).toHaveBeenCalledWith("path1")
|
||||
expect(globMock).toHaveBeenCalledWith("path2")
|
||||
expect(warnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
function createArtifactGlobber(): FileArtifactGlobber {
|
||||
it("splits multiple paths with new line separator and trims start", () => {
|
||||
const globber = createArtifactGlobber()
|
||||
|
||||
const expectedArtifacts = globResults.concat(globResults).map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString("path1\n path2", "raw", false)).toEqual(expectedArtifacts)
|
||||
expect(globMock).toHaveBeenCalledWith("path1")
|
||||
expect(globMock).toHaveBeenCalledWith("path2")
|
||||
expect(warnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("warns when no glob results are produced and empty results shouldn't throw", () => {
|
||||
const globber = createArtifactGlobber([])
|
||||
|
||||
expect(globber.globArtifactString("path", "raw", false)).toEqual([])
|
||||
expect(warnMock).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws when no glob results are produced and empty results shouild throw", () => {
|
||||
const globber = createArtifactGlobber([])
|
||||
expect(() => {
|
||||
globber.globArtifactString("path", "raw", true)
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
function createArtifactGlobber(results: string[] = globResults): FileArtifactGlobber {
|
||||
const MockGlobber = jest.fn<Globber, any>(() => {
|
||||
return {
|
||||
glob: () => globResults
|
||||
glob: globMock,
|
||||
}
|
||||
})
|
||||
globMock.mockReturnValue(results)
|
||||
return new FileArtifactGlobber(new MockGlobber())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
60
__tests__/ArtifactPathValidator.test.ts
Normal file
60
__tests__/ArtifactPathValidator.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
const directoryMock = jest.fn()
|
||||
const warnMock = jest.fn()
|
||||
|
||||
import { ArtifactPathValidator } from "../src/ArtifactPathValidator"
|
||||
|
||||
const pattern = "pattern"
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return { warning: warnMock }
|
||||
})
|
||||
|
||||
jest.mock("fs", () => {
|
||||
return {
|
||||
statSync: () => {
|
||||
return { isDirectory: directoryMock }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe("ArtifactPathValidator", () => {
|
||||
beforeEach(() => {
|
||||
warnMock.mockClear()
|
||||
directoryMock.mockClear()
|
||||
})
|
||||
|
||||
it("warns and filters out path which points to a directory", () => {
|
||||
const paths = ["path1", "path2"]
|
||||
directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false)
|
||||
|
||||
const validator = new ArtifactPathValidator(false, paths, pattern)
|
||||
|
||||
const result = validator.validate()
|
||||
expect(warnMock).toHaveBeenCalled()
|
||||
expect(result).toEqual(["path2"])
|
||||
})
|
||||
|
||||
it("warns when no glob results are produced and empty results shouldn't throw", () => {
|
||||
const validator = new ArtifactPathValidator(false, [], pattern)
|
||||
const result = validator.validate()
|
||||
expect(warnMock).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws when no glob results are produced and empty results shouild throw", () => {
|
||||
const validator = new ArtifactPathValidator(true, [], pattern)
|
||||
expect(() => {
|
||||
validator.validate()
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
it("throws when path points to directory", () => {
|
||||
const paths = ["path1", "path2"]
|
||||
directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false)
|
||||
|
||||
const validator = new ArtifactPathValidator(true, paths, pattern)
|
||||
|
||||
expect(() => {
|
||||
validator.validate()
|
||||
}).toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,45 +1,214 @@
|
||||
import { RequestError } from "@octokit/request-error"
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import { GithubArtifactUploader } from "../src/ArtifactUploader"
|
||||
import { Releases } from "../src/Releases";
|
||||
import type { Releases } from "../src/Releases"
|
||||
|
||||
const artifacts = [
|
||||
new Artifact('a/art1'),
|
||||
new Artifact('b/art2')
|
||||
]
|
||||
const fileContents = Buffer.from('artful facts', 'utf-8')
|
||||
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
|
||||
const fakeReadStream = {}
|
||||
const contentLength = 42
|
||||
const uploadMock = jest.fn()
|
||||
const url = 'http://api.example.com'
|
||||
const releaseId = 100
|
||||
const url = "http://api.example.com"
|
||||
|
||||
jest.mock('fs', () => {
|
||||
const deleteMock = jest.fn()
|
||||
const listArtifactsMock = jest.fn()
|
||||
const uploadMock = jest.fn()
|
||||
|
||||
jest.mock("fs", () => {
|
||||
const originalFs = jest.requireActual("fs")
|
||||
return {
|
||||
readFileSync: () => fileContents,
|
||||
statSync: () => { return { size: contentLength } }
|
||||
};
|
||||
...originalFs,
|
||||
promises: {},
|
||||
createReadStream: () => fakeReadStream,
|
||||
statSync: () => {
|
||||
return { size: contentLength }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('ArtifactUploader', () => {
|
||||
it('uploads artifacts', () => {
|
||||
const uploader = createUploader()
|
||||
|
||||
uploader.uploadArtifacts(artifacts, url)
|
||||
|
||||
expect(uploadMock).toBeCalledTimes(2)
|
||||
expect(uploadMock)
|
||||
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
|
||||
expect(uploadMock)
|
||||
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2')
|
||||
describe("ArtifactUploader", () => {
|
||||
beforeEach(() => {
|
||||
deleteMock.mockClear()
|
||||
listArtifactsMock.mockClear()
|
||||
uploadMock.mockClear()
|
||||
})
|
||||
|
||||
function createUploader(): GithubArtifactUploader {
|
||||
it("abort when upload failed with non-5xx response", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact(401, 2)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("abort when upload failed with 5xx response after 3 attempts", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact(500, 4)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(uploadMock).toHaveBeenCalledTimes(5)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("replaces all artifacts", async () => {
|
||||
mockDeleteSuccess()
|
||||
mockListWithAssets()
|
||||
mockUploadArtifact()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(2)
|
||||
expect(deleteMock).toHaveBeenCalledWith(1)
|
||||
expect(deleteMock).toHaveBeenCalledWith(2)
|
||||
})
|
||||
|
||||
it("replaces no artifacts when previous asset list empty", async () => {
|
||||
mockDeleteSuccess()
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("retry when upload failed with 5xx response", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadArtifact(500, 2)
|
||||
const uploader = createUploader(true)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(uploadMock).toHaveBeenCalledTimes(4)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("throws upload error when replacesExistingArtifacts is true", async () => {
|
||||
mockListWithoutAssets()
|
||||
mockUploadError()
|
||||
const uploader = createUploader(true, true)
|
||||
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
} catch (error) {
|
||||
expect(error).toEqual(Error("Failed to upload artifact art1. error."))
|
||||
}
|
||||
})
|
||||
|
||||
it("throws error from replace", async () => {
|
||||
mockDeleteError()
|
||||
mockListWithAssets()
|
||||
mockUploadArtifact()
|
||||
const uploader = createUploader(true)
|
||||
|
||||
expect.hasAssertions()
|
||||
try {
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
} catch (error) {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
})
|
||||
|
||||
it("updates all artifacts, delete none", async () => {
|
||||
mockDeleteError()
|
||||
mockListWithAssets()
|
||||
mockUploadArtifact()
|
||||
const uploader = createUploader(false)
|
||||
|
||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||
|
||||
expect(deleteMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
function createUploader(replaces: boolean, throws = false): GithubArtifactUploader {
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
return {
|
||||
getByTag: jest.fn(),
|
||||
create: jest.fn(),
|
||||
deleteArtifact: deleteMock,
|
||||
getByTag: jest.fn(),
|
||||
listArtifactsForRelease: listArtifactsMock,
|
||||
listReleases: jest.fn(),
|
||||
update: jest.fn(),
|
||||
uploadArtifact: uploadMock
|
||||
uploadArtifact: uploadMock,
|
||||
generateReleaseNotes: jest.fn(),
|
||||
}
|
||||
})
|
||||
return new GithubArtifactUploader(new MockReleases())
|
||||
return new GithubArtifactUploader(new MockReleases(), replaces, throws)
|
||||
}
|
||||
});
|
||||
|
||||
function mockDeleteError(): any {
|
||||
deleteMock.mockRejectedValue("error")
|
||||
}
|
||||
|
||||
function mockDeleteSuccess(): any {
|
||||
deleteMock.mockResolvedValue({})
|
||||
}
|
||||
|
||||
function mockListWithAssets() {
|
||||
listArtifactsMock.mockResolvedValue([
|
||||
{
|
||||
name: "art1",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
name: "art2",
|
||||
id: 2,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
function mockListWithoutAssets() {
|
||||
listArtifactsMock.mockResolvedValue([])
|
||||
}
|
||||
|
||||
function mockUploadArtifact(status = 200, failures = 0) {
|
||||
const error = new RequestError(`HTTP ${status}`, status, {
|
||||
headers: {},
|
||||
request: { method: "GET", url: "", headers: {} },
|
||||
})
|
||||
for (let index = 0; index < failures; index++) {
|
||||
uploadMock.mockRejectedValueOnce(error)
|
||||
}
|
||||
uploadMock.mockResolvedValue({})
|
||||
}
|
||||
|
||||
function mockUploadError() {
|
||||
uploadMock.mockRejectedValue({
|
||||
message: "error",
|
||||
status: 502,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { ErrorMessage } from "../src/ErrorMessage"
|
||||
|
||||
describe('ErrorMessage', () => {
|
||||
|
||||
describe('has error with code', () => {
|
||||
const error = {
|
||||
message: 'something bad happened',
|
||||
errors: [
|
||||
{
|
||||
code: 'missing',
|
||||
resource: 'release'
|
||||
},
|
||||
{
|
||||
code: 'already_exists',
|
||||
resource: 'release'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
it('does not have error', ()=> {
|
||||
const errorMessage = new ErrorMessage(error)
|
||||
expect(errorMessage.hasErrorWithCode('missing_field')).toBeFalsy()
|
||||
})
|
||||
|
||||
it('has error', ()=> {
|
||||
const errorMessage = new ErrorMessage(error)
|
||||
expect(errorMessage.hasErrorWithCode('missing')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates message with errors', () => {
|
||||
const resource = "release"
|
||||
const error = {
|
||||
message: 'something bad happened',
|
||||
errors: [
|
||||
{
|
||||
code: 'missing',
|
||||
resource: 'release'
|
||||
},
|
||||
{
|
||||
code: 'already_exists',
|
||||
resource: 'release'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const errorMessage = new ErrorMessage(error)
|
||||
|
||||
const expectedString = "something bad happened\nErrors:\n- release does not exist.\n- release already exists."
|
||||
expect(errorMessage.toString()).toBe(expectedString)
|
||||
})
|
||||
|
||||
it('generates message without errors', () => {
|
||||
const error = {
|
||||
message: 'something bad happened'
|
||||
}
|
||||
|
||||
const errorMessage = new ErrorMessage(error)
|
||||
|
||||
expect(errorMessage.toString()).toBe('something bad happened')
|
||||
})
|
||||
})
|
||||
@@ -1,99 +1,97 @@
|
||||
import { GithubError } from "../src/GithubError"
|
||||
|
||||
describe('GithubError', () => {
|
||||
|
||||
it('provides error code', () => {
|
||||
describe("ErrorMessage", () => {
|
||||
describe("has error with code", () => {
|
||||
const error = {
|
||||
code: "missing"
|
||||
message: "something bad happened",
|
||||
errors: [
|
||||
{
|
||||
code: "missing",
|
||||
resource: "release",
|
||||
},
|
||||
{
|
||||
code: "already_exists",
|
||||
resource: "release",
|
||||
},
|
||||
],
|
||||
status: 422,
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
|
||||
expect(githubError.code).toBe('missing')
|
||||
})
|
||||
|
||||
it('generates missing resource error message', () => {
|
||||
const resource = "release"
|
||||
const error = {
|
||||
code: "missing",
|
||||
resource: resource
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
const message = githubError.toString()
|
||||
|
||||
expect(message).toBe(`${resource} does not exist.`)
|
||||
})
|
||||
|
||||
it('generates missing field error message', () => {
|
||||
const resource = "release"
|
||||
const field = "body"
|
||||
const error = {
|
||||
code: "missing_field",
|
||||
field: field,
|
||||
resource: resource
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
const message = githubError.toString()
|
||||
|
||||
expect(message).toBe(`The ${field} field on ${resource} is missing.`)
|
||||
})
|
||||
|
||||
it('generates invalid field error message', () => {
|
||||
const resource = "release"
|
||||
const field = "body"
|
||||
const error = {
|
||||
code: "invalid",
|
||||
field: field,
|
||||
resource: resource
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
const message = githubError.toString()
|
||||
|
||||
expect(message).toBe(`The ${field} field on ${resource} is an invalid format.`)
|
||||
})
|
||||
|
||||
it('generates resource already exists error message', () => {
|
||||
const resource = "release"
|
||||
const field = "body"
|
||||
const error = {
|
||||
code: "already_exists",
|
||||
resource: resource
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
const message = githubError.toString()
|
||||
|
||||
expect(message).toBe(`${resource} already exists.`)
|
||||
})
|
||||
|
||||
describe('generates custom error message', () => {
|
||||
it('with documentation url', () => {
|
||||
const url = "https://api.example.com"
|
||||
const error = {
|
||||
code: "custom",
|
||||
message: "foo",
|
||||
documentation_url: url
|
||||
}
|
||||
|
||||
it("does not have error", () => {
|
||||
const githubError = new GithubError(error)
|
||||
const message = githubError.toString()
|
||||
|
||||
expect(message).toBe(`foo\nPlease see ${url}.`)
|
||||
expect(githubError.hasErrorWithCode("missing_field")).toBeFalsy()
|
||||
})
|
||||
|
||||
it('without documentation url', () => {
|
||||
const error = {
|
||||
code: "custom",
|
||||
message: "foo"
|
||||
}
|
||||
|
||||
it("has error", () => {
|
||||
const githubError = new GithubError(error)
|
||||
const message = githubError.toString()
|
||||
|
||||
expect(message).toBe('foo')
|
||||
expect(githubError.hasErrorWithCode("missing")).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("has error with remediation", () => {
|
||||
it("provides remediation with 404 without errors", () => {
|
||||
const error = { status: 404, message: "message" }
|
||||
const githubError = new GithubError(error)
|
||||
expect(githubError.toString()).toBe(
|
||||
"Error 404: message\nMake sure your github token has access to the repo and has permission to author releases"
|
||||
)
|
||||
})
|
||||
|
||||
it("provides remediation with 404 with errors", () => {
|
||||
const error = {
|
||||
message: "message",
|
||||
errors: [
|
||||
{
|
||||
code: "missing",
|
||||
resource: "release",
|
||||
},
|
||||
],
|
||||
status: 404,
|
||||
}
|
||||
const githubError = new GithubError(error)
|
||||
expect(githubError.toString()).toBe(
|
||||
"Error 404: message\nErrors:\n- release does not exist.\nMake sure your github token has access to the repo and has permission to author releases"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("generates message with errors", () => {
|
||||
const error = {
|
||||
message: "something bad happened",
|
||||
errors: [
|
||||
{
|
||||
code: "missing",
|
||||
resource: "release",
|
||||
},
|
||||
{
|
||||
code: "already_exists",
|
||||
resource: "release",
|
||||
},
|
||||
],
|
||||
status: 422,
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
|
||||
const expectedString =
|
||||
"Error 422: something bad happened\nErrors:\n- release does not exist.\n- release already exists."
|
||||
expect(githubError.toString()).toBe(expectedString)
|
||||
})
|
||||
|
||||
it("generates message without errors", () => {
|
||||
const error = {
|
||||
message: "something bad happened",
|
||||
status: 422,
|
||||
}
|
||||
|
||||
const githubError = new GithubError(error)
|
||||
|
||||
expect(githubError.toString()).toBe("Error 422: something bad happened")
|
||||
})
|
||||
|
||||
it("provides error status", () => {
|
||||
const error = { status: 404 }
|
||||
const githubError = new GithubError(error)
|
||||
expect(githubError.status).toBe(404)
|
||||
})
|
||||
})
|
||||
|
||||
97
__tests__/GithubErrorDetail.test.ts
Normal file
97
__tests__/GithubErrorDetail.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { GithubErrorDetail } from "../src/GithubErrorDetail"
|
||||
|
||||
describe("GithubErrorDetail", () => {
|
||||
it("provides error code", () => {
|
||||
const error = {
|
||||
code: "missing",
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
|
||||
expect(detail.code).toBe("missing")
|
||||
})
|
||||
|
||||
it("generates missing resource error message", () => {
|
||||
const resource = "release"
|
||||
const error = {
|
||||
code: "missing",
|
||||
resource: resource,
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
const message = detail.toString()
|
||||
|
||||
expect(message).toBe(`${resource} does not exist.`)
|
||||
})
|
||||
|
||||
it("generates missing field error message", () => {
|
||||
const resource = "release"
|
||||
const field = "body"
|
||||
const error = {
|
||||
code: "missing_field",
|
||||
field: field,
|
||||
resource: resource,
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
const message = detail.toString()
|
||||
|
||||
expect(message).toBe(`The ${field} field on ${resource} is missing.`)
|
||||
})
|
||||
|
||||
it("generates invalid field error message", () => {
|
||||
const resource = "release"
|
||||
const field = "body"
|
||||
const error = {
|
||||
code: "invalid",
|
||||
field: field,
|
||||
resource: resource,
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
const message = detail.toString()
|
||||
|
||||
expect(message).toBe(`The ${field} field on ${resource} is an invalid format.`)
|
||||
})
|
||||
|
||||
it("generates resource already exists error message", () => {
|
||||
const resource = "release"
|
||||
const error = {
|
||||
code: "already_exists",
|
||||
resource: resource,
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
const message = detail.toString()
|
||||
|
||||
expect(message).toBe(`${resource} already exists.`)
|
||||
})
|
||||
|
||||
describe("generates custom error message", () => {
|
||||
it("with documentation url", () => {
|
||||
const url = "https://api.example.com"
|
||||
const error = {
|
||||
code: "custom",
|
||||
message: "foo",
|
||||
documentation_url: url,
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
const message = detail.toString()
|
||||
|
||||
expect(message).toBe(`foo\nPlease see ${url}.`)
|
||||
})
|
||||
|
||||
it("without documentation url", () => {
|
||||
const error = {
|
||||
code: "custom",
|
||||
message: "foo",
|
||||
}
|
||||
|
||||
const detail = new GithubErrorDetail(error)
|
||||
const message = detail.toString()
|
||||
|
||||
expect(message).toBe("foo")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,165 +1,444 @@
|
||||
const mockGetInput = jest.fn();
|
||||
const mockGetInput = jest.fn()
|
||||
const mockGetBooleanInput = jest.fn()
|
||||
const mockGlob = jest.fn()
|
||||
const mockReadFileSync = jest.fn();
|
||||
const mockStatSync = jest.fn();
|
||||
const mockReadFileSync = jest.fn()
|
||||
const mockStatSync = jest.fn()
|
||||
|
||||
import { Artifact } from "../src/Artifact";
|
||||
import { ArtifactGlobber } from "../src/ArtifactGlobber";
|
||||
import { Context } from "@actions/github/lib/context";
|
||||
import { Inputs, CoreInputs } from "../src/Inputs";
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import { ArtifactGlobber } from "../src/ArtifactGlobber"
|
||||
import { Context } from "@actions/github/lib/context"
|
||||
import { Inputs, CoreInputs } from "../src/Inputs"
|
||||
|
||||
const artifacts = [
|
||||
new Artifact('a/art1'),
|
||||
new Artifact('b/art2')
|
||||
]
|
||||
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
|
||||
|
||||
jest.mock('@actions/core', () => {
|
||||
return { getInput: mockGetInput };
|
||||
})
|
||||
|
||||
jest.mock('fs', () => {
|
||||
jest.mock("@actions/core", () => {
|
||||
return {
|
||||
readFileSync: mockReadFileSync,
|
||||
statSync: mockStatSync
|
||||
};
|
||||
getInput: mockGetInput,
|
||||
getBooleanInput: mockGetBooleanInput,
|
||||
}
|
||||
})
|
||||
|
||||
describe('Inputs', () => {
|
||||
let context: Context;
|
||||
let inputs: Inputs;
|
||||
jest.mock("fs", () => {
|
||||
// existsSync is used by Context's constructor
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
return {
|
||||
existsSync: () => {
|
||||
return false
|
||||
},
|
||||
readFileSync: mockReadFileSync,
|
||||
statSync: mockStatSync,
|
||||
}
|
||||
})
|
||||
|
||||
describe("Inputs", () => {
|
||||
let context: Context
|
||||
let inputs: Inputs
|
||||
beforeEach(() => {
|
||||
mockGetInput.mockReset()
|
||||
context = new Context()
|
||||
inputs = new CoreInputs(createGlobber(), context)
|
||||
})
|
||||
|
||||
it('returns targetCommit', () => {
|
||||
mockGetInput.mockReturnValue('42')
|
||||
expect(inputs.commit).toBe('42')
|
||||
describe("commit", () => {
|
||||
it("returns commit", () => {
|
||||
mockGetInput.mockReturnValueOnce("commit")
|
||||
expect(inputs.commit).toBe("commit")
|
||||
})
|
||||
|
||||
it("returns undefined when omitted", () => {
|
||||
mockGetInput.mockReturnValueOnce("")
|
||||
expect(inputs.commit).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('returns token', () => {
|
||||
mockGetInput.mockReturnValue('42')
|
||||
expect(inputs.token).toBe('42')
|
||||
it("returns token", () => {
|
||||
mockGetInput.mockReturnValue("42")
|
||||
expect(inputs.token).toBe("42")
|
||||
})
|
||||
|
||||
describe('allowsUpdates', () => {
|
||||
it('returns false', () => {
|
||||
describe("allowsUpdates", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.allowUpdates).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true', () => {
|
||||
mockGetInput.mockReturnValue('true')
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.allowUpdates).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('artifacts', () => {
|
||||
it('returns empty artifacts', () => {
|
||||
mockGetInput.mockReturnValueOnce('')
|
||||
.mockReturnValueOnce('')
|
||||
|
||||
describe("artifactErrorsFailBuild", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.artifactErrorsFailBuild).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.artifactErrorsFailBuild).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("artifacts", () => {
|
||||
it("globber told to throw errors", () => {
|
||||
mockGetInput.mockReturnValueOnce("art1").mockReturnValueOnce("contentType").mockReturnValueOnce("true")
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toHaveBeenCalledTimes(1)
|
||||
expect(mockGlob).toHaveBeenCalledWith("art1", "contentType", true)
|
||||
})
|
||||
|
||||
it("returns empty artifacts", () => {
|
||||
mockGetInput.mockReturnValueOnce("").mockReturnValueOnce("")
|
||||
|
||||
expect(inputs.artifacts).toEqual([])
|
||||
expect(mockGlob).toBeCalledTimes(0)
|
||||
expect(mockGlob).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('returns input.artifacts', () => {
|
||||
mockGetInput.mockReturnValueOnce('art1')
|
||||
.mockReturnValueOnce('contentType')
|
||||
|
||||
it("returns input.artifacts", () => {
|
||||
mockGetInput.mockReturnValueOnce("art1").mockReturnValueOnce("contentType").mockReturnValueOnce("false")
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toBeCalledTimes(1)
|
||||
expect(mockGlob).toBeCalledWith('art1', 'contentType')
|
||||
expect(mockGlob).toHaveBeenCalledTimes(1)
|
||||
expect(mockGlob).toHaveBeenCalledWith("art1", "contentType", false)
|
||||
})
|
||||
|
||||
it('returns input.artifacts with default contentType', () => {
|
||||
mockGetInput.mockReturnValueOnce('art1')
|
||||
|
||||
it("returns input.artifacts with default contentType", () => {
|
||||
mockGetInput.mockReturnValueOnce("art1").mockReturnValueOnce("").mockReturnValueOnce("false")
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toBeCalledTimes(1)
|
||||
expect(mockGlob).toBeCalledWith('art1', 'raw')
|
||||
expect(mockGlob).toHaveBeenCalledTimes(1)
|
||||
expect(mockGlob).toHaveBeenCalledWith("art1", "raw", false)
|
||||
})
|
||||
|
||||
it('returns input.artifact', () => {
|
||||
mockGetInput.mockReturnValueOnce('')
|
||||
.mockReturnValueOnce('art2')
|
||||
.mockReturnValueOnce('contentType')
|
||||
|
||||
|
||||
it("returns input.artifact", () => {
|
||||
mockGetInput
|
||||
.mockReturnValueOnce("")
|
||||
.mockReturnValueOnce("art2")
|
||||
.mockReturnValueOnce("contentType")
|
||||
.mockReturnValueOnce("false")
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toBeCalledTimes(1)
|
||||
expect(mockGlob).toBeCalledWith('art2', 'contentType')
|
||||
expect(mockGlob).toHaveBeenCalledTimes(1)
|
||||
expect(mockGlob).toHaveBeenCalledWith("art2", "contentType", false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('body', () => {
|
||||
it('returns input body', () => {
|
||||
mockGetInput.mockReturnValue('body')
|
||||
expect(inputs.body).toBe('body')
|
||||
describe("createdDraft", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.createdDraft).toBe(false)
|
||||
})
|
||||
|
||||
it('returns body file contents', () => {
|
||||
mockGetInput.mockReturnValueOnce('').mockReturnValueOnce('a/path')
|
||||
mockReadFileSync.mockReturnValue('file')
|
||||
|
||||
expect(inputs.body).toBe('file')
|
||||
})
|
||||
|
||||
it('returns empty', () => {
|
||||
expect(inputs.body).toBe('')
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.createdDraft).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('draft', () => {
|
||||
it('returns false', () => {
|
||||
expect(inputs.draft).toBe(false)
|
||||
describe("createdReleaseBody", () => {
|
||||
it("returns input body", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("body")
|
||||
expect(inputs.createdReleaseBody).toBe("body")
|
||||
})
|
||||
|
||||
it('returns true', () => {
|
||||
mockGetInput.mockReturnValue('true')
|
||||
expect(inputs.draft).toBe(true)
|
||||
it("returns body file contents", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("").mockReturnValueOnce("a/path")
|
||||
mockReadFileSync.mockReturnValue("file")
|
||||
|
||||
expect(inputs.createdReleaseBody).toBe("file")
|
||||
})
|
||||
|
||||
it("returns empty", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("").mockReturnValueOnce("")
|
||||
expect(inputs.createdReleaseBody).toBe("")
|
||||
})
|
||||
|
||||
it("returns undefined when omitted", () => {
|
||||
mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("body")
|
||||
expect(inputs.createdReleaseBody).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('name', () => {
|
||||
it('returns input name', () => {
|
||||
mockGetInput.mockReturnValue('name')
|
||||
expect(inputs.name).toBe('name')
|
||||
describe("createdReleaseName", () => {
|
||||
it("returns input name", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("name")
|
||||
expect(inputs.createdReleaseName).toBe("name")
|
||||
})
|
||||
|
||||
it('returns tag', () => {
|
||||
mockGetInput.mockReturnValue('')
|
||||
context.ref = 'refs/tags/sha-tag'
|
||||
expect(inputs.name).toBe('sha-tag')
|
||||
it("returns undefined when omitted", () => {
|
||||
mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("name")
|
||||
expect(inputs.createdReleaseName).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns tag", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("")
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.createdReleaseName).toBe("sha-tag")
|
||||
})
|
||||
})
|
||||
|
||||
describe('tag', () => {
|
||||
it('returns input tag', () => {
|
||||
mockGetInput.mockReturnValue('tag')
|
||||
expect(inputs.tag).toBe('tag')
|
||||
describe("discussionCategory", () => {
|
||||
it("returns category", () => {
|
||||
mockGetInput.mockReturnValue("Release")
|
||||
expect(inputs.discussionCategory).toBe("Release")
|
||||
})
|
||||
it('returns context sha when input is empty', () => {
|
||||
mockGetInput.mockReturnValue('')
|
||||
context.ref = 'refs/tags/sha-tag'
|
||||
expect(inputs.tag).toBe('sha-tag')
|
||||
|
||||
it("returns undefined", () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.discussionCategory).toBe(undefined)
|
||||
})
|
||||
it('returns context sha when input is null', () => {
|
||||
})
|
||||
|
||||
describe("generateReleaseNotes", () => {
|
||||
it("returns returns true", function () {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.generateReleaseNotes).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when omitted", function () {
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.generateReleaseNotes).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("makeLatest", () => {
|
||||
it("returns legacy", () => {
|
||||
mockGetInput.mockReturnValueOnce("legacy")
|
||||
expect(inputs.makeLatest).toBe("legacy")
|
||||
})
|
||||
|
||||
it("returns false", () => {
|
||||
mockGetInput.mockReturnValueOnce("false")
|
||||
expect(inputs.makeLatest).toBe("false")
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValueOnce("true")
|
||||
expect(inputs.makeLatest).toBe("true")
|
||||
})
|
||||
|
||||
it("returns undefined", () => {
|
||||
mockGetInput.mockReturnValueOnce("something_else")
|
||||
expect(inputs.makeLatest).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe("owner", () => {
|
||||
it("returns owner from context", function () {
|
||||
process.env.GITHUB_REPOSITORY = "owner/repo"
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.owner).toBe("owner")
|
||||
})
|
||||
it("returns owner from inputs", function () {
|
||||
mockGetInput.mockReturnValue("owner")
|
||||
expect(inputs.owner).toBe("owner")
|
||||
})
|
||||
})
|
||||
|
||||
describe("createdPrerelase", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.createdPrerelease).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.createdPrerelease).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("replacesArtifacts", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.replacesArtifacts).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.replacesArtifacts).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("removeArtifacts", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.removeArtifacts).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.removeArtifacts).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("repo", () => {
|
||||
it("returns repo from context", function () {
|
||||
process.env.GITHUB_REPOSITORY = "owner/repo"
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.repo).toBe("repo")
|
||||
})
|
||||
it("returns repo from inputs", function () {
|
||||
mockGetInput.mockReturnValue("repo")
|
||||
expect(inputs.repo).toBe("repo")
|
||||
})
|
||||
})
|
||||
|
||||
describe("skipIfReleaseExists", () => {
|
||||
it("returns false", () => {
|
||||
mockGetBooleanInput.mockReturnValue(false)
|
||||
expect(inputs.skipIfReleaseExists).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetBooleanInput.mockReturnValue(true)
|
||||
expect(inputs.skipIfReleaseExists).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("tag", () => {
|
||||
it("returns input tag", () => {
|
||||
mockGetInput.mockReturnValue("tag")
|
||||
expect(inputs.tag).toBe("tag")
|
||||
})
|
||||
it("returns context sha when input is empty", () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.tag).toBe("sha-tag")
|
||||
})
|
||||
it("returns context sha when input is null", () => {
|
||||
mockGetInput.mockReturnValue(null)
|
||||
context.ref = 'refs/tags/sha-tag'
|
||||
expect(inputs.tag).toBe('sha-tag')
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.tag).toBe("sha-tag")
|
||||
})
|
||||
it('throws if no tag', () => {
|
||||
it("throws if no tag", () => {
|
||||
context.ref = ""
|
||||
expect(() => inputs.tag).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("updatedDraft", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.updatedDraft).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValue("true")
|
||||
expect(inputs.updatedDraft).toBe(true)
|
||||
})
|
||||
|
||||
it("returns true when omitted is blank", () => {
|
||||
mockGetInput.mockReturnValueOnce("").mockReturnValue("true")
|
||||
expect(inputs.updatedDraft).toBe(true)
|
||||
})
|
||||
|
||||
it("returns undefined when omitted for update", () => {
|
||||
mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("true")
|
||||
expect(inputs.updatedDraft).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("updatedReleaseBody", () => {
|
||||
it("returns input body", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false").mockReturnValueOnce("body")
|
||||
expect(inputs.updatedReleaseBody).toBe("body")
|
||||
})
|
||||
|
||||
it("returns body file contents", () => {
|
||||
mockGetInput
|
||||
.mockReturnValueOnce("false")
|
||||
.mockReturnValueOnce("false")
|
||||
.mockReturnValueOnce("")
|
||||
.mockReturnValueOnce("a/path")
|
||||
mockReadFileSync.mockReturnValue("file")
|
||||
|
||||
expect(inputs.updatedReleaseBody).toBe("file")
|
||||
})
|
||||
|
||||
it("returns empty", () => {
|
||||
mockGetInput
|
||||
.mockReturnValueOnce("false")
|
||||
.mockReturnValueOnce("false")
|
||||
.mockReturnValueOnce("")
|
||||
.mockReturnValueOnce("")
|
||||
expect(inputs.updatedReleaseBody).toBe("")
|
||||
})
|
||||
|
||||
it("returns undefined when omitted", () => {
|
||||
mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("false").mockReturnValueOnce("body")
|
||||
expect(inputs.updatedReleaseBody).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns undefined when omitted for update", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("true").mockReturnValueOnce("body")
|
||||
expect(inputs.updatedReleaseBody).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("updatedReleaseName", () => {
|
||||
it("returns input name", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false").mockReturnValueOnce("name")
|
||||
expect(inputs.updatedReleaseName).toBe("name")
|
||||
})
|
||||
|
||||
it("returns undefined when omitted", () => {
|
||||
mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("false").mockReturnValueOnce("name")
|
||||
expect(inputs.updatedReleaseName).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns undefined when omitted for update", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("true").mockReturnValueOnce("name")
|
||||
expect(inputs.updatedReleaseName).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns tag", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false").mockReturnValueOnce("")
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.updatedReleaseName).toBe("sha-tag")
|
||||
})
|
||||
})
|
||||
|
||||
describe("updatedPrerelease", () => {
|
||||
it("returns false", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false")
|
||||
expect(inputs.updatedPrerelease).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("true")
|
||||
expect(inputs.updatedPrerelease).toBe(true)
|
||||
})
|
||||
|
||||
it("returns undefined when omitted for update", () => {
|
||||
mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("false")
|
||||
expect(inputs.updatedPrerelease).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateOnlyUnreleased", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.updateOnlyUnreleased).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValueOnce("true")
|
||||
expect(inputs.updateOnlyUnreleased).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("omitBodyDuringUpdate", () => {
|
||||
it("returns false", () => {
|
||||
expect(inputs.omitBodyDuringUpdate).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true", () => {
|
||||
mockGetInput.mockReturnValueOnce("true")
|
||||
expect(inputs.omitBodyDuringUpdate).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
function createGlobber(): ArtifactGlobber {
|
||||
const MockGlobber = jest.fn<ArtifactGlobber, any>(() => {
|
||||
return {
|
||||
globArtifactString: mockGlob
|
||||
globArtifactString: mockGlob,
|
||||
}
|
||||
})
|
||||
mockGlob.mockImplementation(() => artifacts)
|
||||
return new MockGlobber()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
88
__tests__/Integration.test.ts
Normal file
88
__tests__/Integration.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Action } from "../src/Action"
|
||||
import * as github from "@actions/github"
|
||||
import { Inputs } from "../src/Inputs"
|
||||
import { GithubReleases, ReleaseData } from "../src/Releases"
|
||||
import { GithubArtifactUploader } from "../src/ArtifactUploader"
|
||||
import * as path from "path"
|
||||
import { FileArtifactGlobber } from "../src/ArtifactGlobber"
|
||||
import { Outputs } from "../src/Outputs"
|
||||
import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer"
|
||||
import { ReleaseActionSkipper } from "../src/ActionSkipper"
|
||||
|
||||
// This test is currently intended to be manually run during development. To run:
|
||||
// - Make sure you have an environment variable named GITHUB_TOKEN assigned to your token
|
||||
// - Remove skip from the test below
|
||||
describe.skip("Integration Test", () => {
|
||||
let action: Action
|
||||
|
||||
beforeEach(() => {
|
||||
const token = getToken()
|
||||
const git = github.getOctokit(token)
|
||||
|
||||
const inputs = getInputs()
|
||||
const outputs = getOutputs()
|
||||
const releases = new GithubReleases(inputs, git)
|
||||
const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild)
|
||||
const artifactDestroyer = new GithubArtifactDestroyer(releases)
|
||||
const actionSkipper = new ReleaseActionSkipper(inputs.skipIfReleaseExists, releases, inputs.tag)
|
||||
|
||||
action = new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper)
|
||||
})
|
||||
|
||||
it("Performs action", async () => {
|
||||
await action.perform()
|
||||
})
|
||||
|
||||
function getInputs(): Inputs {
|
||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||
return {
|
||||
allowUpdates: true,
|
||||
artifactErrorsFailBuild: false,
|
||||
artifacts: artifacts(),
|
||||
createdDraft: false,
|
||||
createdReleaseBody: "This release was generated by release-action's integration test",
|
||||
createdReleaseName: "Releases Action Integration Test",
|
||||
commit: undefined,
|
||||
discussionCategory: "Release",
|
||||
generateReleaseNotes: true,
|
||||
omitBodyDuringUpdate: false,
|
||||
owner: "ncipollo",
|
||||
createdPrerelease: false,
|
||||
replacesArtifacts: true,
|
||||
removeArtifacts: false,
|
||||
repo: "actions-playground",
|
||||
skipIfReleaseExists: false,
|
||||
tag: "release-action-test",
|
||||
token: getToken(),
|
||||
updatedDraft: false,
|
||||
updatedReleaseBody: "This release was updated by release-action's integration test",
|
||||
updatedReleaseName: "Releases Action Integration Test",
|
||||
updatedPrerelease: false,
|
||||
updateOnlyUnreleased: false,
|
||||
}
|
||||
})
|
||||
return new MockInputs()
|
||||
}
|
||||
|
||||
function getOutputs(): Outputs {
|
||||
const MockOutputs = jest.fn<Outputs, any>(() => {
|
||||
return {
|
||||
applyReleaseData(releaseData: ReleaseData) {
|
||||
console.log(`Release Data: ${releaseData}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
return new MockOutputs()
|
||||
}
|
||||
|
||||
function artifacts() {
|
||||
const globber = new FileArtifactGlobber()
|
||||
const artifactPath = path.join(__dirname, "Integration.test.ts")
|
||||
const artifactString = `~/Desktop,~/Desktop/test.txt,blarg.tx, ${artifactPath}`
|
||||
return globber.globArtifactString(artifactString, "raw", false)
|
||||
}
|
||||
|
||||
function getToken(): string {
|
||||
return process.env.GITHUB_TOKEN ?? ""
|
||||
}
|
||||
})
|
||||
51
__tests__/Outputs.test.ts
Normal file
51
__tests__/Outputs.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
const mockSetOutput = jest.fn()
|
||||
|
||||
import { CoreOutputs, Outputs } from "../src/Outputs"
|
||||
import { ReleaseData } from "../src/Releases"
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return { setOutput: mockSetOutput }
|
||||
})
|
||||
|
||||
const TEST_URLS = {
|
||||
HTML_URL: "https://api.example.com/assets",
|
||||
UPLOAD_URL: "https://api.example.com",
|
||||
TARBALL_URL: "https://api.example.com/tarball",
|
||||
ZIPBALL_URL: "https://api.example.com/zipball",
|
||||
} as const
|
||||
|
||||
describe("Outputs", () => {
|
||||
let outputs: Outputs
|
||||
let releaseData: ReleaseData
|
||||
|
||||
beforeEach(() => {
|
||||
outputs = new CoreOutputs()
|
||||
releaseData = {
|
||||
id: 1,
|
||||
html_url: TEST_URLS.HTML_URL,
|
||||
upload_url: TEST_URLS.UPLOAD_URL,
|
||||
tarball_url: TEST_URLS.TARBALL_URL,
|
||||
zipball_url: TEST_URLS.ZIPBALL_URL,
|
||||
}
|
||||
})
|
||||
|
||||
it("Applies the release data to the action output", () => {
|
||||
outputs.applyReleaseData(releaseData)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("id", releaseData.id)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("html_url", releaseData.html_url)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("upload_url", releaseData.upload_url)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", releaseData.tarball_url)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", releaseData.zipball_url)
|
||||
})
|
||||
|
||||
it("Handles null tarball_url and zipball_url", () => {
|
||||
const releaseDataWithNulls = {
|
||||
...releaseData,
|
||||
tarball_url: null,
|
||||
zipball_url: null,
|
||||
}
|
||||
outputs.applyReleaseData(releaseDataWithNulls)
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", "")
|
||||
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", "")
|
||||
})
|
||||
})
|
||||
74
__tests__/ReleaseValidator.test.ts
Normal file
74
__tests__/ReleaseValidator.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { ReleaseValidator } from "../src/ReleaseValidator"
|
||||
|
||||
describe("validateReleaseUpdate", () => {
|
||||
describe("updateOnlyUnreleased is disabled", () => {
|
||||
const validator = new ReleaseValidator(false)
|
||||
it("should not throw", () => {
|
||||
const releaseResponse = {
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
name: "Name",
|
||||
}
|
||||
expect(() => {
|
||||
validator.validateReleaseUpdate(releaseResponse)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
describe("updateOnlyUnreleased is enabled", () => {
|
||||
const validator = new ReleaseValidator(true)
|
||||
it("should throw if neither draft or prerelease are enabled", () => {
|
||||
const releaseResponse = {
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
name: "Name",
|
||||
}
|
||||
expect(() => {
|
||||
validator.validateReleaseUpdate(releaseResponse)
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
it("should not throw if draft is enabled", () => {
|
||||
const releaseResponse = {
|
||||
draft: true,
|
||||
prerelease: false,
|
||||
name: "Name",
|
||||
}
|
||||
expect(() => {
|
||||
validator.validateReleaseUpdate(releaseResponse)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it("should not throw if prerelease is enabled", () => {
|
||||
const releaseResponse = {
|
||||
draft: false,
|
||||
prerelease: true,
|
||||
name: "Name",
|
||||
}
|
||||
expect(() => {
|
||||
validator.validateReleaseUpdate(releaseResponse)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it("should not throw if draft & prerelease is enabled", () => {
|
||||
const releaseResponse = {
|
||||
draft: true,
|
||||
prerelease: true,
|
||||
name: "Name",
|
||||
}
|
||||
expect(() => {
|
||||
validator.validateReleaseUpdate(releaseResponse)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it("should default error message release name to release", () => {
|
||||
const releaseResponse = {
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
name: null,
|
||||
}
|
||||
expect(() => {
|
||||
validator.validateReleaseUpdate(releaseResponse)
|
||||
}).toThrow(`Tried to update "release" which is neither a draft or prerelease. (updateOnlyUnreleased is on)`)
|
||||
})
|
||||
})
|
||||
})
|
||||
100
action.yml
100
action.yml
@@ -2,43 +2,129 @@ name: 'Create Release'
|
||||
description: 'Creates github releases'
|
||||
author: 'Nick Cipollo'
|
||||
inputs:
|
||||
allowUpdates:
|
||||
allowUpdates:
|
||||
description: 'An optional flag which indicates if we should update a release if it already exists. Defaults to false.'
|
||||
required: false
|
||||
default: ''
|
||||
artifactErrorsFailBuild:
|
||||
description: 'An optional flag which indicates if artifact read or upload errors should fail the build.'
|
||||
required: false
|
||||
default: ''
|
||||
artifact:
|
||||
deprecationMessage: Use 'artifacts' instead.
|
||||
description: 'An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs)'
|
||||
required: false
|
||||
default: ''
|
||||
artifacts:
|
||||
description: 'An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs)'
|
||||
default: ''
|
||||
required: false
|
||||
default: ''
|
||||
artifactContentType:
|
||||
description: 'The content type of the artifact. Defaults to raw'
|
||||
required: false
|
||||
default: ''
|
||||
body:
|
||||
description: 'An optional body for the release.'
|
||||
required: false
|
||||
default: ''
|
||||
bodyFile:
|
||||
description: 'An optional body file for the release. This should be the path to the file'
|
||||
default: ''
|
||||
required: false
|
||||
default: ''
|
||||
commit:
|
||||
description: "An optional commit reference. This will be used to create the tag if it does not exist."
|
||||
required: false
|
||||
default: ''
|
||||
discussionCategory:
|
||||
description: "When provided this will generate a discussion of the specified category. The category must exist otherwise this will cause the action to fail. This isn't used with draft releases"
|
||||
required: false
|
||||
default: ''
|
||||
draft:
|
||||
description: "Optionally marks this release as a draft release. Set to true to enable."
|
||||
required: false
|
||||
default: ''
|
||||
generateReleaseNotes:
|
||||
description: 'Indicates if release notes should be automatically generated.'
|
||||
required: false
|
||||
default: 'false'
|
||||
makeLatest:
|
||||
description: 'Indicates if the release should be the "latest" release or not.'
|
||||
required: false
|
||||
default: 'legacy'
|
||||
name:
|
||||
description: 'An optional name for the release. If this is omitted the tag will be used.'
|
||||
required: false
|
||||
default: ''
|
||||
omitBody:
|
||||
description: 'Indicates if the release body should be omitted.'
|
||||
required: false
|
||||
default: 'false'
|
||||
omitBodyDuringUpdate:
|
||||
description: 'Indicates if the release body should be omitted during updates. The body will still be applied for newly created releases. This will preserve the existing body during updates.'
|
||||
required: false
|
||||
default: 'false'
|
||||
omitDraftDuringUpdate:
|
||||
description: 'Indicates if the draft flag should be omitted during updates. The draft flag will still be applied for newly created releases. This will preserve the existing draft state during updates.'
|
||||
required: false
|
||||
default: 'false'
|
||||
omitName:
|
||||
description: 'Indicates if the release name should be omitted.'
|
||||
required: false
|
||||
default: 'false'
|
||||
omitNameDuringUpdate:
|
||||
description: 'Indicates if the release name should be omitted during updates. The name will still be applied for newly created releases. This will preserve the existing name during updates.'
|
||||
required: false
|
||||
default: 'false'
|
||||
omitPrereleaseDuringUpdate:
|
||||
description: 'Indicates if the prerelease flag should be omitted during updates. The prerelease flag will still be applied for newly created releases. This will preserve the existing prerelease state during updates.'
|
||||
required: false
|
||||
default: 'false'
|
||||
owner:
|
||||
description: "Optionally specify the owner of the repo where the release should be generated. Defaults to current repo's owner."
|
||||
required: false
|
||||
default: ''
|
||||
prerelease:
|
||||
description: "Optionally marks this release as prerelease. Set to true to enable."
|
||||
required: false
|
||||
default: ''
|
||||
removeArtifacts:
|
||||
description: 'Indicates if existing release artifacts should be removed, Defaults to false.'
|
||||
required: false
|
||||
default: 'false'
|
||||
replacesArtifacts:
|
||||
description: "Indicates if existing release artifacts should be replaced. Defaults to true."
|
||||
required: false
|
||||
default: 'true'
|
||||
repo:
|
||||
description: "Optionally specify the repo where the release should be generated. Defaults to current repo"
|
||||
required: false
|
||||
default: ''
|
||||
skipIfReleaseExists:
|
||||
description: "When skipIfReleaseExists is enabled the action will be skipped if a non-draft release already exists for the provided tag."
|
||||
required: false
|
||||
default: 'false'
|
||||
tag:
|
||||
description: 'An optional tag for the release. If this is omitted the git ref will be used (if it is a tag).'
|
||||
required: false
|
||||
default: ''
|
||||
token:
|
||||
description: 'The Github token.'
|
||||
required: true
|
||||
default: ''
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
updateOnlyUnreleased:
|
||||
description: "When allowUpdates is enabled, this will fail the action if the release it is updating is not a draft or a prerelease."
|
||||
required: false
|
||||
default: 'false'
|
||||
outputs:
|
||||
id:
|
||||
description: 'The identifier of the created release.'
|
||||
html_url:
|
||||
description: 'The HTML URL of the release.'
|
||||
upload_url:
|
||||
description: 'The URL for uploading assets to the release.'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'lib/Main.js'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'tag'
|
||||
color: 'gray-dark'
|
||||
|
||||
35
biome.json
Normal file
35
biome.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": ["node_modules/**/*", "dist/"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"lineEnding": "lf",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120
|
||||
},
|
||||
"organizeImports": { "enabled": true },
|
||||
"linter": {
|
||||
"enabled": false,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"trailingCommas": "es5",
|
||||
"semicolons": "asNeeded",
|
||||
"indentWidth": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
41163
dist/index.js
vendored
Normal file
41163
dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
786
dist/licenses.txt
vendored
Normal file
786
dist/licenses.txt
vendored
Normal file
@@ -0,0 +1,786 @@
|
||||
@actions/core
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
|
||||
@actions/exec
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
|
||||
@actions/github
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
|
||||
@actions/http-client
|
||||
MIT
|
||||
Actions Http Client for Node.js
|
||||
|
||||
Copyright (c) GitHub, Inc.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@actions/io
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
|
||||
@fastify/busboy
|
||||
MIT
|
||||
Copyright Brian White. All rights reserved.
|
||||
|
||||
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.
|
||||
|
||||
@octokit/auth-token
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@octokit/core
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@octokit/endpoint
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@octokit/graphql
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@octokit/plugin-paginate-rest
|
||||
MIT
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
|
||||
|
||||
@octokit/plugin-rest-endpoint-methods
|
||||
MIT
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
|
||||
|
||||
@octokit/request
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@octokit/request-error
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
balanced-match
|
||||
MIT
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
before-after-hook
|
||||
Apache-2.0
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Gregor Martynus and other contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
brace-expansion
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
deprecation
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Gregor Martynus and contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
glob
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2009-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
lru-cache
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
minimatch
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
minipass
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2017-2023 npm, Inc., Isaac Z. Schlueter, and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
once
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
path-scurry
|
||||
BlueOak-1.0.0
|
||||
# Blue Oak Model License
|
||||
|
||||
Version 1.0.0
|
||||
|
||||
## Purpose
|
||||
|
||||
This license gives everyone as much permission to work with
|
||||
this software as possible, while protecting contributors
|
||||
from liability.
|
||||
|
||||
## Acceptance
|
||||
|
||||
In order to receive this license, you must agree to its
|
||||
rules. The rules of this license are both obligations
|
||||
under that agreement and conditions to your license.
|
||||
You must not do anything with this software that triggers
|
||||
a rule that you cannot or will not follow.
|
||||
|
||||
## Copyright
|
||||
|
||||
Each contributor licenses you to do everything with this
|
||||
software that would otherwise infringe that contributor's
|
||||
copyright in it.
|
||||
|
||||
## Notices
|
||||
|
||||
You must ensure that everyone who gets a copy of
|
||||
any part of this software from you, with or without
|
||||
changes, also gets the text of this license or a link to
|
||||
<https://blueoakcouncil.org/license/1.0.0>.
|
||||
|
||||
## Excuse
|
||||
|
||||
If anyone notifies you in writing that you have not
|
||||
complied with [Notices](#notices), you can keep your
|
||||
license by taking all practical steps to comply within 30
|
||||
days after the notice. If you do not do so, your license
|
||||
ends immediately.
|
||||
|
||||
## Patent
|
||||
|
||||
Each contributor licenses you to do everything with this
|
||||
software that would otherwise infringe any patent claims
|
||||
they can license or become able to license.
|
||||
|
||||
## Reliability
|
||||
|
||||
No contributor can revoke this license.
|
||||
|
||||
## No Liability
|
||||
|
||||
***As far as the law allows, this software comes as is,
|
||||
without any warranty or condition, and no contributor
|
||||
will be liable to anyone for any damages related to this
|
||||
software or this license, under any kind of legal claim.***
|
||||
|
||||
|
||||
tunnel
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Koichi Kobayashi
|
||||
|
||||
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.
|
||||
|
||||
|
||||
undici
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) Matteo Collina and Undici contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
universal-user-agent
|
||||
ISC
|
||||
# [ISC License](https://spdx.org/licenses/ISC)
|
||||
|
||||
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m)
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
untildify
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
wrappy
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
1
dist/sourcemap-register.js
vendored
Normal file
1
dist/sourcemap-register.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testRunner: 'jest-circus/runner',
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ErrorMessage_1 = require("./ErrorMessage");
|
||||
class Action {
|
||||
constructor(inputs, releases, uploader) {
|
||||
this.inputs = inputs;
|
||||
this.releases = releases;
|
||||
this.uploader = uploader;
|
||||
}
|
||||
perform() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const uploadUrl = yield this.createOrUpdateRelease();
|
||||
const artifacts = this.inputs.artifacts;
|
||||
if (artifacts.length > 0) {
|
||||
yield this.uploader.uploadArtifacts(artifacts, uploadUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
createOrUpdateRelease() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
return yield this.createRelease();
|
||||
}
|
||||
catch (error) {
|
||||
if (this.releaseAlreadyExisted(error) && this.inputs.allowUpdates) {
|
||||
return this.updateRelease();
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
createRelease() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const response = yield this.releases.create(this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name);
|
||||
return response.data.upload_url;
|
||||
});
|
||||
}
|
||||
releaseAlreadyExisted(error) {
|
||||
const errorMessage = new ErrorMessage_1.ErrorMessage(error);
|
||||
return errorMessage.hasErrorWithCode('already_exists');
|
||||
}
|
||||
updateRelease() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const getResponse = yield this.releases.getByTag(this.inputs.tag);
|
||||
const id = getResponse.data.id;
|
||||
const response = yield this.releases.update(id, this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name);
|
||||
return response.data.upload_url;
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.Action = Action;
|
||||
@@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const path_1 = require("path");
|
||||
const fs_1 = require("fs");
|
||||
class Artifact {
|
||||
constructor(path, contentType = "raw") {
|
||||
this.path = path;
|
||||
this.name = path_1.basename(path);
|
||||
this.contentType = contentType;
|
||||
}
|
||||
get contentLength() {
|
||||
return fs_1.statSync(this.path).size;
|
||||
}
|
||||
readFile() {
|
||||
return fs_1.readFileSync(this.path);
|
||||
}
|
||||
}
|
||||
exports.Artifact = Artifact;
|
||||
@@ -1,16 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Globber_1 = require("./Globber");
|
||||
const Artifact_1 = require("./Artifact");
|
||||
class FileArtifactGlobber {
|
||||
constructor(globber = new Globber_1.FileGlobber()) {
|
||||
this.globber = globber;
|
||||
}
|
||||
globArtifactString(artifact, contentType) {
|
||||
return artifact.split(',')
|
||||
.map((path) => this.globber.glob(path))
|
||||
.reduce((accumulated, current) => accumulated.concat(current))
|
||||
.map((path) => new Artifact_1.Artifact(path, contentType));
|
||||
}
|
||||
}
|
||||
exports.FileArtifactGlobber = FileArtifactGlobber;
|
||||
@@ -1,24 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class GithubArtifactUploader {
|
||||
constructor(releases) {
|
||||
this.releases = releases;
|
||||
}
|
||||
uploadArtifacts(artifacts, uploadUrl) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
artifacts.forEach((artifact) => __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name);
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.GithubArtifactUploader = GithubArtifactUploader;
|
||||
@@ -1,35 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const GithubError_1 = require("./GithubError");
|
||||
class ErrorMessage {
|
||||
constructor(error) {
|
||||
this.error = error;
|
||||
this.githubErrors = this.generateGithubErrors();
|
||||
}
|
||||
generateGithubErrors() {
|
||||
const errors = this.error.errors;
|
||||
if (errors instanceof Array) {
|
||||
return errors.map((err) => new GithubError_1.GithubError(err));
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
hasErrorWithCode(code) {
|
||||
return this.githubErrors.some((err) => err.code == code);
|
||||
}
|
||||
toString() {
|
||||
const message = this.error.message;
|
||||
const errors = this.githubErrors;
|
||||
if (errors.length > 0) {
|
||||
return `${message}\nErrors:\n${this.errorBulletedList(errors)}`;
|
||||
}
|
||||
else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
errorBulletedList(errors) {
|
||||
return errors.map((err) => `- ${err}`).join("\n");
|
||||
}
|
||||
}
|
||||
exports.ErrorMessage = ErrorMessage;
|
||||
@@ -1,56 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class GithubError {
|
||||
constructor(error) {
|
||||
this.error = error;
|
||||
}
|
||||
get code() {
|
||||
return this.error.code;
|
||||
}
|
||||
toString() {
|
||||
const code = this.error.code;
|
||||
switch (code) {
|
||||
case 'missing':
|
||||
return this.missingResourceMessage();
|
||||
case 'missing_field':
|
||||
return this.missingFieldMessage();
|
||||
case 'invalid':
|
||||
return this.invalidFieldMessage();
|
||||
case 'already_exists':
|
||||
return this.resourceAlreadyExists();
|
||||
default:
|
||||
return this.customErrorMessage();
|
||||
}
|
||||
}
|
||||
customErrorMessage() {
|
||||
const message = this.error.message;
|
||||
const documentation = this.error.documentation_url;
|
||||
let documentationMessage;
|
||||
if (documentation) {
|
||||
documentationMessage = `\nPlease see ${documentation}.`;
|
||||
}
|
||||
else {
|
||||
documentationMessage = "";
|
||||
}
|
||||
return `${message}${documentationMessage}`;
|
||||
}
|
||||
invalidFieldMessage() {
|
||||
const resource = this.error.resource;
|
||||
const field = this.error.field;
|
||||
return `The ${field} field on ${resource} is an invalid format.`;
|
||||
}
|
||||
missingResourceMessage() {
|
||||
const resource = this.error.resource;
|
||||
return `${resource} does not exist.`;
|
||||
}
|
||||
missingFieldMessage() {
|
||||
const resource = this.error.resource;
|
||||
const field = this.error.field;
|
||||
return `The ${field} field on ${resource} is missing.`;
|
||||
}
|
||||
resourceAlreadyExists() {
|
||||
const resource = this.error.resource;
|
||||
return `${resource} already exists.`;
|
||||
}
|
||||
}
|
||||
exports.GithubError = GithubError;
|
||||
@@ -1,9 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const glob_1 = require("glob");
|
||||
class FileGlobber {
|
||||
glob(pattern) {
|
||||
return new glob_1.GlobSync(pattern, { mark: true }).found;
|
||||
}
|
||||
}
|
||||
exports.FileGlobber = FileGlobber;
|
||||
@@ -1,80 +0,0 @@
|
||||
"use strict";
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const fs_1 = require("fs");
|
||||
class CoreInputs {
|
||||
constructor(artifactGlobber, context) {
|
||||
this.artifactGlobber = artifactGlobber;
|
||||
this.context = context;
|
||||
}
|
||||
get allowUpdates() {
|
||||
const allow = core.getInput('allowUpdates');
|
||||
return allow == 'true';
|
||||
}
|
||||
get artifacts() {
|
||||
let artifacts = core.getInput('artifacts');
|
||||
if (!artifacts) {
|
||||
artifacts = core.getInput('artifact');
|
||||
}
|
||||
if (artifacts) {
|
||||
let contentType = core.getInput('artifactContentType');
|
||||
if (!contentType) {
|
||||
contentType = 'raw';
|
||||
}
|
||||
return this.artifactGlobber
|
||||
.globArtifactString(artifacts, contentType);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
get body() {
|
||||
const body = core.getInput('body');
|
||||
if (body) {
|
||||
return body;
|
||||
}
|
||||
const bodyFile = core.getInput('bodyFile');
|
||||
if (bodyFile) {
|
||||
return this.stringFromFile(bodyFile);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
get commit() {
|
||||
return core.getInput('commit');
|
||||
}
|
||||
get draft() {
|
||||
const draft = core.getInput('draft');
|
||||
return draft == 'true';
|
||||
}
|
||||
get name() {
|
||||
const name = core.getInput('name');
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
return this.tag;
|
||||
}
|
||||
get tag() {
|
||||
const tag = core.getInput('tag');
|
||||
if (tag) {
|
||||
return tag;
|
||||
}
|
||||
const ref = this.context.ref;
|
||||
const tagPath = "refs/tags/";
|
||||
if (ref && ref.startsWith(tagPath)) {
|
||||
return ref.substr(tagPath.length, ref.length);
|
||||
}
|
||||
throw Error("No tag found in ref or input!");
|
||||
}
|
||||
get token() {
|
||||
return core.getInput('token', { required: true });
|
||||
}
|
||||
stringFromFile(path) {
|
||||
return fs_1.readFileSync(path, 'utf-8');
|
||||
}
|
||||
}
|
||||
exports.CoreInputs = CoreInputs;
|
||||
49
lib/Main.js
49
lib/Main.js
@@ -1,49 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const github = __importStar(require("@actions/github"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const Inputs_1 = require("./Inputs");
|
||||
const Releases_1 = require("./Releases");
|
||||
const Action_1 = require("./Action");
|
||||
const ArtifactUploader_1 = require("./ArtifactUploader");
|
||||
const ArtifactGlobber_1 = require("./ArtifactGlobber");
|
||||
const ErrorMessage_1 = require("./ErrorMessage");
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const action = createAction();
|
||||
yield action.perform();
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = new ErrorMessage_1.ErrorMessage(error);
|
||||
core.setFailed(errorMessage.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
function createAction() {
|
||||
const token = core.getInput('token');
|
||||
const context = github.context;
|
||||
const git = new github.GitHub(token);
|
||||
const globber = new ArtifactGlobber_1.FileArtifactGlobber();
|
||||
const inputs = new Inputs_1.CoreInputs(globber, context);
|
||||
const releases = new Releases_1.GithubReleases(context, git);
|
||||
const uploader = new ArtifactUploader_1.GithubArtifactUploader(releases);
|
||||
return new Action_1.Action(inputs, releases, uploader);
|
||||
}
|
||||
run();
|
||||
@@ -1,67 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class GithubReleases {
|
||||
constructor(context, git) {
|
||||
this.context = context;
|
||||
this.git = git;
|
||||
}
|
||||
create(tag, body, commitHash, draft, name) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return this.git.repos.createRelease({
|
||||
body: body,
|
||||
name: name,
|
||||
draft: draft,
|
||||
owner: this.context.repo.owner,
|
||||
repo: this.context.repo.repo,
|
||||
target_commitish: commitHash,
|
||||
tag_name: tag
|
||||
});
|
||||
});
|
||||
}
|
||||
getByTag(tag) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return this.git.repos.getReleaseByTag({
|
||||
owner: this.context.repo.owner,
|
||||
repo: this.context.repo.repo,
|
||||
tag: tag
|
||||
});
|
||||
});
|
||||
}
|
||||
update(id, tag, body, commitHash, draft, name) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return this.git.repos.updateRelease({
|
||||
release_id: id,
|
||||
body: body,
|
||||
name: name,
|
||||
draft: draft,
|
||||
owner: this.context.repo.owner,
|
||||
repo: this.context.repo.repo,
|
||||
target_commitish: commitHash,
|
||||
tag_name: tag
|
||||
});
|
||||
});
|
||||
}
|
||||
uploadArtifact(assetUrl, contentLength, contentType, file, name) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return this.git.repos.uploadReleaseAsset({
|
||||
url: assetUrl,
|
||||
headers: {
|
||||
"content-length": contentLength,
|
||||
"content-type": contentType
|
||||
},
|
||||
file: file,
|
||||
name: name
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.GithubReleases = GithubReleases;
|
||||
103
package.json
103
package.json
@@ -1,41 +1,66 @@
|
||||
{
|
||||
"name": "release-action",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"description": "An action which manages a github release",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf lib/*",
|
||||
"debug": "yarn clean && yarn install && yarn build",
|
||||
"release": "yarn clean && yarn install --production && yarn build",
|
||||
"test": "jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ncipollo/release-action.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
"node",
|
||||
"setup"
|
||||
],
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.0.0",
|
||||
"@actions/github": "^1.0.0",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/node": "^12.0.4",
|
||||
"add": "^2.0.6",
|
||||
"glob": "^7.1.4",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.13",
|
||||
"jest": "^24.8.0",
|
||||
"jest-circus": "^24.7.1",
|
||||
"ts-jest": "^24.0.2",
|
||||
"typescript": "^3.7.3"
|
||||
}
|
||||
"name": "release-action",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"description": "An action which manages a github release",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf lib/*",
|
||||
"coverage": "jest --coverage",
|
||||
"debug": "yarn clean && yarn install && yarn build && yarn package",
|
||||
"format": "yarn biome format --write .",
|
||||
"package": "ncc build --source-map --license licenses.txt",
|
||||
"release": "yarn clean && yarn install --production && yarn build && yarn package",
|
||||
"test": "jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ncipollo/release-action.git"
|
||||
},
|
||||
"keywords": ["actions", "node", "setup"],
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"jest": {
|
||||
"clearMocks": true,
|
||||
"collectCoverage": true,
|
||||
"coveragePathIgnorePatterns": ["src/Globber.ts", "src/Releases.ts"],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 95,
|
||||
"functions": 100,
|
||||
"lines": 100,
|
||||
"statements": 100
|
||||
}
|
||||
},
|
||||
"moduleFileExtensions": ["js", "ts"],
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/*.test.ts"],
|
||||
"testRunner": "jest-circus/runner",
|
||||
"transform": {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"verbose": true
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/github": "^6.0.1",
|
||||
"@types/node": "^22.15.29",
|
||||
"glob": "^11.0.2",
|
||||
"untildify": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"ts-jest": "^29.3.4",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"jest-cli/yargs": "^17.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
6
sheepit.toml
Normal file
6
sheepit.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[repository]
|
||||
enable_commit = true
|
||||
tag_pattern = "v{version}"
|
||||
|
||||
[scripts]
|
||||
before_commit = "yarn release"
|
||||
162
src/Action.ts
162
src/Action.ts
@@ -1,70 +1,154 @@
|
||||
import { Inputs } from "./Inputs";
|
||||
import { Releases } from "./Releases";
|
||||
import { ArtifactUploader } from "./ArtifactUploader";
|
||||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import * as core from "@actions/core"
|
||||
import type { ActionSkipper } from "./ActionSkipper"
|
||||
import type { ArtifactDestroyer } from "./ArtifactDestroyer"
|
||||
import type { ArtifactUploader } from "./ArtifactUploader"
|
||||
import { GithubError } from "./GithubError"
|
||||
import type { Inputs } from "./Inputs"
|
||||
import type { Outputs } from "./Outputs"
|
||||
import { ReleaseValidator } from "./ReleaseValidator"
|
||||
import type {
|
||||
CreateOrUpdateReleaseResponse,
|
||||
CreateReleaseResponse,
|
||||
ReleaseByTagResponse,
|
||||
Releases,
|
||||
UpdateReleaseResponse,
|
||||
} from "./Releases"
|
||||
|
||||
export class Action {
|
||||
private inputs: Inputs
|
||||
private outputs: Outputs
|
||||
private releases: Releases
|
||||
private uploader: ArtifactUploader
|
||||
private artifactDestroyer: ArtifactDestroyer
|
||||
private skipper: ActionSkipper
|
||||
|
||||
constructor(inputs: Inputs, releases: Releases, uploader: ArtifactUploader) {
|
||||
private releaseValidator: ReleaseValidator
|
||||
|
||||
constructor(
|
||||
inputs: Inputs,
|
||||
outputs: Outputs,
|
||||
releases: Releases,
|
||||
uploader: ArtifactUploader,
|
||||
artifactDestroyer: ArtifactDestroyer,
|
||||
skipper: ActionSkipper
|
||||
) {
|
||||
this.inputs = inputs
|
||||
this.outputs = outputs
|
||||
this.releases = releases
|
||||
this.uploader = uploader
|
||||
this.artifactDestroyer = artifactDestroyer
|
||||
this.skipper = skipper
|
||||
this.releaseValidator = new ReleaseValidator(inputs.updateOnlyUnreleased)
|
||||
}
|
||||
|
||||
async perform() {
|
||||
const uploadUrl = await this.createOrUpdateRelease()
|
||||
if (await this.skipper.shouldSkip()) {
|
||||
core.notice("Skipping action, release already exists and skipIfReleaseExists is enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
const releaseResponse = await this.createOrUpdateRelease()
|
||||
const releaseData = releaseResponse.data
|
||||
const releaseId = releaseData.id
|
||||
const uploadUrl = releaseData.upload_url
|
||||
|
||||
if (this.inputs.removeArtifacts) {
|
||||
await this.artifactDestroyer.destroyArtifacts(releaseId)
|
||||
}
|
||||
|
||||
const artifacts = this.inputs.artifacts
|
||||
if (artifacts.length > 0) {
|
||||
await this.uploader.uploadArtifacts(artifacts, uploadUrl)
|
||||
await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl)
|
||||
}
|
||||
|
||||
this.outputs.applyReleaseData(releaseData)
|
||||
}
|
||||
|
||||
private async createOrUpdateRelease(): Promise<string> {
|
||||
try {
|
||||
return await this.createRelease()
|
||||
} catch (error) {
|
||||
if (this.releaseAlreadyExisted(error) && this.inputs.allowUpdates) {
|
||||
return this.updateRelease()
|
||||
} else {
|
||||
throw error
|
||||
private async createOrUpdateRelease(): Promise<CreateOrUpdateReleaseResponse> {
|
||||
if (this.inputs.allowUpdates) {
|
||||
let getResponse: ReleaseByTagResponse
|
||||
try {
|
||||
getResponse = await this.releases.getByTag(this.inputs.tag)
|
||||
} catch (error: any) {
|
||||
return await this.checkForMissingReleaseError(error)
|
||||
}
|
||||
|
||||
// Fail if this isn't an unreleased release & updateOnlyUnreleased is enabled.
|
||||
this.releaseValidator.validateReleaseUpdate(getResponse.data)
|
||||
|
||||
return await this.updateRelease(getResponse.data.id)
|
||||
} else {
|
||||
return await this.createRelease()
|
||||
}
|
||||
}
|
||||
|
||||
private async createRelease(): Promise<string> {
|
||||
const response = await this.releases.create(
|
||||
this.inputs.tag,
|
||||
this.inputs.body,
|
||||
this.inputs.commit,
|
||||
this.inputs.draft,
|
||||
this.inputs.name
|
||||
)
|
||||
|
||||
return response.data.upload_url
|
||||
private async checkForMissingReleaseError(error: Error): Promise<CreateOrUpdateReleaseResponse> {
|
||||
if (Action.noPublishedRelease(error)) {
|
||||
return await this.updateDraftOrCreateRelease()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private releaseAlreadyExisted(error: any): boolean {
|
||||
const errorMessage = new ErrorMessage(error)
|
||||
return errorMessage.hasErrorWithCode('already_exists')
|
||||
}
|
||||
private async updateRelease(id: number): Promise<UpdateReleaseResponse> {
|
||||
let releaseBody = this.inputs.updatedReleaseBody
|
||||
|
||||
private async updateRelease(): Promise<string> {
|
||||
const getResponse = await this.releases.getByTag(this.inputs.tag)
|
||||
const id = getResponse.data.id
|
||||
if (this.inputs.generateReleaseNotes && !this.inputs.omitBodyDuringUpdate) {
|
||||
const response = await this.releases.generateReleaseNotes(this.inputs.tag)
|
||||
releaseBody = response.data.body
|
||||
}
|
||||
|
||||
const response = await this.releases.update(
|
||||
return await this.releases.update(
|
||||
id,
|
||||
this.inputs.tag,
|
||||
this.inputs.body,
|
||||
releaseBody,
|
||||
this.inputs.commit,
|
||||
this.inputs.draft,
|
||||
this.inputs.name
|
||||
this.inputs.discussionCategory,
|
||||
this.inputs.updatedDraft,
|
||||
this.inputs.makeLatest,
|
||||
this.inputs.updatedReleaseName,
|
||||
this.inputs.updatedPrerelease
|
||||
)
|
||||
|
||||
return response.data.upload_url
|
||||
}
|
||||
}
|
||||
|
||||
private static noPublishedRelease(error: any): boolean {
|
||||
const githubError = new GithubError(error)
|
||||
return githubError.status == 404
|
||||
}
|
||||
|
||||
private async updateDraftOrCreateRelease(): Promise<CreateReleaseResponse | UpdateReleaseResponse> {
|
||||
const draftReleaseId = await this.findMatchingDraftReleaseId()
|
||||
if (draftReleaseId) {
|
||||
return await this.updateRelease(draftReleaseId)
|
||||
} else {
|
||||
return await this.createRelease()
|
||||
}
|
||||
}
|
||||
|
||||
private async findMatchingDraftReleaseId(): Promise<number | undefined> {
|
||||
const tag = this.inputs.tag
|
||||
const response = await this.releases.listReleases()
|
||||
const releases = response.data
|
||||
if (!releases) {
|
||||
throw new Error(`No releases found. Response: ${JSON.stringify(response)}`)
|
||||
}
|
||||
|
||||
const draftRelease = releases.find((release) => release.draft && release.tag_name == tag)
|
||||
|
||||
return draftRelease?.id
|
||||
}
|
||||
|
||||
private async createRelease(): Promise<CreateReleaseResponse> {
|
||||
return await this.releases.create(
|
||||
this.inputs.tag,
|
||||
this.inputs.createdReleaseBody,
|
||||
this.inputs.commit,
|
||||
this.inputs.discussionCategory,
|
||||
this.inputs.createdDraft,
|
||||
this.inputs.generateReleaseNotes,
|
||||
this.inputs.makeLatest,
|
||||
this.inputs.createdReleaseName,
|
||||
this.inputs.createdPrerelease
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
28
src/ActionSkipper.ts
Normal file
28
src/ActionSkipper.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Releases } from "./Releases"
|
||||
|
||||
export interface ActionSkipper {
|
||||
shouldSkip(): Promise<boolean>
|
||||
}
|
||||
|
||||
export class ReleaseActionSkipper {
|
||||
constructor(
|
||||
private skipIfReleaseExists: boolean,
|
||||
private releases: Releases,
|
||||
private tag: string
|
||||
) {}
|
||||
|
||||
async shouldSkip(): Promise<boolean> {
|
||||
if (!this.skipIfReleaseExists) {
|
||||
// Bail if skip flag isn't set.
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const getResponse = await this.releases.getByTag(this.tag)
|
||||
return getResponse.data != null
|
||||
} catch (error: any) {
|
||||
// There is either no release or something else went wrong. Either way, run the action.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { basename } from "path";
|
||||
import { readFileSync, statSync } from "fs";
|
||||
import { basename } from "path"
|
||||
import { createReadStream, readFileSync, ReadStream, statSync } from "fs"
|
||||
|
||||
export class Artifact {
|
||||
readonly contentType: string
|
||||
@@ -9,14 +9,14 @@ export class Artifact {
|
||||
constructor(path: string, contentType: string = "raw") {
|
||||
this.path = path
|
||||
this.name = basename(path)
|
||||
this.contentType = contentType;
|
||||
this.contentType = contentType
|
||||
}
|
||||
|
||||
get contentLength(): number {
|
||||
return statSync(this.path).size
|
||||
}
|
||||
|
||||
readFile(): Buffer {
|
||||
return readFileSync(this.path)
|
||||
readFile(): ReadStream {
|
||||
return createReadStream(this.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
src/ArtifactDestroyer.ts
Normal file
19
src/ArtifactDestroyer.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Releases } from "./Releases"
|
||||
import * as core from "@actions/core"
|
||||
|
||||
export interface ArtifactDestroyer {
|
||||
destroyArtifacts(releaseId: number): Promise<void>
|
||||
}
|
||||
|
||||
export class GithubArtifactDestroyer implements ArtifactDestroyer {
|
||||
constructor(private releases: Releases) {}
|
||||
|
||||
async destroyArtifacts(releaseId: number): Promise<void> {
|
||||
const releaseAssets = await this.releases.listArtifactsForRelease(releaseId)
|
||||
for (const artifact of releaseAssets) {
|
||||
const asset = artifact
|
||||
core.debug(`Deleting existing artifact ${artifact.name}...`)
|
||||
await this.releases.deleteArtifact(asset.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Globber, FileGlobber } from "./Globber";
|
||||
import { Artifact } from "./Artifact";
|
||||
import * as core from "@actions/core"
|
||||
import { Globber, FileGlobber } from "./Globber"
|
||||
import { Artifact } from "./Artifact"
|
||||
import untildify from "untildify"
|
||||
import { ArtifactPathValidator } from "./ArtifactPathValidator"
|
||||
import { PathNormalizer } from "./PathNormalizer"
|
||||
|
||||
export interface ArtifactGlobber {
|
||||
globArtifactString(artifact: string, contentType: string): Artifact[]
|
||||
globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[]
|
||||
}
|
||||
|
||||
export class FileArtifactGlobber implements ArtifactGlobber {
|
||||
@@ -12,11 +16,45 @@ export class FileArtifactGlobber implements ArtifactGlobber {
|
||||
this.globber = globber
|
||||
}
|
||||
|
||||
globArtifactString(artifact: string, contentType: string): Artifact[] {
|
||||
return artifact.split(',')
|
||||
.map((path) => this.globber.glob(path))
|
||||
globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[] {
|
||||
const split = /[,\n]/
|
||||
return artifact
|
||||
.split(split)
|
||||
.map((path) => path.trimStart())
|
||||
.map((path) => PathNormalizer.normalizePath(path))
|
||||
.map((path) => FileArtifactGlobber.expandPath(path))
|
||||
.map((pattern) => this.globPattern(pattern, errorsFailBuild))
|
||||
.map((globResult) => FileArtifactGlobber.validatePattern(errorsFailBuild, globResult[1], globResult[0]))
|
||||
.reduce((accumulated, current) => accumulated.concat(current))
|
||||
.map((path) => new Artifact(path, contentType))
|
||||
}
|
||||
}
|
||||
|
||||
private globPattern(pattern: string, errorsFailBuild: boolean): [string, string[]] {
|
||||
const paths = this.globber.glob(pattern)
|
||||
if (paths.length == 0) {
|
||||
if (errorsFailBuild) {
|
||||
FileArtifactGlobber.throwGlobError(pattern)
|
||||
} else {
|
||||
FileArtifactGlobber.reportGlobWarning(pattern)
|
||||
}
|
||||
}
|
||||
return [pattern, paths]
|
||||
}
|
||||
|
||||
private static validatePattern(errorsFailBuild: boolean, paths: string[], pattern: string): string[] {
|
||||
const validator = new ArtifactPathValidator(errorsFailBuild, paths, pattern)
|
||||
return validator.validate()
|
||||
}
|
||||
|
||||
private static reportGlobWarning(pattern: string) {
|
||||
core.warning(`Artifact pattern :${pattern} did not match any files`)
|
||||
}
|
||||
|
||||
private static throwGlobError(pattern: string) {
|
||||
throw Error(`Artifact pattern :${pattern} did not match any files`)
|
||||
}
|
||||
|
||||
private static expandPath(path: string): string {
|
||||
return untildify(path)
|
||||
}
|
||||
}
|
||||
|
||||
43
src/ArtifactPathValidator.ts
Normal file
43
src/ArtifactPathValidator.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as core from "@actions/core"
|
||||
import { statSync } from "fs"
|
||||
|
||||
export class ArtifactPathValidator {
|
||||
private readonly errorsFailBuild: boolean
|
||||
private paths: string[]
|
||||
private readonly pattern: string
|
||||
|
||||
constructor(errorsFailBuild: boolean, paths: string[], pattern: string) {
|
||||
this.paths = paths
|
||||
this.pattern = pattern
|
||||
this.errorsFailBuild = errorsFailBuild
|
||||
}
|
||||
|
||||
validate(): string[] {
|
||||
this.verifyPathsNotEmpty()
|
||||
return this.paths.filter((path) => this.verifyNotDirectory(path))
|
||||
}
|
||||
|
||||
private verifyPathsNotEmpty() {
|
||||
if (this.paths.length == 0) {
|
||||
const message = `Artifact pattern:${this.pattern} did not match any files`
|
||||
this.reportError(message)
|
||||
}
|
||||
}
|
||||
|
||||
private verifyNotDirectory(path: string): boolean {
|
||||
const isDir = statSync(path).isDirectory()
|
||||
if (isDir) {
|
||||
const message = `Artifact is a directory:${path}. Directories can not be uploaded to a release.`
|
||||
this.reportError(message)
|
||||
}
|
||||
return !isDir
|
||||
}
|
||||
|
||||
private reportError(message: string) {
|
||||
if (this.errorsFailBuild) {
|
||||
throw Error(message)
|
||||
} else {
|
||||
core.warning(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,64 @@
|
||||
import { Artifact } from "./Artifact";
|
||||
import { Releases } from "./Releases";
|
||||
import * as core from "@actions/core"
|
||||
import { Artifact } from "./Artifact"
|
||||
import { Releases } from "./Releases"
|
||||
|
||||
export interface ArtifactUploader {
|
||||
uploadArtifacts(artifacts: Artifact[], uploadUrl: string): Promise<void>
|
||||
uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void>
|
||||
}
|
||||
|
||||
export class GithubArtifactUploader implements ArtifactUploader {
|
||||
private releases: Releases
|
||||
constructor(
|
||||
private releases: Releases,
|
||||
private replacesExistingArtifacts: boolean = true,
|
||||
private throwsUploadErrors: boolean = false
|
||||
) {}
|
||||
|
||||
constructor(releases: Releases) {
|
||||
this.releases = releases
|
||||
async uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void> {
|
||||
if (this.replacesExistingArtifacts) {
|
||||
await this.deleteUpdatedArtifacts(artifacts, releaseId)
|
||||
}
|
||||
for (const artifact of artifacts) {
|
||||
await this.uploadArtifact(artifact, releaseId, uploadUrl)
|
||||
}
|
||||
}
|
||||
|
||||
async uploadArtifacts(artifacts: Artifact[], uploadUrl: string) {
|
||||
artifacts.forEach(async artifact => {
|
||||
await this.releases.uploadArtifact(uploadUrl,
|
||||
private async uploadArtifact(artifact: Artifact, releaseId: number, uploadUrl: string, retry = 3) {
|
||||
try {
|
||||
core.debug(`Uploading artifact ${artifact.name}...`)
|
||||
await this.releases.uploadArtifact(
|
||||
uploadUrl,
|
||||
artifact.contentLength,
|
||||
artifact.contentType,
|
||||
artifact.readFile(),
|
||||
artifact.name)
|
||||
});
|
||||
artifact.name,
|
||||
releaseId
|
||||
)
|
||||
} catch (error: any) {
|
||||
if (error.status >= 500 && retry > 0) {
|
||||
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`)
|
||||
await this.uploadArtifact(artifact, releaseId, uploadUrl, retry - 1)
|
||||
} else {
|
||||
if (this.throwsUploadErrors) {
|
||||
throw Error(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
|
||||
} else {
|
||||
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteUpdatedArtifacts(artifacts: Artifact[], releaseId: number): Promise<void> {
|
||||
const releaseAssets = await this.releases.listArtifactsForRelease(releaseId)
|
||||
const assetByName: Record<string, { id: number; name: string }> = {}
|
||||
releaseAssets.forEach((asset) => {
|
||||
assetByName[asset.name] = asset
|
||||
})
|
||||
for (const artifact of artifacts) {
|
||||
const asset = assetByName[artifact.name]
|
||||
if (asset) {
|
||||
core.debug(`Deleting existing artifact ${artifact.name}...`)
|
||||
await this.releases.deleteArtifact(asset.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { GithubError } from "./GithubError"
|
||||
|
||||
export class ErrorMessage {
|
||||
private error: any
|
||||
private githubErrors: GithubError[]
|
||||
|
||||
constructor(error: any) {
|
||||
this.error = error
|
||||
this.githubErrors = this.generateGithubErrors()
|
||||
}
|
||||
|
||||
private generateGithubErrors(): GithubError[] {
|
||||
const errors = this.error.errors
|
||||
if (errors instanceof Array) {
|
||||
return errors.map((err) => new GithubError(err))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
hasErrorWithCode(code: String): boolean {
|
||||
return this.githubErrors.some((err) => err.code == code)
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const message = this.error.message
|
||||
const errors = this.githubErrors
|
||||
if (errors.length > 0) {
|
||||
return `${message}\nErrors:\n${this.errorBulletedList(errors)}`
|
||||
} else {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
private errorBulletedList(errors: GithubError[]): string {
|
||||
return errors.map((err) => `- ${err}`).join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,50 @@
|
||||
import { GithubErrorDetail } from "./GithubErrorDetail"
|
||||
|
||||
export class GithubError {
|
||||
private error: any;
|
||||
private error: any
|
||||
private readonly githubErrors: GithubErrorDetail[]
|
||||
|
||||
constructor(error: any) {
|
||||
this.error = error
|
||||
this.githubErrors = this.generateGithubErrors()
|
||||
}
|
||||
|
||||
get code(): string {
|
||||
return this.error.code
|
||||
private generateGithubErrors(): GithubErrorDetail[] {
|
||||
const errors = this.error.errors
|
||||
if (errors instanceof Array) {
|
||||
return errors.map((err) => new GithubErrorDetail(err))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
get status(): number {
|
||||
return this.error.status
|
||||
}
|
||||
|
||||
hasErrorWithCode(code: String): boolean {
|
||||
return this.githubErrors.some((err) => err.code == code)
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const code = this.error.code
|
||||
switch (code) {
|
||||
case 'missing':
|
||||
return this.missingResourceMessage()
|
||||
case 'missing_field':
|
||||
return this.missingFieldMessage()
|
||||
case 'invalid':
|
||||
return this.invalidFieldMessage()
|
||||
case 'already_exists':
|
||||
return this.resourceAlreadyExists()
|
||||
default:
|
||||
return this.customErrorMessage()
|
||||
}
|
||||
}
|
||||
|
||||
private customErrorMessage(): string {
|
||||
const message = this.error.message;
|
||||
const documentation = this.error.documentation_url
|
||||
|
||||
let documentationMessage: string
|
||||
if (documentation) {
|
||||
documentationMessage = `\nPlease see ${documentation}.`
|
||||
const message = this.error.message
|
||||
const errors = this.githubErrors
|
||||
const status = this.status
|
||||
if (errors.length > 0) {
|
||||
return `Error ${status}: ${message}\nErrors:\n${this.errorBulletedList(errors)}${this.remediation()}`
|
||||
} else {
|
||||
documentationMessage = ""
|
||||
return `Error ${status}: ${message}${this.remediation()}`
|
||||
}
|
||||
|
||||
return `${message}${documentationMessage}`
|
||||
}
|
||||
|
||||
private invalidFieldMessage(): string {
|
||||
const resource = this.error.resource
|
||||
const field = this.error.field
|
||||
|
||||
return `The ${field} field on ${resource} is an invalid format.`
|
||||
private errorBulletedList(errors: GithubErrorDetail[]): string {
|
||||
return errors.map((err) => `- ${err}`).join("\n")
|
||||
}
|
||||
|
||||
private missingResourceMessage(): string {
|
||||
const resource = this.error.resource
|
||||
return `${resource} does not exist.`
|
||||
}
|
||||
|
||||
private missingFieldMessage(): string {
|
||||
const resource = this.error.resource
|
||||
const field = this.error.field
|
||||
|
||||
return `The ${field} field on ${resource} is missing.`
|
||||
}
|
||||
|
||||
private resourceAlreadyExists(): string {
|
||||
const resource = this.error.resource
|
||||
return `${resource} already exists.`
|
||||
private remediation(): String {
|
||||
if (this.status == 404) {
|
||||
return "\nMake sure your github token has access to the repo and has permission to author releases"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
65
src/GithubErrorDetail.ts
Normal file
65
src/GithubErrorDetail.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export class GithubErrorDetail {
|
||||
private error: any
|
||||
|
||||
constructor(error: any) {
|
||||
this.error = error
|
||||
}
|
||||
|
||||
get code(): string {
|
||||
return this.error.code
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const code = this.error.code
|
||||
switch (code) {
|
||||
case "missing":
|
||||
return this.missingResourceMessage()
|
||||
case "missing_field":
|
||||
return this.missingFieldMessage()
|
||||
case "invalid":
|
||||
return this.invalidFieldMessage()
|
||||
case "already_exists":
|
||||
return this.resourceAlreadyExists()
|
||||
default:
|
||||
return this.customErrorMessage()
|
||||
}
|
||||
}
|
||||
|
||||
private customErrorMessage(): string {
|
||||
const message = this.error.message
|
||||
const documentation = this.error.documentation_url
|
||||
|
||||
let documentationMessage: string
|
||||
if (documentation) {
|
||||
documentationMessage = `\nPlease see ${documentation}.`
|
||||
} else {
|
||||
documentationMessage = ""
|
||||
}
|
||||
|
||||
return `${message}${documentationMessage}`
|
||||
}
|
||||
|
||||
private invalidFieldMessage(): string {
|
||||
const resource = this.error.resource
|
||||
const field = this.error.field
|
||||
|
||||
return `The ${field} field on ${resource} is an invalid format.`
|
||||
}
|
||||
|
||||
private missingResourceMessage(): string {
|
||||
const resource = this.error.resource
|
||||
return `${resource} does not exist.`
|
||||
}
|
||||
|
||||
private missingFieldMessage(): string {
|
||||
const resource = this.error.resource
|
||||
const field = this.error.field
|
||||
|
||||
return `The ${field} field on ${resource} is missing.`
|
||||
}
|
||||
|
||||
private resourceAlreadyExists(): string {
|
||||
const resource = this.error.resource
|
||||
return `${resource} already exists.`
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GlobSync } from "glob";
|
||||
import { globSync } from "glob"
|
||||
|
||||
export interface Globber {
|
||||
glob(pattern: string): string[]
|
||||
@@ -6,6 +6,6 @@ export interface Globber {
|
||||
|
||||
export class FileGlobber implements Globber {
|
||||
glob(pattern: string): string[] {
|
||||
return new GlobSync(pattern, { mark: true }).found
|
||||
return globSync(pattern, { mark: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
209
src/Inputs.ts
209
src/Inputs.ts
@@ -1,18 +1,34 @@
|
||||
import * as core from '@actions/core';
|
||||
import { Context } from "@actions/github/lib/context";
|
||||
import { readFileSync } from 'fs';
|
||||
import { ArtifactGlobber } from './ArtifactGlobber';
|
||||
import { Artifact } from './Artifact';
|
||||
import * as core from "@actions/core"
|
||||
import { Context } from "@actions/github/lib/context"
|
||||
import { readFileSync } from "fs"
|
||||
import { ArtifactGlobber } from "./ArtifactGlobber"
|
||||
import { Artifact } from "./Artifact"
|
||||
|
||||
export interface Inputs {
|
||||
readonly allowUpdates: boolean
|
||||
readonly artifactErrorsFailBuild: boolean
|
||||
readonly artifacts: Artifact[]
|
||||
readonly body: string
|
||||
readonly commit: string
|
||||
readonly draft: boolean
|
||||
readonly name: string
|
||||
readonly commit?: string
|
||||
readonly createdDraft: boolean
|
||||
readonly createdPrerelease: boolean
|
||||
readonly createdReleaseBody?: string
|
||||
readonly createdReleaseName?: string
|
||||
readonly discussionCategory?: string
|
||||
readonly generateReleaseNotes: boolean
|
||||
readonly makeLatest?: "legacy" | "true" | "false" | undefined
|
||||
readonly omitBodyDuringUpdate: boolean
|
||||
readonly owner: string
|
||||
readonly removeArtifacts: boolean
|
||||
readonly replacesArtifacts: boolean
|
||||
readonly repo: string
|
||||
readonly skipIfReleaseExists: boolean
|
||||
readonly tag: string
|
||||
readonly token: string
|
||||
readonly updatedDraft?: boolean
|
||||
readonly updatedReleaseBody?: string
|
||||
readonly updatedReleaseName?: string
|
||||
readonly updatedPrerelease?: boolean
|
||||
readonly updateOnlyUnreleased: boolean
|
||||
}
|
||||
|
||||
export class CoreInputs implements Inputs {
|
||||
@@ -25,51 +41,90 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
get allowUpdates(): boolean {
|
||||
const allow = core.getInput('allowUpdates')
|
||||
return allow == 'true'
|
||||
const allow = core.getInput("allowUpdates")
|
||||
return allow == "true"
|
||||
}
|
||||
|
||||
get artifacts(): Artifact[] {
|
||||
let artifacts = core.getInput('artifacts')
|
||||
let artifacts = core.getInput("artifacts")
|
||||
if (!artifacts) {
|
||||
artifacts = core.getInput('artifact')
|
||||
artifacts = core.getInput("artifact")
|
||||
}
|
||||
if (artifacts) {
|
||||
let contentType = core.getInput('artifactContentType')
|
||||
let contentType = core.getInput("artifactContentType")
|
||||
if (!contentType) {
|
||||
contentType = 'raw'
|
||||
contentType = "raw"
|
||||
}
|
||||
return this.artifactGlobber
|
||||
.globArtifactString(artifacts, contentType)
|
||||
return this.artifactGlobber.globArtifactString(artifacts, contentType, this.artifactErrorsFailBuild)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
get body(): string {
|
||||
const body = core.getInput('body')
|
||||
get artifactErrorsFailBuild(): boolean {
|
||||
const allow = core.getInput("artifactErrorsFailBuild")
|
||||
return allow == "true"
|
||||
}
|
||||
|
||||
private get body(): string | undefined {
|
||||
const body = core.getInput("body")
|
||||
if (body) {
|
||||
return body
|
||||
}
|
||||
|
||||
const bodyFile = core.getInput('bodyFile')
|
||||
const bodyFile = core.getInput("bodyFile")
|
||||
if (bodyFile) {
|
||||
return this.stringFromFile(bodyFile)
|
||||
}
|
||||
|
||||
return ''
|
||||
return ""
|
||||
}
|
||||
|
||||
get commit(): string {
|
||||
return core.getInput('commit')
|
||||
get createdDraft(): boolean {
|
||||
const draft = core.getInput("draft")
|
||||
return draft == "true"
|
||||
}
|
||||
|
||||
get draft(): boolean {
|
||||
const draft = core.getInput('draft')
|
||||
return draft == 'true'
|
||||
get createdPrerelease(): boolean {
|
||||
const preRelease = core.getInput("prerelease")
|
||||
return preRelease == "true"
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
const name = core.getInput('name')
|
||||
get createdReleaseBody(): string | undefined {
|
||||
if (CoreInputs.omitBody) return undefined
|
||||
return this.body
|
||||
}
|
||||
|
||||
private static get omitBody(): boolean {
|
||||
return core.getInput("omitBody") == "true"
|
||||
}
|
||||
|
||||
get createdReleaseName(): string | undefined {
|
||||
if (CoreInputs.omitName) return undefined
|
||||
return this.name
|
||||
}
|
||||
|
||||
private static get omitName(): boolean {
|
||||
return core.getInput("omitName") == "true"
|
||||
}
|
||||
|
||||
get commit(): string | undefined {
|
||||
const commit = core.getInput("commit")
|
||||
if (commit) {
|
||||
return commit
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
get discussionCategory(): string | undefined {
|
||||
const category = core.getInput("discussionCategory")
|
||||
if (category) {
|
||||
return category
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
private get name(): string | undefined {
|
||||
const name = core.getInput("name")
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
@@ -77,10 +132,54 @@ export class CoreInputs implements Inputs {
|
||||
return this.tag
|
||||
}
|
||||
|
||||
get generateReleaseNotes(): boolean {
|
||||
const generate = core.getInput("generateReleaseNotes")
|
||||
return generate == "true"
|
||||
}
|
||||
|
||||
get makeLatest(): "legacy" | "true" | "false" | undefined {
|
||||
let latest = core.getInput("makeLatest")
|
||||
if (latest == "true" || latest == "false" || latest == "legacy") {
|
||||
return latest
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
get owner(): string {
|
||||
let owner = core.getInput("owner")
|
||||
if (owner) {
|
||||
return owner
|
||||
}
|
||||
return this.context.repo.owner
|
||||
}
|
||||
|
||||
get removeArtifacts(): boolean {
|
||||
const removes = core.getInput("removeArtifacts")
|
||||
return removes == "true"
|
||||
}
|
||||
|
||||
get replacesArtifacts(): boolean {
|
||||
const replaces = core.getInput("replacesArtifacts")
|
||||
return replaces == "true"
|
||||
}
|
||||
|
||||
get repo(): string {
|
||||
let repo = core.getInput("repo")
|
||||
if (repo) {
|
||||
return repo
|
||||
}
|
||||
return this.context.repo.repo
|
||||
}
|
||||
|
||||
get skipIfReleaseExists(): boolean {
|
||||
return core.getBooleanInput("skipIfReleaseExists")
|
||||
}
|
||||
|
||||
get tag(): string {
|
||||
const tag = core.getInput('tag')
|
||||
const tag = core.getInput("tag")
|
||||
if (tag) {
|
||||
return tag;
|
||||
return tag
|
||||
}
|
||||
|
||||
const ref = this.context.ref
|
||||
@@ -93,10 +192,54 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
get token(): string {
|
||||
return core.getInput('token', { required: true })
|
||||
return core.getInput("token", { required: true })
|
||||
}
|
||||
|
||||
get updatedDraft(): boolean | undefined {
|
||||
if (CoreInputs.omitDraftDuringUpdate) return undefined
|
||||
return this.createdDraft
|
||||
}
|
||||
|
||||
private static get omitDraftDuringUpdate(): boolean {
|
||||
return core.getInput("omitDraftDuringUpdate") == "true"
|
||||
}
|
||||
|
||||
get updatedPrerelease(): boolean | undefined {
|
||||
if (CoreInputs.omitPrereleaseDuringUpdate) return undefined
|
||||
return this.createdPrerelease
|
||||
}
|
||||
|
||||
private static get omitPrereleaseDuringUpdate(): boolean {
|
||||
return core.getInput("omitPrereleaseDuringUpdate") == "true"
|
||||
}
|
||||
|
||||
get updatedReleaseBody(): string | undefined {
|
||||
if (CoreInputs.omitBody || CoreInputs.omitBodyDuringUpdate) return undefined
|
||||
return this.body
|
||||
}
|
||||
|
||||
get updateOnlyUnreleased(): boolean {
|
||||
return core.getInput("updateOnlyUnreleased") == "true"
|
||||
}
|
||||
|
||||
get updatedReleaseName(): string | undefined {
|
||||
if (CoreInputs.omitName || CoreInputs.omitNameDuringUpdate) return undefined
|
||||
return this.name
|
||||
}
|
||||
|
||||
private static get omitBodyDuringUpdate(): boolean {
|
||||
return core.getInput("omitBodyDuringUpdate") == "true"
|
||||
}
|
||||
|
||||
get omitBodyDuringUpdate(): boolean {
|
||||
return CoreInputs.omitBodyDuringUpdate
|
||||
}
|
||||
|
||||
private static get omitNameDuringUpdate(): boolean {
|
||||
return core.getInput("omitNameDuringUpdate") == "true"
|
||||
}
|
||||
|
||||
stringFromFile(path: string): string {
|
||||
return readFileSync(path, 'utf-8')
|
||||
return readFileSync(path, "utf-8")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
src/Main.ts
55
src/Main.ts
@@ -1,32 +1,39 @@
|
||||
import * as github from '@actions/github';
|
||||
import * as core from '@actions/core';
|
||||
import { CoreInputs } from './Inputs';
|
||||
import { GithubReleases } from './Releases';
|
||||
import { Action } from './Action';
|
||||
import { GithubArtifactUploader } from './ArtifactUploader';
|
||||
import { FileArtifactGlobber } from './ArtifactGlobber';
|
||||
import { ErrorMessage } from './ErrorMessage';
|
||||
import * as github from "@actions/github"
|
||||
import * as core from "@actions/core"
|
||||
import { CoreInputs } from "./Inputs"
|
||||
import { GithubReleases } from "./Releases"
|
||||
import { Action } from "./Action"
|
||||
import { GithubArtifactUploader } from "./ArtifactUploader"
|
||||
import { FileArtifactGlobber } from "./ArtifactGlobber"
|
||||
import { GithubError } from "./GithubError"
|
||||
import { CoreOutputs } from "./Outputs"
|
||||
import { GithubArtifactDestroyer } from "./ArtifactDestroyer"
|
||||
import { ActionSkipper, ReleaseActionSkipper } from "./ActionSkipper"
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const action = createAction()
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
const errorMessage = new ErrorMessage(error)
|
||||
core.setFailed(errorMessage.toString());
|
||||
}
|
||||
try {
|
||||
const action = createAction()
|
||||
await action.perform()
|
||||
} catch (error) {
|
||||
const githubError = new GithubError(error)
|
||||
core.setFailed(githubError.toString())
|
||||
}
|
||||
}
|
||||
|
||||
function createAction(): Action {
|
||||
const token = core.getInput('token')
|
||||
const context = github.context
|
||||
const git = new github.GitHub(token)
|
||||
const globber = new FileArtifactGlobber()
|
||||
const token = core.getInput("token")
|
||||
const context = github.context
|
||||
const git = github.getOctokit(token)
|
||||
const globber = new FileArtifactGlobber()
|
||||
|
||||
const inputs = new CoreInputs(globber, context)
|
||||
const releases = new GithubReleases(context, git)
|
||||
const uploader = new GithubArtifactUploader(releases)
|
||||
return new Action(inputs, releases, uploader)
|
||||
const inputs = new CoreInputs(globber, context)
|
||||
const outputs = new CoreOutputs()
|
||||
const releases = new GithubReleases(inputs, git)
|
||||
const skipper = new ReleaseActionSkipper(inputs.skipIfReleaseExists, releases, inputs.tag)
|
||||
const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild)
|
||||
const artifactDestroyer = new GithubArtifactDestroyer(releases)
|
||||
|
||||
return new Action(inputs, outputs, releases, uploader, artifactDestroyer, skipper)
|
||||
}
|
||||
|
||||
run();
|
||||
run()
|
||||
|
||||
16
src/Outputs.ts
Normal file
16
src/Outputs.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as core from "@actions/core"
|
||||
import { ReleaseData } from "./Releases"
|
||||
|
||||
export interface Outputs {
|
||||
applyReleaseData(releaseData: ReleaseData): void
|
||||
}
|
||||
|
||||
export class CoreOutputs implements Outputs {
|
||||
applyReleaseData(releaseData: ReleaseData) {
|
||||
core.setOutput("id", releaseData.id)
|
||||
core.setOutput("html_url", releaseData.html_url)
|
||||
core.setOutput("upload_url", releaseData.upload_url)
|
||||
core.setOutput("tarball_url", releaseData.tarball_url || "")
|
||||
core.setOutput("zipball_url", releaseData.zipball_url || "")
|
||||
}
|
||||
}
|
||||
7
src/PathNormalizer.ts
Normal file
7
src/PathNormalizer.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import path from "path"
|
||||
|
||||
export class PathNormalizer {
|
||||
static normalizePath(pathString: string): string {
|
||||
return pathString.split(path.sep).join("/")
|
||||
}
|
||||
}
|
||||
21
src/ReleaseValidator.ts
Normal file
21
src/ReleaseValidator.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export class ReleaseValidator {
|
||||
constructor(private updateOnlyUnreleased: boolean) {}
|
||||
|
||||
validateReleaseUpdate(releaseResponse: ReleaseStageArguments) {
|
||||
if (!this.updateOnlyUnreleased) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!releaseResponse.draft && !releaseResponse.prerelease) {
|
||||
throw new Error(
|
||||
`Tried to update "${releaseResponse.name ?? "release"}" which is neither a draft or prerelease. (updateOnlyUnreleased is on)`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ReleaseStageArguments = {
|
||||
draft: boolean
|
||||
name: string | null
|
||||
prerelease: boolean
|
||||
}
|
||||
159
src/Releases.ts
159
src/Releases.ts
@@ -1,42 +1,76 @@
|
||||
import { Context } from "@actions/github/lib/context";
|
||||
import { GitHub } from "@actions/github";
|
||||
import { AnyResponse, Response, ReposCreateReleaseResponse, ReposGetReleaseByTagResponse } from "@octokit/rest";
|
||||
import type { GitHub } from "@actions/github/lib/utils"
|
||||
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"
|
||||
import type { OctokitResponse } from "@octokit/types"
|
||||
import type { Inputs } from "./Inputs"
|
||||
|
||||
export type CreateReleaseResponse = RestEndpointMethodTypes["repos"]["createRelease"]["response"]
|
||||
export type ReleaseByTagResponse = RestEndpointMethodTypes["repos"]["getReleaseByTag"]["response"]
|
||||
export type ListReleasesResponse = RestEndpointMethodTypes["repos"]["listReleases"]["response"]
|
||||
export type ListReleaseAssetsResponseData = RestEndpointMethodTypes["repos"]["listReleaseAssets"]["response"]["data"]
|
||||
export type UpdateReleaseResponse = RestEndpointMethodTypes["repos"]["updateRelease"]["response"]
|
||||
export type UploadArtifactResponse = RestEndpointMethodTypes["repos"]["uploadReleaseAsset"]["response"]
|
||||
export type CreateOrUpdateReleaseResponse = CreateReleaseResponse | UpdateReleaseResponse
|
||||
export type GenerateReleaseNotesResponse = RestEndpointMethodTypes["repos"]["generateReleaseNotes"]["response"]
|
||||
|
||||
export type ReleaseData = {
|
||||
id: number
|
||||
html_url: string
|
||||
upload_url: string
|
||||
tarball_url: string | null
|
||||
zipball_url: string | null
|
||||
}
|
||||
|
||||
export interface Releases {
|
||||
create(
|
||||
tag: string,
|
||||
body?: string,
|
||||
commitHash?: string,
|
||||
discussionCategory?: string,
|
||||
draft?: boolean,
|
||||
name?: string
|
||||
): Promise<Response<ReposCreateReleaseResponse>>
|
||||
generateReleaseNotes?: boolean,
|
||||
makeLatest?: "legacy" | "true" | "false" | undefined,
|
||||
name?: string,
|
||||
prerelease?: boolean
|
||||
): Promise<CreateReleaseResponse>
|
||||
|
||||
getByTag(tag: string): Promise<Response<ReposGetReleaseByTagResponse>>
|
||||
deleteArtifact(assetId: number): Promise<OctokitResponse<any>>
|
||||
|
||||
getByTag(tag: string): Promise<ReleaseByTagResponse>
|
||||
|
||||
generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse>
|
||||
|
||||
listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData>
|
||||
|
||||
listReleases(): Promise<ListReleasesResponse>
|
||||
|
||||
update(
|
||||
id: number,
|
||||
tag: string,
|
||||
body?: string,
|
||||
commitHash?: string,
|
||||
discussionCategory?: string,
|
||||
draft?: boolean,
|
||||
name?: string
|
||||
): Promise<Response<ReposCreateReleaseResponse>>
|
||||
makeLatest?: "legacy" | "true" | "false" | undefined,
|
||||
name?: string,
|
||||
prerelease?: boolean
|
||||
): Promise<UpdateReleaseResponse>
|
||||
|
||||
uploadArtifact(
|
||||
assetUrl: string,
|
||||
contentLength: number,
|
||||
contentType: string,
|
||||
file: string | object,
|
||||
name: string
|
||||
): Promise<Response<AnyResponse>>
|
||||
name: string,
|
||||
releaseId: number
|
||||
): Promise<UploadArtifactResponse>
|
||||
}
|
||||
|
||||
export class GithubReleases implements Releases {
|
||||
context: Context
|
||||
git: GitHub
|
||||
git: InstanceType<typeof GitHub>
|
||||
inputs: Inputs
|
||||
|
||||
constructor(context: Context, git: GitHub) {
|
||||
this.context = context
|
||||
constructor(inputs: Inputs, git: InstanceType<typeof GitHub>) {
|
||||
this.inputs = inputs
|
||||
this.git = git
|
||||
}
|
||||
|
||||
@@ -44,25 +78,65 @@ export class GithubReleases implements Releases {
|
||||
tag: string,
|
||||
body?: string,
|
||||
commitHash?: string,
|
||||
discussionCategory?: string,
|
||||
draft?: boolean,
|
||||
name?: string
|
||||
): Promise<Response<ReposCreateReleaseResponse>> {
|
||||
return this.git.repos.createRelease({
|
||||
generateReleaseNotes?: boolean,
|
||||
makeLatest?: "legacy" | "true" | "false" | undefined,
|
||||
name?: string,
|
||||
prerelease?: boolean
|
||||
): Promise<CreateReleaseResponse> {
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
return this.git.rest.repos.createRelease({
|
||||
body: body,
|
||||
name: name,
|
||||
discussion_category_name: discussionCategory,
|
||||
draft: draft,
|
||||
owner: this.context.repo.owner,
|
||||
repo: this.context.repo.repo,
|
||||
generate_release_notes: generateReleaseNotes,
|
||||
make_latest: makeLatest,
|
||||
owner: this.inputs.owner,
|
||||
prerelease: prerelease,
|
||||
repo: this.inputs.repo,
|
||||
target_commitish: commitHash,
|
||||
tag_name: tag
|
||||
tag_name: tag,
|
||||
})
|
||||
}
|
||||
|
||||
async getByTag(tag: string): Promise<Response<ReposGetReleaseByTagResponse>> {
|
||||
return this.git.repos.getReleaseByTag({
|
||||
owner: this.context.repo.owner,
|
||||
repo: this.context.repo.repo,
|
||||
tag: tag
|
||||
async deleteArtifact(assetId: number): Promise<OctokitResponse<any>> {
|
||||
return this.git.rest.repos.deleteReleaseAsset({
|
||||
asset_id: assetId,
|
||||
owner: this.inputs.owner,
|
||||
repo: this.inputs.repo,
|
||||
})
|
||||
}
|
||||
|
||||
async generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse> {
|
||||
return this.git.rest.repos.generateReleaseNotes({
|
||||
owner: this.inputs.owner,
|
||||
repo: this.inputs.repo,
|
||||
tag_name: tag,
|
||||
})
|
||||
}
|
||||
|
||||
async getByTag(tag: string): Promise<ReleaseByTagResponse> {
|
||||
return this.git.rest.repos.getReleaseByTag({
|
||||
owner: this.inputs.owner,
|
||||
repo: this.inputs.repo,
|
||||
tag: tag,
|
||||
})
|
||||
}
|
||||
|
||||
async listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData> {
|
||||
return this.git.paginate(this.git.rest.repos.listReleaseAssets, {
|
||||
owner: this.inputs.owner,
|
||||
release_id: releaseId,
|
||||
repo: this.inputs.repo,
|
||||
})
|
||||
}
|
||||
|
||||
async listReleases(): Promise<ListReleasesResponse> {
|
||||
return this.git.rest.repos.listReleases({
|
||||
owner: this.inputs.owner,
|
||||
repo: this.inputs.repo,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,18 +145,25 @@ export class GithubReleases implements Releases {
|
||||
tag: string,
|
||||
body?: string,
|
||||
commitHash?: string,
|
||||
discussionCategory?: string,
|
||||
draft?: boolean,
|
||||
name?: string
|
||||
): Promise<Response<ReposCreateReleaseResponse>> {
|
||||
return this.git.repos.updateRelease({
|
||||
makeLatest?: "legacy" | "true" | "false" | undefined,
|
||||
name?: string,
|
||||
prerelease?: boolean
|
||||
): Promise<UpdateReleaseResponse> {
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
return this.git.rest.repos.updateRelease({
|
||||
release_id: id,
|
||||
body: body,
|
||||
name: name,
|
||||
discussion_category_name: discussionCategory,
|
||||
draft: draft,
|
||||
owner: this.context.repo.owner,
|
||||
repo: this.context.repo.repo,
|
||||
make_latest: makeLatest,
|
||||
owner: this.inputs.owner,
|
||||
prerelease: prerelease,
|
||||
repo: this.inputs.repo,
|
||||
target_commitish: commitHash,
|
||||
tag_name: tag
|
||||
tag_name: tag,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,16 +172,20 @@ export class GithubReleases implements Releases {
|
||||
contentLength: number,
|
||||
contentType: string,
|
||||
file: string | object,
|
||||
name: string
|
||||
): Promise<Response<AnyResponse>> {
|
||||
return this.git.repos.uploadReleaseAsset({
|
||||
name: string,
|
||||
releaseId: number
|
||||
): Promise<UploadArtifactResponse> {
|
||||
return this.git.rest.repos.uploadReleaseAsset({
|
||||
url: assetUrl,
|
||||
headers: {
|
||||
"content-length": contentLength,
|
||||
"content-type": contentType
|
||||
"content-type": contentType,
|
||||
},
|
||||
file: file,
|
||||
name: name
|
||||
data: file as any,
|
||||
name: name,
|
||||
owner: this.inputs.owner,
|
||||
release_id: releaseId,
|
||||
repo: this.inputs.repo,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user