Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d199224a7 | |||
| d4975510fe | |||
| 871c495487 | |||
| 40a502b14b | |||
| fc45b70f30 | |||
| 9f6c37ac52 | |||
| 80e91ee891 | |||
| bedf824517 | |||
| be9f18b69f | |||
| 1d7e38e56d | |||
| aa13e110b1 | |||
| 15033a1aed | |||
| cd47f1e123 | |||
| 0f1fef3752 | |||
| 5fdab2aaf2 | |||
| 1c12ced7ba | |||
| 211b25966b | |||
| d98e55434d | |||
| 5c894298f2 | |||
| 9a3c005162 | |||
| 225370fc48 | |||
| 28a7970270 | |||
| 6c824bd448 | |||
| 47357ddfee | |||
| a465bf5e6d | |||
| 0fbdc19f81 | |||
| 4f11810a00 | |||
| 46c2a7e41a | |||
| 626bbe7136 | |||
| 1e5fc20bfe | |||
| a9ebfb1a78 | |||
| 4a3fe0bcd3 | |||
| 3d556ddb81 | |||
| a65441cf46 | |||
| 565d0bbe18 | |||
| e8d384d3af | |||
| 55b188b8c6 | |||
| 747fa4805a | |||
| 9c0a43bda4 | |||
| e984b2b6bb | |||
| c2bb007435 | |||
| 2e4712de6f | |||
| ae706665a1 | |||
| 7b46e3ab34 | |||
| b2151226b6 | |||
| 1643ea2734 | |||
| 5ce4932391 | |||
| a1c30dfc53 | |||
| f210cdb256 | |||
| 531da1858f | |||
| 9d54cd22ea | |||
| 713902387e | |||
| 05b1b08f77 | |||
| 47ccfea021 | |||
| 46bd5e54fd | |||
| 14ac06ecd8 | |||
| 9b019476db | |||
| 1f7964519a | |||
| 67eeeea9fa | |||
| 3116829a9b | |||
| 547d771ca3 | |||
| 4897b2cd3b | |||
| 81b71dc6e6 | |||
| 4d15218252 | |||
| b62614fa25 | |||
| e2358e2973 | |||
| 14d6a0a2d2 | |||
| 6fcaac5046 | |||
| b297969f56 | |||
| da04d22321 | |||
| 5fd70ca47a | |||
| 2c6d31be8f | |||
| b3bf422391 | |||
| 4ff3b554b8 | |||
| 3dcd65e44b | |||
| ef4525e9dd | |||
| a6e7249776 | |||
| b529540e0c | |||
| f689e65c54 | |||
| 7bc0d5222f | |||
| 7f17a6e550 | |||
| 6160df50dc | |||
| a2ab4bcf78 | |||
| 7772d5f810 | |||
| 1c4866fa48 | |||
| ebace7edd3 | |||
| e533651251 | |||
| eb4c32847c | |||
| 020f7034f4 | |||
| 2a1b7d5c7e | |||
| eaba9217f8 | |||
| f2d01998f0 | |||
| 99d3ad0a64 | |||
| ac36ca4405 | |||
| 92e6443cf0 | |||
| 8f9992ca17 | |||
| 80fc75ef9c | |||
| 8b9dfa809b | |||
| e35e0e640b | |||
| ccf748b53b | |||
| 8caeee5d56 | |||
| b26ef29d75 | |||
| fba68de49e | |||
| df04d7dbaf | |||
| 37202e8dbc | |||
| 7cd421b8bc | |||
| 17a0aa40f3 | |||
| b7ae833847 | |||
| a2078cf37c | |||
| 4ebc9007c0 | |||
| f66f5629b3 | |||
| ac007c0698 | |||
| 6b9630ac94 | |||
| e54c7a866d | |||
| 750d949f10 | |||
| 28803fc3b4 | |||
| ad054c855d | |||
| 7d605994f9 | |||
| e7914df1c6 | |||
| 1d687b2170 | |||
| a796c65f64 | |||
| 5e3e440c7f | |||
| ea2ffbe002 | |||
| db9fd45770 | |||
| 770cf14bde | |||
| 00fc8b2580 | |||
| e2adf403d6 | |||
| 35ed15faaf | |||
| 9821b26794 | |||
| 8662b07822 | |||
| 2c3e55b8c9 | |||
| 35cd59e8d5 | |||
| eae6c87114 | |||
| 938549d01a | |||
| 079812ed8a | |||
| ac5434c423 | |||
| 4c46ecfd35 | |||
| 534e4012a4 | |||
| 71fe4a8f36 | |||
| aec0ef46e4 | |||
| a40bce7c8d | |||
| d7f00ea0fd | |||
| 46b418103a | |||
| 3004de4b40 | |||
| da76d1fd39 | |||
| fc9958ec6a | |||
| f9ab88cdc3 | |||
| 293aa1ae02 | |||
| 0bb10220a7 | |||
| 3ce161a815 | |||
| 571d130f50 | |||
| 027a230b99 | |||
| dfccb207bf | |||
| 3977d56fd3 | |||
| 4a7f2143e6 | |||
| 5218a83722 | |||
| 2a2b51f939 | |||
| 4f5cf60872 | |||
| 0a64f32b4e | |||
| 9aacf6aeaa | |||
| 5a1b82748e | |||
| d78ededdcb | |||
| df0101c5c3 | |||
| d293c20cc9 | |||
| c4a488fc74 | |||
| d919136160 | |||
| e85d20fbb0 |
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
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.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Create a request to help us improve
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Thank you 🙇♀ for wanting to create an issue in this repository. Before you do, please ensure you are filing the issue in the right place. Issues should only be opened on if the issue **relates to code in this repository**.
|
||||
|
||||
* If you have found a security issue [please submit it here](https://hackerone.com/github)
|
||||
* If you have questions about writing workflows or action files, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
|
||||
* If you are having an issue or question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
|
||||
|
||||
If your issue is relevant to this repository, please include the information below:
|
||||
|
||||
**Describe the enhancement**
|
||||
A clear and concise description of what the features or enhancement you need.
|
||||
|
||||
**Code Snippet**
|
||||
If applicable, add a code snippet to show the api enhancement.
|
||||
|
||||
**Additional information**
|
||||
Add any other context about the feature here.
|
||||
@@ -1,39 +0,0 @@
|
||||
workflow "CI" {
|
||||
on = "push"
|
||||
resolves = ["Format", "Lint", "Test"]
|
||||
}
|
||||
|
||||
action "Dependencies" {
|
||||
uses = "actions/npm@v2.0.0"
|
||||
args = "ci"
|
||||
}
|
||||
|
||||
action "Bootstrap" {
|
||||
needs = "Dependencies"
|
||||
uses = "actions/npm@v2.0.0"
|
||||
args = "run bootstrap"
|
||||
}
|
||||
|
||||
action "Compile" {
|
||||
needs = "Bootstrap"
|
||||
uses = "actions/npm@v2.0.0"
|
||||
args = "run build"
|
||||
}
|
||||
|
||||
action "Format" {
|
||||
needs = "Dependencies"
|
||||
uses = "actions/npm@v2.0.0"
|
||||
args = "run format-check"
|
||||
}
|
||||
|
||||
action "Lint" {
|
||||
needs = "Dependencies"
|
||||
uses = "actions/npm@v2.0.0"
|
||||
args = "run lint"
|
||||
}
|
||||
|
||||
action "Test" {
|
||||
needs = "Compile"
|
||||
uses = "actions/npm@v2.0.0"
|
||||
args = "test"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
name: toolkit-unit-tests
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
|
||||
- name: Bootstrap
|
||||
run: npm run bootstrap
|
||||
|
||||
- name: Compile
|
||||
run: npm run build
|
||||
|
||||
- name: npm test
|
||||
run: npm test
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Format
|
||||
run: npm run format-check
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@github.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -1,53 +1,182 @@
|
||||
# Actions Toolkit 🛠
|
||||
|
||||
<p align="center">
|
||||
<img src="res/at-logo.png">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/actions/toolkit"><img alt="GitHub Actions status" src="https://github.com/actions/toolkit/workflows/toolkit-unit-tests/badge.svg"></a>
|
||||
</p>
|
||||
|
||||
|
||||
## GitHub Actions Toolkit
|
||||
|
||||
The GitHub Actions ToolKit provides a set of packages to make creating actions easier.
|
||||
|
||||
<br/>
|
||||
<h3 align="center">Get started with the <a href="https://github.com/actions/javascript-action">javascript-action template</a>!</h3>
|
||||
<br/>
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | Description |
|
||||
| ------- | ----------- |
|
||||
| [@actions/core](packages/core) | Core functions for setting results, logging, secrets and environment variables |
|
||||
| [@actions/exec](packages/exec) | Functions necessary for running tools on the command line |
|
||||
| [@actions/exit](packages/exit) | Provides utilities for exiting from an action |
|
||||
| [@actions/io](packages/io) | Core functions for CLI filesystem scenarios |
|
||||
| [@actions/tool-cache](packages/tool-cache) | Functions necessary for downloading and caching tools |
|
||||
| [@actions/toolkit](packages/toolkit) | A general-purpose toolkit for writing actions |
|
||||
:heavy_check_mark: [@actions/core](packages/core)
|
||||
|
||||
## Development
|
||||
Provides functions for inputs, outputs, results, logging, secrets and variables. Read more [here](packages/core)
|
||||
|
||||
This repository uses [Lerna](https://github.com/lerna/lerna#readme) to manage multiple packages. Read the documentation there to begin contributing.
|
||||
|
||||
Note that before a PR will be accepted, you must ensure:
|
||||
- all tests are passing
|
||||
- `npm run format` reports no issues
|
||||
- `npm run lint` reports no issues
|
||||
|
||||
### Useful Scripts
|
||||
|
||||
- `npm run bootstrap` This runs `lerna bootstrap` which will install dependencies in this repository's packages and cross-link packages where necessary.
|
||||
- `npm run build` This compiles TypeScript code in each package (this is especially important if one package relies on changes in another when you're running tests). This is just an alias for `lerna run tsc`.
|
||||
- `npm run format` This checks that formatting has been applied with Prettier.
|
||||
- `npm test` This runs all Jest tests in all packages in this repository.
|
||||
- If you need to run tests for only one package, you can pass normal Jest CLI options:
|
||||
```console
|
||||
$ npm test -- packages/toolkit
|
||||
```
|
||||
- `npm run create-package [name]` This runs a script that automates a couple of parts of creating a new package.
|
||||
|
||||
### Creating a Package
|
||||
|
||||
1. In a new branch, create a new Lerna package:
|
||||
|
||||
```console
|
||||
$ npm run create-package new-package
|
||||
```bash
|
||||
$ npm install @actions/core --save
|
||||
```
|
||||
<br/>
|
||||
|
||||
This will ask you some questions about the new package. Start with `0.0.0` as the first version (look generally at some of the other packages for how the package.json is structured).
|
||||
:runner: [@actions/exec](packages/exec)
|
||||
|
||||
2. Add `tsc` script to the new package's package.json file:
|
||||
Provides functions to exec cli tools and process output. Read more [here](packages/exec)
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"tsc": "tsc"
|
||||
}
|
||||
```bash
|
||||
$ npm install @actions/exec --save
|
||||
```
|
||||
<br/>
|
||||
|
||||
3. Start developing 😄 and open a pull request.
|
||||
:pencil2: [@actions/io](packages/io)
|
||||
|
||||
Provides disk i/o functions like cp, mv, rmRF, find etc. Read more [here](packages/io)
|
||||
|
||||
```bash
|
||||
$ npm install @actions/io --save
|
||||
```
|
||||
<br/>
|
||||
|
||||
:hammer: [@actions/tool-cache](packages/tool-cache)
|
||||
|
||||
Provides functions for downloading and caching tools. e.g. setup-* actions. Read more [here](packages/tool-cache)
|
||||
|
||||
```bash
|
||||
$ npm install @actions/tool-cache --save
|
||||
```
|
||||
<br/>
|
||||
|
||||
:octocat: [@actions/github](packages/github)
|
||||
|
||||
Provides an Octokit client hydrated with the context that the current action is being run in. Read more [here](packages/github)
|
||||
|
||||
```bash
|
||||
$ npm install @actions/github --save
|
||||
```
|
||||
<br/>
|
||||
|
||||
## Creating an Action with the Toolkit
|
||||
|
||||
:question: [Choosing an action type](docs/action-types.md)
|
||||
|
||||
Outlines the differences and why you would want to create a JavaScript or a container based action.
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
:curly_loop: [Versioning](docs/action-versioning.md)
|
||||
|
||||
Actions are downloaded and run from the GitHub graph of repos. This contains guidance for versioning actions and safe releases.
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
:warning: [Problem Matchers](docs/problem-matchers.md)
|
||||
|
||||
Problem Matchers are a way to scan the output of actions for a specified regex pattern and surface that information prominently in the UI.
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h3><a href="https://github.com/actions/hello-world-javascript-action">Hello World JavaScript Action</a></h3>
|
||||
|
||||
Illustrates how to create a simple hello world javascript action.
|
||||
|
||||
```javascript
|
||||
...
|
||||
const nameToGreet = core.getInput('who-to-greet');
|
||||
console.log(`Hello ${nameToGreet}!`);
|
||||
...
|
||||
```
|
||||
<br/>
|
||||
|
||||
<h3><a href="https://github.com/actions/javascript-action">JavaScript Action Walkthrough</a></h3>
|
||||
|
||||
Walkthrough and template for creating a JavaScript Action with tests, linting, workflow, publishing, and versioning.
|
||||
|
||||
```javascript
|
||||
async function run() {
|
||||
try {
|
||||
const ms = core.getInput('milliseconds');
|
||||
console.log(`Waiting ${ms} milliseconds ...`)
|
||||
...
|
||||
```
|
||||
```javascript
|
||||
PASS ./index.test.js
|
||||
✓ throws invalid number
|
||||
✓ wait 500 ms
|
||||
✓ test runs
|
||||
|
||||
Test Suites: 1 passed, 1 total
|
||||
Tests: 3 passed, 3 total
|
||||
```
|
||||
<br/>
|
||||
|
||||
<h3><a href="https://github.com/actions/typescript-action">TypeScript Action Walkthrough</a></h3>
|
||||
|
||||
Walkthrough creating a TypeScript Action with compilation, tests, linting, workflow, publishing, and versioning.
|
||||
|
||||
```javascript
|
||||
import * as core from '@actions/core';
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const ms = core.getInput('milliseconds');
|
||||
console.log(`Waiting ${ms} milliseconds ...`)
|
||||
...
|
||||
```
|
||||
```javascript
|
||||
PASS ./index.test.js
|
||||
✓ throws invalid number
|
||||
✓ wait 500 ms
|
||||
✓ test runs
|
||||
|
||||
Test Suites: 1 passed, 1 total
|
||||
Tests: 3 passed, 3 total
|
||||
```
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h3><a href="docs/container-action.md">Docker Action Walkthrough</a></h3>
|
||||
|
||||
Create an action that is delivered as a container and run with docker.
|
||||
|
||||
```docker
|
||||
FROM alpine:3.10
|
||||
COPY LICENSE README.md /
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
```
|
||||
<br/>
|
||||
|
||||
<h3><a href="https://github.com/actions/container-toolkit-action">Docker Action Walkthrough with Octokit</a></h3>
|
||||
|
||||
Create an action that is delivered as a container which uses the toolkit. This example uses the GitHub context to construct an Octokit client.
|
||||
|
||||
```docker
|
||||
FROM node:slim
|
||||
COPY . .
|
||||
RUN npm install --production
|
||||
ENTRYPOINT ["node", "/lib/main.js"]
|
||||
```
|
||||
```javascript
|
||||
const myInput = core.getInput('myInput');
|
||||
core.debug(`Hello ${myInput} from inside a container`);
|
||||
|
||||
const context = github.context;
|
||||
console.log(`We can even get context data, like the repo: ${context.repo.repo}`)
|
||||
```
|
||||
<br/>
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions. See [how to contribute](docs/contribute.md).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
See [our code of conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty](https://hackerone.com/github)
|
||||
|
||||
Thanks for helping make GitHub Actions safe for everyone.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Debugging
|
||||
If the build logs do not provide enough detail on why a build may be failing, some other options exist to assist with troubleshooting.
|
||||
|
||||
## Runner Diagnostic Logs
|
||||
Runner Diagnostic Logs provide additional log files detailing how the Runner is executing an action.
|
||||
|
||||
Each file contains different logging information that corresponds to that process:
|
||||
* The Runner process coordinates setting up workers to execute jobs.
|
||||
* The Worker process executes the job.
|
||||
|
||||
These files contain the prefix `Runner_` or `Worker_` to indicate the log source.
|
||||
|
||||
### How to Access Runner Diagnostic Logs
|
||||
These log files are enabled by [setting the secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#creating-encrypted-secrets) `ACTIONS_RUNNER_DEBUG` to `true`.
|
||||
|
||||
All actions ran while this secret is enabled contain additional diagnostic log files in the `runner-diagnostic-logs` folder of the [log archive](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#downloading-logs-and-artifacts).
|
||||
|
||||
## Step Debug Logs
|
||||
Step debug logs increase the verbosity of a job's logs during and after a job's execution to assist with troubleshooting.
|
||||
|
||||
Additional log events with the prefix `::debug::` will now also appear in the job's logs.
|
||||
|
||||
### How to Access Step Debug Logs
|
||||
This flag can be enabled by [setting the secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#creating-encrypted-secrets) `ACTIONS_STEP_DEBUG` to `true`.
|
||||
|
||||
All actions ran while this secret is enabled will show debug events in the [Downloaded Logs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#downloading-logs-and-artifacts) and [Web Logs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#viewing-logs-to-diagnose-failures).
|
||||
@@ -0,0 +1,45 @@
|
||||
# Action Types
|
||||
|
||||
There are two types of actions. JavaScript and Docker actions.
|
||||
|
||||
- **JavaScript Actions**: JavaScript actions run on the host machine. The unit of work is decoupled from the environment.
|
||||
- **Docker Actions**: A container action is a container which carries both the unit of work along with the environment and its dependencies packaged up as a container.
|
||||
|
||||
Both have access to the workspace and the github event payload and context.
|
||||
|
||||
## Why would I choose a Docker action?
|
||||
|
||||
Docker actions carry both the unit of work and the environment.
|
||||
|
||||
This creates a more consistent and reliable unit of work where the consumer of the action does not need to worry about the toolsets and its dependencies.
|
||||
|
||||
Docker actions are currently limited to Linux only.
|
||||
|
||||
## Why would I choose a host action?
|
||||
|
||||
JavaScript actions decouple the unit of work from the environment and run directly on the host machine or VM.
|
||||
|
||||
Consider a simple example of testing a node lib on node 8, 10 and running a custom action. Each job will setup a node version on the host and custom-action will run its unit of work on each environment (node8+ubuntu16, node8+windows-2019, etc.)
|
||||
|
||||
```yaml
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
node: [8.x, 10.x]
|
||||
os: [ubuntu-16.04, windows-2019]
|
||||
runs-on: ${{matrix.os}}
|
||||
actions:
|
||||
- uses: actions/setup-node@master
|
||||
with:
|
||||
version: ${{matrix.node}}
|
||||
- run: |
|
||||
npm install
|
||||
- run: |
|
||||
npm test
|
||||
- uses: actions/custom-action@master
|
||||
```
|
||||
|
||||
JavaScript actions work on any environment that host action runtime is supported on which is currently node 12. However, a host action that runs a toolset expects the environment that it's running on to have that toolset in its PATH or using a setup-* action to acquire it on demand.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Versioning
|
||||
|
||||
Actions are downloaded and run from the GitHub graph of repos. The workflow references an action using a ref.
|
||||
|
||||
Examples:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/javascript-action@v1 # recommended. starter workflows use this
|
||||
- uses: actions/javascript-action@v1.0.0 # if an action offers specific releases
|
||||
- uses: actions/javascript-action@41775a4 # binding to a specific sha
|
||||
```
|
||||
|
||||
# Compatibility
|
||||
|
||||
Binding to a major version is the latest of that major version ( e.g. `v1` == "1.*" )
|
||||
|
||||
Major versions should guarantee compatibility. A major version can add new capabilities but should not break existing input compatibility or break existing workflows.
|
||||
|
||||
Major version binding allows you to take advantage of bug fixes and critical functionality and security fixes. The `master` branch has the latest code and is unstable to bind to. Changes are committed to master before the changes are ready to be released to the marketplace by creating a tag. In addition, a new major version may break compatibility will get implemented in master after branching off the previous major version.
|
||||
|
||||
> Warning: do not reference `master` since that is the latest code and may contain breaking changes of the next major version.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/javascript-action@master # do not do this
|
||||
```
|
||||
|
||||
Binding to the immutable sha1 may offer more reliability. However, note that the hosted images toolsets (e.g. ubuntu-latest) move forward and if there is a tool breaking issue, actions may react with a patch to a major version to compensate so binding to a specific SHA may prevent you from getting fixes.
|
||||
|
||||
> Recommendation: bind to major versions to get functionality and fixes but reserve binding to a specific release or SHA as a mitigation strategy for unforeseen breaks.
|
||||
|
||||
# Recommendations
|
||||
|
||||
1. **Create a GitHub release for each specific version**: Creating a release like [ v1.0.0 ](https://github.com/actions/javascript-action/releases/tag/v1.0.0) allows users to bind back to a specific version if an issue is encountered with the latest major version.
|
||||
|
||||
2. **Publish the specific version to the marketplace**: When you release a specific version, choose the option to "Publish this release to the GitHub Marketplace".
|
||||
|
||||
3. **Make the new release available to those binding to the major version tag**: Move the major version tag (v1, v2, etc.) to point to the ref of the current release. This will act as the stable release for that major version. You should keep this tag updated to the most recent stable minor/patch release.
|
||||
|
||||
```
|
||||
git tag --force --annotate -m "Update v1 tag" v1
|
||||
git push --force origin v1
|
||||
```
|
||||
# Major Versions
|
||||
|
||||
All releases for a major version should hold compat including input compatibility and behavior compatibility.
|
||||
|
||||
Introduce a major version for compatibility breaks and major rewrites of the action.
|
||||
|
||||
Ideally, a major version would carry other benefits to the user to entice them to upgrade their workflows. Since updating their workflows will need to be done with an understanding of the changes and what compatibility was broken, introducing a new major version shouldn't be taken lightly.
|
||||
|
||||
To get feedback and to set expectations, the new major version can be initially released with `v2-beta` tag to indicate you can try it out but it's still going under some churn. Upon release the `-beta` can be dropped and there's an expectation of compatibility from that point forward.
|
||||
|
||||
[An example of v2-beta with checkout](https://github.com/actions/checkout/tree/c170eefc2657d93cc91397be50a299bff978a052#checkout-v2-beta)
|
||||
|
||||
# Sample Workflow
|
||||
|
||||
This illustrates the versioning workflow covered above.
|
||||
|
||||

|
||||
@@ -0,0 +1 @@
|
||||
<mxfile modified="2019-12-12T18:56:00.899Z" host="www.draw.io" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" etag="CISsL8yLQ-3TSrXBbF_M" version="12.3.8" type="device" pages="1"><diagram name="Page-1" id="ff44883e-f642-bcb2-894b-16b3d25a3f0b">7VvbctsqFP0aP9YjkISkx9hJcx7amZ7mTNs8dYiEZVpZeBC+9esPyMi64UsdW3Ymdh4Mm5tgrbU3AqdnDyfLR46n488sIkkPWtGyZ9/3IASWi+SXsqzWFhf5a0PMaaQrlYYn+ocULbV1RiOS1SoKxhJBp3VjyNKUhKJmw5yzRb3aiCX1Uac4Ji3DU4iTwtp3S/t3GomxtgMUlAX/EBqP9eA+1FN+weHvmLNZqkfsQXuUf9bFE1z0paeajXHEFhWT/dCzh5wxsU5NlkOSqNUtFq5oJ1bF0/bswVhMEpkBMpkXf9zSGBzSWE6Ok1RUh9vW38h6CcMgsC2HoJGLPwSt/kkkV1ZnU5bKr0E443MS6RErg2cCc6HJIJlgD0ga3Sk0ZT5McJbRcG38SJOijcxVW2SCs99kyBLG8+FtK/9sSgos1bRHLN2MBvOuBF/92PQrM88yY0kq6Oy94re1ya2K3JKKH5V0pZXMlY1UpmjTXucCGTbjITEvLtRKwDwmwlxF96IWvdKtxu6RsAmRTy4rcJJgQed1EWCtmnhTb9P0C6PyQaGlFe4UQtX69jyr3sV6GrpVyR+ZqDxGacpZdRjDADo1xfaxaSdnTNjvRH7Dsgqxnquk28Kyoxhjd0MH5NfpgNwGHdaUPQsd1gPNcTIrHFqLHkki44ZixWJMBXma4ny5FjJ01cmRe+2cNTn4hWMGJr+C8k+LI06dI7bqCCc0TpULk8gRripIwlW6GrnqbxfEc8IFWe5EUJfCBhJuIcxFGcSKKuNq+LK2Y15D62+gaUeqa1PqHi99bEQ4Sqtov3eH3cjZDRoksjuTM2zJud/vX1TR4MKKdrzrUbR9Xc720tB4Th0aH14OGucGTQ2apgu7ZBx0bnHwL+Kgsz8Oom7ioGdfLA6im5yrckbO5eQc/+T3377+Gz/Mp9/++5SxT+Hwg/G1AyVy1MGLTMR5guM0lM92V5TIgTaF8htPFE7pS6a+JFMJzkgm68zbZzQSs6lKhjhJ2Ezsh3xKOJUTVbAUjb6UpsGUZVRQlsJcxcA/kc9FjUMBpw0SgF2idNuv1AACln25DYsRoFtk3BUZd3F6V3js6NTHQ6DvAdeDvufK2OgFdW45xtI9B4Tto6XGRg6hs8Vg43L7+x19yAkWZL2eyon3lKoGOUbxVvdvgT7o25u+eCUw1Hvf2UnnsULCeBJXFDjNWAHasQJ1GtGtW7DYhdDlt1xtrr+xYHG80y/IeW6X7lsNbwsbaB7qtv3GqRUC3bptELTUPMGZEk2TQ1IOokmVqkI1r6pK06aWGpW4qHShd7pgQqMo2eYt6i4ip5d+qIbyFVgn2f017uyQZdieewZFg2A7jV6laNh2ubvfg94PVm5jo24f+CrV1OvpsDrkoq28rtfLHuFsbPLGBy/ZXodXWRDXsB6F7ZV+ETSu1bymOzvULwLg7enpdI4xcFf8bjX8NcOfvybsz+Pz/XffcMFysv2s1bfe3H7WLvezLXUaCLn93rVx9uEEBsGeaz9rBNp09nEKoOvnVvAkqLddydtA3XP9K0PddA10HtTBu0U9gAdovWBGJ6i753PqoA/frVNH1rU59fa10ASnqxyoZYmt3D1bhl9NXOPu+fUgOY5bA8kFBpDsM22VjSB5LZCGcs6YpgoXMVbSk5tRkqlt4QT/kgudL0Em+XulmJ0iVDbPcQ3HSYHbIUqG874RVSKi6QamjPA5DWkay7S+zoMWG6mJczKnbJa9JwiDxqtLYFKa6UjwCAxltvwJ/Prdp/xPA/vhfw==</diagram></mxfile>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,137 @@
|
||||
# :: Commands
|
||||
|
||||
The [core toolkit package](https://github.com/actions/toolkit/tree/master/packages/core) offers a number of convenience functions for
|
||||
setting results, logging, registering secrets and exporting variables across actions. Sometimes, however, its useful to be able to do
|
||||
these things in a script or other tool.
|
||||
|
||||
To allow this, we provide a special `::` syntax which, if logged to `stdout` on a new line, will allow the runner to perform special behavior on
|
||||
your commands. The following commands are all supported:
|
||||
|
||||
### Set an environment variable
|
||||
|
||||
To set an environment variable for future out of process steps, use `::set-env`:
|
||||
|
||||
```sh
|
||||
echo "::set-env name=FOO::BAR"
|
||||
```
|
||||
|
||||
Running `$FOO` in a future step will now return `BAR`
|
||||
|
||||
This is wrapped by the core exportVariable method which sets for future steps but also updates the variable for this step
|
||||
|
||||
```javascript
|
||||
export function exportVariable(name: string, val: string): void {}
|
||||
```
|
||||
|
||||
### PATH Manipulation
|
||||
|
||||
To prepend a string to PATH, use `::addPath`:
|
||||
|
||||
```sh
|
||||
echo "::add-path::BAR"
|
||||
```
|
||||
|
||||
Running `$PATH` in a future step will now return `BAR:{Previous Path}`;
|
||||
|
||||
This is wrapped by the core addPath method:
|
||||
```javascript
|
||||
export function addPath(inputPath: string): void {}
|
||||
```
|
||||
|
||||
### Set outputs
|
||||
|
||||
To set an output for the step, use `::set-output`:
|
||||
|
||||
```sh
|
||||
echo "::set-output name=FOO::BAR"
|
||||
```
|
||||
|
||||
Running `steps.[step-id].outputs.FOO` in your Yaml will now give you `BAR`
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: Set the value
|
||||
id: step_one
|
||||
run: echo "::set-output name=FOO::BAR"
|
||||
- name: Use it
|
||||
run: echo ${{ steps.step_one.outputs.FOO }}
|
||||
```
|
||||
|
||||
This is wrapped by the core setOutput method:
|
||||
|
||||
```javascript
|
||||
export function setOutput(name: string, value: string): void {}
|
||||
```
|
||||
|
||||
### Register a secret
|
||||
|
||||
If a script or action does work to create a secret at runtime, it can be registered with the runner to be masked in logs.
|
||||
|
||||
To mask a value in the logs, use `::add-mask`:
|
||||
|
||||
```sh
|
||||
echo "::add-mask::mysecretvalue"
|
||||
```
|
||||
|
||||
This is wrapped by the core setSecret method
|
||||
|
||||
```javascript
|
||||
function setSecret(secret: string): void {}
|
||||
```
|
||||
|
||||
Now, future logs containing BAR will be masked. E.g. running `echo "Hello FOO BAR World"` will now print `Hello FOO **** World`.
|
||||
|
||||
**WARNING** The add-mask and setSecret commands only support single line secrets. To register a multiline secrets you must register each line individually otherwise it will not be masked.
|
||||
|
||||
**WARNING** Do **not** mask short values if you can avoid it, it could render your output unreadable (and future steps' output as well).
|
||||
For example, if you mask the letter `l`, running `echo "Hello FOO BAR World"` will now print `He*********o FOO BAR Wor****d`
|
||||
|
||||
### Group and Ungroup Log Lines
|
||||
|
||||
Emitting a group with a title will instruct the logs to create a collapsable region up to the next ungroup command.
|
||||
|
||||
```bash
|
||||
echo "::group::my title"
|
||||
echo "::endgroup::"
|
||||
```
|
||||
|
||||
This is wrapped by the core methods:
|
||||
|
||||
```javascript
|
||||
function startGroup(name: string): void {}
|
||||
function endGroup(): void {}
|
||||
```
|
||||
|
||||
### Problem Matchers
|
||||
Problems matchers can be used to scan a build's output to automatically surface lines to the user that matches the provided pattern. A file path to a .json Problem Matcher must be provided. See [Problem Matchers](problem-matchers.md) for more information on how to define a Problem Matcher.
|
||||
|
||||
```bash
|
||||
echo "::add-matcher::eslint-compact-problem-matcher.json"
|
||||
echo "::remove-matcher owner=eslint-compact::"
|
||||
```
|
||||
|
||||
`add-matcher` takes a path to a Problem Matcher file
|
||||
`remove-matcher` removes a Problem Matcher by owner
|
||||
### Save State
|
||||
|
||||
Save state to be used in the corresponding wrapper (finally) post job entry point.
|
||||
|
||||
```bash
|
||||
echo "::save-state name=FOO::foovalue"
|
||||
```
|
||||
|
||||
### Log Level
|
||||
|
||||
Finally, there are several commands to emit different levels of log output:
|
||||
|
||||
| log level | example usage |
|
||||
|---|---|
|
||||
| [debug](action-debugging.md) | `echo "::debug::My debug message"` |
|
||||
| warning | `echo "::warning::My warning message"` |
|
||||
| error | `echo "::error::My error message"` |
|
||||
|
||||
### Command Prompt
|
||||
CMD processes the `"` character differently from other shells when echoing. In CMD, the above snippets should have the `"` characters removed in order to correctly process. For example, the set output command would be:
|
||||
```cmd
|
||||
echo ::set-output name=FOO::BAR
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
# Creating a Container Action Using the Toolkit
|
||||
|
||||
In progress.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Creating a Docker Action
|
||||
|
||||
The [container-template](https://github.com/actions/container-template) repo contains the base files to create a Docker action.
|
||||
|
||||
# Create a Repo from the Template
|
||||
|
||||
Navigate to https://github.com/actions/container-template
|
||||
|
||||
Click on `Use this template` to create the repo for your action.
|
||||
|
||||

|
||||
|
||||
Complete creating your repo and clone the repo.
|
||||
|
||||
> NOTE: The location of the repo will be how users will reference your action in their workflow file with the using keyword.
|
||||
|
||||
e.g. To use https://github.com/actions/setup-node, users will author:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
using: actions/setup-node@master
|
||||
```
|
||||
|
||||
# Define Metadata
|
||||
|
||||
Your action has a name and a description. Update the author.
|
||||
|
||||
Create inputs that your unit of work will need. These will be what workflow authors set with the `with:` keyword.
|
||||
|
||||
```yaml
|
||||
name: 'My Container Action'
|
||||
description: 'Get started with Container actions'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
myInput:
|
||||
description: 'Input to use'
|
||||
default: 'world'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.myInput }}
|
||||
```
|
||||
|
||||
It will be run with docker and the input is mapped into the args
|
||||
|
||||
# Change Code
|
||||
|
||||
The entry point is in entrypoint.sh
|
||||
|
||||
```bash
|
||||
#!/bin/sh -l
|
||||
|
||||
echo "hello $1"
|
||||
```
|
||||
|
||||
# Publish
|
||||
|
||||
Simply push your action to publish.
|
||||
|
||||
```bash
|
||||
$ git push
|
||||
```
|
||||
|
||||
The runner will download the action and build the docker container on the fly at runtime.
|
||||
|
||||
> Consider versioning your actions with tags. See [versioning](/docs/action-versioning.md)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
## Development
|
||||
|
||||
This repository uses [Lerna](https://github.com/lerna/lerna#readme) to manage multiple packages. Read the documentation there to begin contributing.
|
||||
|
||||
Note that before a PR will be accepted, you must ensure:
|
||||
- all tests are passing
|
||||
- `npm run format` reports no issues
|
||||
- `npm run lint` reports no issues
|
||||
|
||||
### Useful Scripts
|
||||
|
||||
- `npm run bootstrap` This runs `lerna bootstrap` which will install dependencies in this repository's packages and cross-link packages where necessary.
|
||||
- `npm run build` This compiles TypeScript code in each package (this is especially important if one package relies on changes in another when you're running tests). This is just an alias for `lerna run tsc`.
|
||||
- `npm run format` This checks that formatting has been applied with Prettier.
|
||||
- `npm test` This runs all Jest tests in all packages in this repository.
|
||||
- If you need to run tests for only one package, you can pass normal Jest CLI options:
|
||||
```console
|
||||
$ npm test -- packages/toolkit
|
||||
```
|
||||
- `npm run create-package [name]` This runs a script that automates a couple of parts of creating a new package.
|
||||
|
||||
### Creating a Package
|
||||
|
||||
1. In a new branch, create a new Lerna package:
|
||||
|
||||
```console
|
||||
$ npm run create-package new-package
|
||||
```
|
||||
|
||||
This will ask you some questions about the new package. Start with `0.0.0` as the first version (look generally at some of the other packages for how the package.json is structured).
|
||||
|
||||
2. Add `tsc` script to the new package's package.json file:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"tsc": "tsc"
|
||||
}
|
||||
```
|
||||
|
||||
3. Start developing 😄 and open a pull request.
|
||||
@@ -0,0 +1,288 @@
|
||||
# Creating an Action using the GitHub Context
|
||||
|
||||
## Goal
|
||||
|
||||
In this walkthrough we will learn how to build a basic action using GitHub context data to greet users when they open an issue or PR. In the process we will explore how to access this context and how to make authenticated requests to the GitHub API.
|
||||
|
||||
Note that a complete version of this action can be found at https://github.com/damccorm/issue-greeter.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This walkthrough assumes that you have gone through the basic [javascript action walkthrough](./javascript-action.md) and have a basic action set up. If not, we recommend you go through that first.
|
||||
|
||||
## Installing dependencies
|
||||
|
||||
All of the dependencies we need should come packaged for us in this library's core and github packages. To install, run the following in your action:
|
||||
|
||||
`npm install @actions/core && npm install @actions/github`
|
||||
|
||||
## Metadata
|
||||
|
||||
Next, we will need a welcome message and a repo token as an input. Recall that inputs are defined in the `action.yml` metadata file - update your `action.yml` file to define `welcome-message` and `repo-token` as inputs.
|
||||
|
||||
```yaml
|
||||
name: "Welcome"
|
||||
description: "A basic welcome action"
|
||||
author: "GitHub"
|
||||
inputs:
|
||||
welcome-message:
|
||||
description: "Message to display when a user opens an issue or PR"
|
||||
default: "Thanks for opening an issue! Make sure you've followed CONTRIBUTING.md"
|
||||
repo-token:
|
||||
description: "Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}"
|
||||
required: true
|
||||
runs:
|
||||
using: "node12"
|
||||
main: "lib/main.js"
|
||||
```
|
||||
|
||||
## Action logic
|
||||
|
||||
Now that we've installed our dependencies and defined our inputs, we're ready to start writing the action logic in `src/main.ts`! For clarity, we'll structure our action up as follows:
|
||||
|
||||
```ts
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
const welcomeMessage: string = core.getInput('welcome-message');
|
||||
// TODO - Get context data
|
||||
|
||||
// TODO - make request to the GitHub API to comment on the issue
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
### Getting context data
|
||||
|
||||
For the purpose of this walkthrough, we will need the following pieces of context data:
|
||||
|
||||
- the name of the repo that the action is being run on
|
||||
- the organization/owner of that repo
|
||||
- the number of the issue that has been opened
|
||||
|
||||
Fortunately, the GitHub package provides all of this to us with [a single convenience function](https://github.com/actions/toolkit/blob/ac007c06984bc483fae2ba649788dfc858bc6a8b/packages/github/src/context.ts#L34), so we can simply do:
|
||||
|
||||
`const issue: {owner: string; repo: string; number: number} = github.context.issue;`
|
||||
|
||||
The context object also contains a number of easily accessed properties, as well as easy access to the full [GitHub payload](https://developer.github.com/v3/activity/events/types/). We can use this to check and make sure we're actually looking at a recently opened issue (and not something else, like a comment on an existing issue):
|
||||
|
||||
```ts
|
||||
if (github.context.payload.action !== 'opened') {
|
||||
console.log('No issue or PR was opened, skipping');
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
Our whole `src/main.ts` file now looks like:
|
||||
|
||||
```ts
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
const welcomeMessage: string = core.getInput('welcome-message', {required: true});
|
||||
const repoToken: string = core.getInput('repo-token', {required: true});
|
||||
const issue: {owner: string; repo: string; number: number} = github.context.issue;
|
||||
|
||||
if (github.context.payload.action !== 'opened') {
|
||||
console.log('No issue or pull request was opened, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - make request to the GitHub API to comment on the issue
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
### Sending requests to the GitHub API
|
||||
|
||||
Now that we have our context data, we are able to send a request to the GitHub API using the [Octokit REST client](https://github.com/octokit/rest.js). The REST client exposes a number of easy convenience functions, including one for adding comments to issues/PRs (issues and PRs are treated as one concept by the Octokit client):
|
||||
|
||||
```ts
|
||||
const client: github.GitHub = new github.GitHub(repoToken);
|
||||
await client.issues.createComment({
|
||||
owner: issue.owner,
|
||||
repo: issue.repo,
|
||||
issue_number: issue.number,
|
||||
body: welcomeMessage
|
||||
});
|
||||
```
|
||||
|
||||
For more docs on the client, you can visit the [Octokit REST documentation](https://octokit.github.io/rest.js/). Now our action code should be complete:
|
||||
|
||||
```ts
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
const welcomeMessage: string = core.getInput('welcome-message', {required: true});
|
||||
const repoToken: string = core.getInput('repo-token', {required: true});
|
||||
const issue: {owner: string; repo: string; number: number} = github.context.issue;
|
||||
|
||||
if (github.context.payload.action !== 'opened') {
|
||||
console.log('No issue or pull request was opened, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
const client: github.GitHub = new github.GitHub(repoToken);
|
||||
await client.issues.createComment({
|
||||
owner: issue.owner,
|
||||
repo: issue.repo,
|
||||
issue_number: issue.number,
|
||||
body: welcomeMessage
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
## Writing unit tests for your action
|
||||
|
||||
Next, we're going to write a basic unit test for our action using jest. If you followed the [javascript walkthrough](./javascript-action.md), you should have a file `__tests__/main.test.ts` that runs tests when `npm test` is called. We're going to start by populating that with one test:
|
||||
|
||||
```ts
|
||||
const nock = require('nock');
|
||||
const path = require('path');
|
||||
|
||||
describe('action test suite', () => {
|
||||
it('It posts a comment on an opened issue', async () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
For the purposes of this walkthrough, we'll focus on populating this test and leave the remaining test coverage as an exercise for the reader.
|
||||
|
||||
### Mocking inputs
|
||||
|
||||
First, we want to make sure that we can mock our inputs (welcome-message, and repo-token). Actions handles inputs by populating process.env.INPUT_${input name in all caps}, so we can mock that simply by setting those environment variables:
|
||||
|
||||
```ts
|
||||
const nock = require('nock');
|
||||
const path = require('path');
|
||||
|
||||
describe('action test suite', () => {
|
||||
it('It posts a comment on an opened issue', async () => {
|
||||
const welcomeMessage = 'hello';
|
||||
const repoToken = 'token';
|
||||
process.env['INPUT_WELCOME-MESSAGE'] = welcomeMessage;
|
||||
process.env['INPUT_REPO-TOKEN'] = repoToken;
|
||||
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking the GitHub context
|
||||
|
||||
Mocking the GitHub context is relatively straightforward. Since most of it is simply populated by environment variables, you can just set the corresponding environment variables defined [here](https://github.com/actions/toolkit/blob/ac007c06984bc483fae2ba649788dfc858bc6a8b/packages/github/src/context.ts#L23) and test that it works in that environment. In this case, we can setup our test with:
|
||||
|
||||
```ts
|
||||
const nock = require('nock');
|
||||
const path = require('path');
|
||||
|
||||
describe('action test suite', () => {
|
||||
it('It posts a comment on an opened issue', async () => {
|
||||
const welcomeMessage = 'hello';
|
||||
const repoToken = 'token';
|
||||
process.env['INPUT_WELCOME-MESSAGE'] = welcomeMessage;
|
||||
process.env['INPUT_REPO-TOKEN'] = repoToken;
|
||||
|
||||
process.env['GITHUB_REPOSITORY'] = 'foo/bar';
|
||||
process.env['GITHUB_EVENT_PATH'] = path.join(__dirname, 'payload.json');
|
||||
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Note that the payload is loaded from GITHUB_EVENT_PATH. Since we set that to `path.join(__dirname, 'payload.json')`, we need to go save our payload there. For the purposes of this test, we can simply save the following to `__tests__/payload.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"issue": {
|
||||
"number": 10
|
||||
},
|
||||
"action": "opened"
|
||||
}
|
||||
```
|
||||
|
||||
Now, calling `github.context.issue` should return `{owner: foo, repo: bar, number: 10}`, and `github.context.payload.action` should get set to 'opened'
|
||||
|
||||
> One important detail here is that because the GitHub context loads these environment variables as soon as it is required, you should set them before you require your action. In most cases, this means you need to rerequire your action in every test. If this is a problem, you can get around it by mocking the class directly using jest (or whatever framework you choose).
|
||||
|
||||
### Mocking the Octokit Client
|
||||
|
||||
To mock the client calls, we recommend using [nock](https://github.com/nock/nock) which allows you to mock the http requests made by the client. First, install nock with `npm install nock --save-dev`.
|
||||
|
||||
For this test, we expect the following call:
|
||||
|
||||
```ts
|
||||
client.issues.createComment({
|
||||
owner: 'foo',
|
||||
repo: 'bar',
|
||||
issue_number: 10,
|
||||
body: 'you posted your first issue'
|
||||
});
|
||||
```
|
||||
|
||||
From [the GitHub endpoint docs](https://developer.github.com/v3/issues/comments/#create-a-comment), we expect this to get make a POST request to `https://api.github.com/repos/foo/bar/issues/10/comments` with body of `{"body":"hello"}`
|
||||
|
||||
We can mock this with:
|
||||
|
||||
```ts
|
||||
const nock = require('nock');
|
||||
const path = require('path');
|
||||
|
||||
describe('action test suite', () => {
|
||||
it('It posts a comment on an opened issue', async () => {
|
||||
const welcomeMessage = 'hello';
|
||||
const repoToken = 'token';
|
||||
process.env['INPUT_WELCOME-MESSAGE'] = welcomeMessage;
|
||||
process.env['INPUT_REPO-TOKEN'] = repoToken;
|
||||
|
||||
process.env['GITHUB_REPOSITORY'] = 'foo/bar';
|
||||
process.env['GITHUB_EVENT_PATH'] = path.join(__dirname, 'payload.json');
|
||||
|
||||
nock('https://api.github.com')
|
||||
.persist()
|
||||
.post('/repos/foo/bar/issues/10/comments', '{\"body\":\"hello\"}')
|
||||
.reply(200);
|
||||
|
||||
const main = require('../src/main');
|
||||
|
||||
await main.run();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This will fail if the url or body doesn't exactly match the parameters passed into the nock function. We can now run `npm test` and the test should succeed.
|
||||
|
||||
## Build and publish
|
||||
|
||||
Now that we've written and unit tested our action, we can build our action with `npm run build` and push it to a repo where it can be consumed by workflows. For more info on versioning your action, see [our versioning docs](./action-versioning.md).
|
||||
|
||||
## Next steps
|
||||
|
||||
If you're interested in building out this action further, try extending your action to only run on a user's first issue. See our [first-contribution action](https://github.com/actions/first-interaction) for inspiration.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Problem Matchers
|
||||
Problem Matchers are a way to scan the output of actions for a specified regex pattern and surface that information prominently in the UI. Both [GitHub Annotations](https://developer.github.com/v3/checks/runs/#annotations-object-1) and log file decorations are created when a match is detected.
|
||||
|
||||
## Single Line Matchers
|
||||
|
||||
Let's consider the ESLint compact output:
|
||||
```
|
||||
badFile.js: line 50, col 11, Error - 'myVar' is defined but never used. (no-unused-vars)
|
||||
```
|
||||
We can define a problem matcher in json that detects input in that format:
|
||||
```json
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "eslint-compact",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5,
|
||||
"code": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The following fields are available for problem matchers:
|
||||
|
||||
```
|
||||
{
|
||||
owner: An ID field that can be used to remove or replace the problem matcher. **required**
|
||||
pattern: [
|
||||
{
|
||||
regexp: The regex pattern that provides the groups to match against **required**
|
||||
file: a group number containing the file name
|
||||
line: a group number containing the line number
|
||||
column: a group number containing the column information
|
||||
severity: a group number containing either 'warning' or 'error' case-insensitive. Defaults to `error`
|
||||
code: a group number containing the error code
|
||||
message: a group number containing the error message. **required** at least one pattern must set the message
|
||||
loop: loops until a match is not found, only valid on the last pattern of a multipattern matcher
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Multiline Matching
|
||||
Consider the following output:
|
||||
```
|
||||
test.js
|
||||
1:0 error Missing "use strict" statement strict
|
||||
5:10 error 'addOne' is defined but never used no-unused-vars
|
||||
✖ 2 problems (2 errors, 0 warnings)
|
||||
```
|
||||
The file name is printed once, yet multiple error lines are printed. The `loop` keyword provides a way to discover multiple errors in outputs.
|
||||
|
||||
The eslint-stylish problem matcher defined below catches that output, and creates two annotations from it.
|
||||
|
||||
```
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "eslint-stylish",
|
||||
"pattern": [
|
||||
{
|
||||
// Matches the 1st line in the output
|
||||
"regexp": "^([^\\s].*)$",
|
||||
"file": 1
|
||||
},
|
||||
{
|
||||
// Matches the 2nd and 3rd line in the output
|
||||
"regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$",
|
||||
// File is carried through from above, so we define the rest of the groups
|
||||
"line": 1,
|
||||
"column": 2,
|
||||
"severity": 3,
|
||||
"message": 4,
|
||||
"code": 5,
|
||||
"loop": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The first pattern matches the `test.js` line and records the file information. This line is not decorated in the UI.
|
||||
The second pattern loops through the remaining lines with `loop: true` until it fails to find a match, and surfaces these lines prominently in the UI.
|
||||
|
||||
## Adding and Removing Problem Matchers
|
||||
Problem Matchers are enabled and removed via the toolkit [commands](commands.md#problem-matchers).
|
||||
|
||||
## Duplicate Problem Matchers
|
||||
Registering two problem-matchers with the same owner will result in only the problem matcher registered last running.
|
||||
|
||||
## Examples
|
||||
Some of the starter actions are already using problem matchers, for example:
|
||||
- [setup-node](https://github.com/actions/setup-node/tree/master/.github)
|
||||
- [setup-python](https://github.com/actions/setup-python/tree/master/.github)
|
||||
- [setup-go](https://github.com/actions/setup-go/tree/master/.github)
|
||||
- [setup-dotnet](https://github.com/actions/setup-dotnet/tree/master/.github)
|
||||
@@ -0,0 +1,100 @@
|
||||
# Github Package
|
||||
|
||||
In order to support using actions to interact with GitHub, I propose adding a `github` package to the toolkit.
|
||||
|
||||
Its main purpose will be to provide a hydrated GitHub context/Octokit client with some convenience functions. It is largely pulled from the GitHub utilities provided in https://github.com/JasonEtco/actions-toolkit, though it has been condensed.
|
||||
|
||||
### Spec
|
||||
|
||||
##### interfaces.ts
|
||||
|
||||
```ts
|
||||
/*
|
||||
* Interfaces
|
||||
*/
|
||||
|
||||
export interface PayloadRepository {
|
||||
[key: string]: any
|
||||
full_name?: string
|
||||
name: string
|
||||
owner: {
|
||||
[key: string]: any
|
||||
login: string
|
||||
name?: string
|
||||
}
|
||||
html_url?: string
|
||||
}
|
||||
|
||||
export interface WebhookPayloadWithRepository {
|
||||
[key: string]: any
|
||||
repository?: PayloadRepository
|
||||
issue?: {
|
||||
[key: string]: any
|
||||
number: number
|
||||
html_url?: string
|
||||
body?: string
|
||||
}
|
||||
pull_request?: {
|
||||
[key: string]: any
|
||||
number: number
|
||||
html_url?: string
|
||||
body?: string
|
||||
}
|
||||
sender?: {
|
||||
[key: string]: any
|
||||
type: string
|
||||
}
|
||||
action?: string
|
||||
installation?: {
|
||||
id: number
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### context.ts
|
||||
|
||||
Contains a GitHub context
|
||||
|
||||
```ts
|
||||
export class Context {
|
||||
/**
|
||||
* Webhook payload object that triggered the workflow
|
||||
*/
|
||||
public payload: WebhookPayloadWithRepository
|
||||
|
||||
/**
|
||||
* Name of the event that triggered the workflow
|
||||
*/
|
||||
public event: string
|
||||
public sha: string
|
||||
public ref: string
|
||||
public workflow: string
|
||||
public action: string
|
||||
public actor: string
|
||||
|
||||
/**
|
||||
* Hydrate the context from the environment
|
||||
*/
|
||||
constructor ()
|
||||
|
||||
public get issue ()
|
||||
|
||||
public get repo ()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
##### github.ts
|
||||
|
||||
Contains a hydrated Octokit client
|
||||
|
||||
```ts
|
||||
export class GithubClient extends Octokit {
|
||||
// For making GraphQL requests
|
||||
public graphql: (query: string, variables?: Variables) => Promise<GraphQlQueryResponse>
|
||||
|
||||
// Calls super and initializes graphql
|
||||
constructor (token: string)
|
||||
}
|
||||
```
|
||||
@@ -6,7 +6,7 @@ In order to support the node-config action, I propose adding the following into
|
||||
|
||||
Holds all the functions necessary for interacting with the runner/environment.
|
||||
|
||||
```
|
||||
```ts
|
||||
// Logging functions
|
||||
export function debug(message: string): void
|
||||
export function warning(message: string): void
|
||||
@@ -66,7 +66,7 @@ export function setFailed(message: string): void
|
||||
|
||||
Holds all the functions necessary for file system manipulation (cli scenarios, not fs replacements):
|
||||
|
||||
```
|
||||
```ts
|
||||
/**
|
||||
* Interface for cp/mv options
|
||||
*/
|
||||
@@ -132,7 +132,7 @@ export function which(tool: string, options?: WhichOptions): Promise<string>
|
||||
|
||||
Holds all the functions necessary for running the tools node-config depends on (aka 7-zip and tar)
|
||||
|
||||
```
|
||||
```ts
|
||||
/**
|
||||
* Interface for exec options
|
||||
*/
|
||||
@@ -155,7 +155,7 @@ export function exec(commandLine: string, args?: string[], options?: IExecOption
|
||||
|
||||
Holds all the functions necessary for downloading and caching node.
|
||||
|
||||
```
|
||||
```ts
|
||||
/**
|
||||
* Download a tool from an url and stream it into a file
|
||||
*
|
||||
Generated
+4958
-1429
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -20,11 +20,11 @@
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-github": "^2.0.0",
|
||||
"eslint-plugin-jest": "^22.5.1",
|
||||
"jest": "^24.7.1",
|
||||
"jest": "^24.9.0",
|
||||
"jest-circus": "^24.7.1",
|
||||
"lerna": "^3.13.3",
|
||||
"lerna": "^3.18.4",
|
||||
"prettier": "^1.17.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"typescript": "^3.4.4"
|
||||
"typescript": "^3.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
+134
-1
@@ -4,4 +4,137 @@
|
||||
|
||||
## Usage
|
||||
|
||||
See [src/core.ts](src/core.ts).
|
||||
### Import the package
|
||||
|
||||
```js
|
||||
// javascript
|
||||
const core = require('@actions/core');
|
||||
|
||||
// typescript
|
||||
import * as core from '@actions/core';
|
||||
```
|
||||
|
||||
#### Inputs/Outputs
|
||||
|
||||
Action inputs can be read with `getInput`. Outputs can be set with `setOutput` which makes them available to be mapped into inputs of other actions to ensure they are decoupled.
|
||||
|
||||
```js
|
||||
const myInput = core.getInput('inputName', { required: true });
|
||||
|
||||
core.setOutput('outputKey', 'outputVal');
|
||||
```
|
||||
|
||||
#### Exporting variables
|
||||
|
||||
Since each step runs in a separate process, you can use `exportVariable` to add it to this step and future steps environment blocks.
|
||||
|
||||
```js
|
||||
core.exportVariable('envVar', 'Val');
|
||||
```
|
||||
|
||||
#### Setting a secret
|
||||
|
||||
Setting a secret registers the secret with the runner to ensure it is masked in logs.
|
||||
|
||||
```js
|
||||
core.setSecret('myPassword');
|
||||
```
|
||||
|
||||
#### PATH Manipulation
|
||||
|
||||
To make a tool's path available in the path for the remainder of the job (without altering the machine or containers state), use `addPath`. The runner will prepend the path given to the jobs PATH.
|
||||
|
||||
```js
|
||||
core.addPath('/path/to/mytool');
|
||||
```
|
||||
|
||||
#### Exit codes
|
||||
|
||||
You should use this library to set the failing exit code for your action. If status is not set and the script runs to completion, that will lead to a success.
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
try {
|
||||
// Do stuff
|
||||
}
|
||||
catch (err) {
|
||||
// setFailed logs the message and sets a failing exit code
|
||||
core.setFailed(`Action failed with error ${err}`);
|
||||
}
|
||||
|
||||
Note that `setNeutral` is not yet implemented in actions V2 but equivalent functionality is being planned.
|
||||
|
||||
```
|
||||
|
||||
#### Logging
|
||||
|
||||
Finally, this library provides some utilities for logging. Note that debug logging is hidden from the logs by default. This behavior can be toggled by enabling the [Step Debug Logs](../../docs/action-debugging.md#step-debug-logs).
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
const myInput = core.getInput('input');
|
||||
try {
|
||||
core.debug('Inside try block');
|
||||
|
||||
if (!myInput) {
|
||||
core.warning('myInput was not set');
|
||||
}
|
||||
|
||||
// Do stuff
|
||||
}
|
||||
catch (err) {
|
||||
core.error(`Error ${err}, action may still succeed though`);
|
||||
}
|
||||
```
|
||||
|
||||
This library can also wrap chunks of output in foldable groups.
|
||||
|
||||
```js
|
||||
const core = require('@actions/core')
|
||||
|
||||
// Manually wrap output
|
||||
core.startGroup('Do some function')
|
||||
doSomeFunction()
|
||||
core.endGroup()
|
||||
|
||||
// Wrap an asynchronous function call
|
||||
const result = await core.group('Do something async', async () => {
|
||||
const response = await doSomeHTTPRequest()
|
||||
return response
|
||||
})
|
||||
```
|
||||
|
||||
#### Action state
|
||||
|
||||
You can use this library to save state and get state for sharing information between a given wrapper action:
|
||||
|
||||
**action.yml**
|
||||
```yaml
|
||||
name: 'Wrapper action sample'
|
||||
inputs:
|
||||
name:
|
||||
default: 'GitHub'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'main.js'
|
||||
post: 'cleanup.js'
|
||||
```
|
||||
|
||||
In action's `main.js`:
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
core.saveState("pidToKill", 12345);
|
||||
```
|
||||
|
||||
In action's `cleanup.js`:
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
var pid = core.getState("pidToKill");
|
||||
|
||||
process.kill(pid);
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
# @actions/core Releases
|
||||
|
||||
### 1.2.0
|
||||
|
||||
- saveState and getState functions for wrapper tasks (on finally entry points that run post job)
|
||||
|
||||
### 1.1.3
|
||||
|
||||
- setSecret added to register a secret with the runner to be masked from the logs
|
||||
- exportSecret which was not implemented and never worked was removed after clarification from product.
|
||||
|
||||
### 1.1.1
|
||||
|
||||
- Add support for action input variables with multiple spaces [#127](https://github.com/actions/toolkit/issues/127)
|
||||
- Switched ## commands to :: commands (should have no noticeable impact) [#110)(https://github.com/actions/toolkit/pull/110)
|
||||
|
||||
### 1.1.0
|
||||
|
||||
- Added helpers for `group` and `endgroup` [#98](https://github.com/actions/toolkit/pull/98)
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Initial release
|
||||
@@ -1,4 +1,3 @@
|
||||
import {ExitCode} from '@actions/exit'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import * as core from '../src/core'
|
||||
@@ -17,7 +16,11 @@ const testEnvVars = {
|
||||
// Set inputs
|
||||
INPUT_MY_INPUT: 'val',
|
||||
INPUT_MISSING: '',
|
||||
'INPUT_SPECIAL_CHARS_\'\t"\\': '\'\t"\\ repsonse '
|
||||
'INPUT_SPECIAL_CHARS_\'\t"\\': '\'\t"\\ response ',
|
||||
INPUT_MULTIPLE_SPACES_VARIABLE: 'I have multiple spaces',
|
||||
|
||||
// Save inputs
|
||||
STATE_TEST_1: 'state_val'
|
||||
}
|
||||
|
||||
describe('@actions/core', () => {
|
||||
@@ -34,50 +37,26 @@ describe('@actions/core', () => {
|
||||
|
||||
it('exportVariable produces the correct command and sets the env', () => {
|
||||
core.exportVariable('my var', 'var val')
|
||||
assertWriteCalls([`##[set-env name=my var;]var val${os.EOL}`])
|
||||
assertWriteCalls([`::set-env name=my var,::var val${os.EOL}`])
|
||||
})
|
||||
|
||||
it('exportVariable escapes variable names', () => {
|
||||
core.exportVariable('special char var \r\n];', 'special val')
|
||||
expect(process.env['special char var \r\n];']).toBe('special val')
|
||||
assertWriteCalls([
|
||||
`##[set-env name=special char var %0D%0A%5D%3B;]special val${os.EOL}`
|
||||
`::set-env name=special char var %0D%0A%5D%3B,::special val${os.EOL}`
|
||||
])
|
||||
})
|
||||
|
||||
it('exportVariable escapes variable values', () => {
|
||||
core.exportVariable('my var2', 'var val\r\n')
|
||||
expect(process.env['my var2']).toBe('var val\r\n')
|
||||
assertWriteCalls([`##[set-env name=my var2;]var val%0D%0A${os.EOL}`])
|
||||
assertWriteCalls([`::set-env name=my var2,::var val%0D%0A${os.EOL}`])
|
||||
})
|
||||
|
||||
it('exportSecret produces the correct commands and sets the env', () => {
|
||||
core.exportSecret('my secret', 'secret val')
|
||||
expect(process.env['my secret']).toBe('secret val')
|
||||
assertWriteCalls([
|
||||
`##[set-env name=my secret;]secret val${os.EOL}`,
|
||||
`##[set-secret]secret val${os.EOL}`
|
||||
])
|
||||
})
|
||||
|
||||
it('exportSecret escapes secret names', () => {
|
||||
core.exportSecret('special char secret \r\n];', 'special secret val')
|
||||
expect(process.env['special char secret \r\n];']).toBe('special secret val')
|
||||
assertWriteCalls([
|
||||
`##[set-env name=special char secret %0D%0A%5D%3B;]special secret val${
|
||||
os.EOL
|
||||
}`,
|
||||
`##[set-secret]special secret val${os.EOL}`
|
||||
])
|
||||
})
|
||||
|
||||
it('exportSecret escapes secret values', () => {
|
||||
core.exportSecret('my secret2', 'secret val\r\n')
|
||||
expect(process.env['my secret2']).toBe('secret val\r\n')
|
||||
assertWriteCalls([
|
||||
`##[set-env name=my secret2;]secret val%0D%0A${os.EOL}`,
|
||||
`##[set-secret]secret val%0D%0A${os.EOL}`
|
||||
])
|
||||
it('setSecret produces the correct command', () => {
|
||||
core.setSecret('secret val')
|
||||
assertWriteCalls([`::add-mask::secret val${os.EOL}`])
|
||||
})
|
||||
|
||||
it('prependPath produces the correct commands and sets the env', () => {
|
||||
@@ -85,7 +64,7 @@ describe('@actions/core', () => {
|
||||
expect(process.env['PATH']).toBe(
|
||||
`myPath${path.delimiter}path1${path.delimiter}path2`
|
||||
)
|
||||
assertWriteCalls([`##[add-path]myPath${os.EOL}`])
|
||||
assertWriteCalls([`::add-path::myPath${os.EOL}`])
|
||||
})
|
||||
|
||||
it('getInput gets non-required input', () => {
|
||||
@@ -102,7 +81,7 @@ describe('@actions/core', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('getInput doesnt throw on missing non-required input', () => {
|
||||
it('getInput does not throw on missing non-required input', () => {
|
||||
expect(core.getInput('missing', {required: false})).toBe('')
|
||||
})
|
||||
|
||||
@@ -111,59 +90,92 @@ describe('@actions/core', () => {
|
||||
})
|
||||
|
||||
it('getInput handles special characters', () => {
|
||||
expect(core.getInput('special chars_\'\t"\\')).toBe('\'\t"\\ repsonse')
|
||||
expect(core.getInput('special chars_\'\t"\\')).toBe('\'\t"\\ response')
|
||||
})
|
||||
|
||||
it('getInput handles multiple spaces', () => {
|
||||
expect(core.getInput('multiple spaces variable')).toBe(
|
||||
'I have multiple spaces'
|
||||
)
|
||||
})
|
||||
|
||||
it('setOutput produces the correct command', () => {
|
||||
core.setOutput('some output', 'some value')
|
||||
assertWriteCalls([`##[set-output name=some output;]some value${os.EOL}`])
|
||||
})
|
||||
|
||||
it('setNeutral sets the correct exit code', () => {
|
||||
core.setFailed('Failure message')
|
||||
expect(process.exitCode).toBe(ExitCode.Failure)
|
||||
assertWriteCalls([`::set-output name=some output,::some value${os.EOL}`])
|
||||
})
|
||||
|
||||
it('setFailure sets the correct exit code and failure message', () => {
|
||||
core.setFailed('Failure message')
|
||||
expect(process.exitCode).toBe(ExitCode.Failure)
|
||||
assertWriteCalls([`##[error]Failure message${os.EOL}`])
|
||||
expect(process.exitCode).toBe(core.ExitCode.Failure)
|
||||
assertWriteCalls([`::error::Failure message${os.EOL}`])
|
||||
})
|
||||
|
||||
it('setFailure escapes the failure message', () => {
|
||||
core.setFailed('Failure \r\n\nmessage\r')
|
||||
expect(process.exitCode).toBe(ExitCode.Failure)
|
||||
assertWriteCalls([`##[error]Failure %0D%0A%0Amessage%0D${os.EOL}`])
|
||||
expect(process.exitCode).toBe(core.ExitCode.Failure)
|
||||
assertWriteCalls([`::error::Failure %0D%0A%0Amessage%0D${os.EOL}`])
|
||||
})
|
||||
|
||||
it('error sets the correct error message', () => {
|
||||
core.error('Error message')
|
||||
assertWriteCalls([`##[error]Error message${os.EOL}`])
|
||||
assertWriteCalls([`::error::Error message${os.EOL}`])
|
||||
})
|
||||
|
||||
it('error escapes the error message', () => {
|
||||
core.error('Error message\r\n\n')
|
||||
assertWriteCalls([`##[error]Error message%0D%0A%0A${os.EOL}`])
|
||||
assertWriteCalls([`::error::Error message%0D%0A%0A${os.EOL}`])
|
||||
})
|
||||
|
||||
it('warning sets the correct message', () => {
|
||||
core.warning('Warning')
|
||||
assertWriteCalls([`##[warning]Warning${os.EOL}`])
|
||||
assertWriteCalls([`::warning::Warning${os.EOL}`])
|
||||
})
|
||||
|
||||
it('warning escapes the message', () => {
|
||||
core.warning('\r\nwarning\n')
|
||||
assertWriteCalls([`##[warning]%0D%0Awarning%0A${os.EOL}`])
|
||||
assertWriteCalls([`::warning::%0D%0Awarning%0A${os.EOL}`])
|
||||
})
|
||||
|
||||
it('startGroup starts a new group', () => {
|
||||
core.startGroup('my-group')
|
||||
assertWriteCalls([`::group::my-group${os.EOL}`])
|
||||
})
|
||||
|
||||
it('endGroup ends new group', () => {
|
||||
core.endGroup()
|
||||
assertWriteCalls([`::endgroup::${os.EOL}`])
|
||||
})
|
||||
|
||||
it('group wraps an async call in a group', async () => {
|
||||
const result = await core.group('mygroup', async () => {
|
||||
process.stdout.write('in my group\n')
|
||||
return true
|
||||
})
|
||||
expect(result).toBe(true)
|
||||
assertWriteCalls([
|
||||
`::group::mygroup${os.EOL}`,
|
||||
'in my group\n',
|
||||
`::endgroup::${os.EOL}`
|
||||
])
|
||||
})
|
||||
|
||||
it('debug sets the correct message', () => {
|
||||
core.debug('Debug')
|
||||
assertWriteCalls([`##[debug]Debug${os.EOL}`])
|
||||
assertWriteCalls([`::debug::Debug${os.EOL}`])
|
||||
})
|
||||
|
||||
it('debug escapes the message', () => {
|
||||
core.debug('\r\ndebug\n')
|
||||
assertWriteCalls([`##[debug]%0D%0Adebug%0A${os.EOL}`])
|
||||
assertWriteCalls([`::debug::%0D%0Adebug%0A${os.EOL}`])
|
||||
})
|
||||
|
||||
it('saveState produces the correct command', () => {
|
||||
core.saveState('state_1', 'some value')
|
||||
assertWriteCalls([`::save-state name=state_1,::some value${os.EOL}`])
|
||||
})
|
||||
|
||||
it('getState gets wrapper action state', () => {
|
||||
expect(core.getState('TEST_1')).toBe('state_val')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/core",
|
||||
"version": "0.0.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@actions/core",
|
||||
"version": "0.0.0",
|
||||
"version": "1.2.0",
|
||||
"description": "Actions core lib",
|
||||
"keywords": [
|
||||
"core",
|
||||
"actions"
|
||||
"github",
|
||||
"actions",
|
||||
"core"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/core",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +22,8 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
"url": "git+https://github.com/actions/toolkit.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
@@ -32,8 +34,5 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/exit": "^0.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ interface CommandProperties {
|
||||
*
|
||||
* Examples:
|
||||
* ##[warning]This is the user warning message
|
||||
* ##[set-secret name=mypassword]definatelyNotAPassword!
|
||||
* ##[set-secret name=mypassword]definitelyNotAPassword!
|
||||
*/
|
||||
export function issueCommand(
|
||||
command: string,
|
||||
@@ -25,11 +25,11 @@ export function issueCommand(
|
||||
process.stdout.write(cmd.toString() + os.EOL)
|
||||
}
|
||||
|
||||
export function issue(name: string, message: string): void {
|
||||
export function issue(name: string, message: string = ''): void {
|
||||
issueCommand(name, {}, message)
|
||||
}
|
||||
|
||||
const CMD_PREFIX = '##['
|
||||
const CMD_STRING = '::'
|
||||
|
||||
class Command {
|
||||
private readonly command: string
|
||||
@@ -47,7 +47,7 @@ class Command {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let cmdStr = CMD_PREFIX + this.command
|
||||
let cmdStr = CMD_STRING + this.command
|
||||
|
||||
if (this.properties && Object.keys(this.properties).length > 0) {
|
||||
cmdStr += ' '
|
||||
@@ -57,13 +57,13 @@ class Command {
|
||||
if (val) {
|
||||
// safely append the val - avoid blowing up when attempting to
|
||||
// call .replace() if message is not a string for some reason
|
||||
cmdStr += `${key}=${escape(`${val || ''}`)};`
|
||||
cmdStr += `${key}=${escape(`${val || ''}`)},`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmdStr += ']'
|
||||
cmdStr += CMD_STRING
|
||||
|
||||
// safely append the message - avoid blowing up when attempting to
|
||||
// call .replace() if message is not a string for some reason
|
||||
|
||||
+94
-16
@@ -1,6 +1,6 @@
|
||||
import {ExitCode} from '@actions/exit'
|
||||
import {issue, issueCommand} from './command'
|
||||
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
|
||||
/**
|
||||
@@ -11,12 +11,27 @@ export interface InputOptions {
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The code to exit an action
|
||||
*/
|
||||
export enum ExitCode {
|
||||
/**
|
||||
* A code indicating that the action was successful
|
||||
*/
|
||||
Success = 0,
|
||||
|
||||
/**
|
||||
* A code indicating that the action was a failure
|
||||
*/
|
||||
Failure = 1
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Variables
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* sets env variable for this action and future actions in the job
|
||||
* Sets env variable for this action and future actions in the job
|
||||
* @param name the name of the variable to set
|
||||
* @param val the value of the variable
|
||||
*/
|
||||
@@ -26,13 +41,11 @@ export function exportVariable(name: string, val: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* exports the variable and registers a secret which will get masked from logs
|
||||
* @param name the name of the variable to set
|
||||
* @param val value of the secret
|
||||
* Registers a secret which will get masked from logs
|
||||
* @param secret value of the secret
|
||||
*/
|
||||
export function exportSecret(name: string, val: string): void {
|
||||
exportVariable(name, val)
|
||||
issueCommand('set-secret', {}, val)
|
||||
export function setSecret(secret: string): void {
|
||||
issueCommand('add-mask', {}, secret)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +66,7 @@ export function addPath(inputPath: string): void {
|
||||
*/
|
||||
export function getInput(name: string, options?: InputOptions): string {
|
||||
const val: string =
|
||||
process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || ''
|
||||
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`)
|
||||
}
|
||||
@@ -75,13 +88,6 @@ export function setOutput(name: string, value: string): void {
|
||||
// Results
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sets the action status to neutral
|
||||
*/
|
||||
export function setNeutral(): void {
|
||||
process.exitCode = ExitCode.Neutral
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action status to failed.
|
||||
* When the action exits it will be with an exit code of 1
|
||||
@@ -119,3 +125,75 @@ export function error(message: string): void {
|
||||
export function warning(message: string): void {
|
||||
issue('warning', message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes info to log with console.log.
|
||||
* @param message info message
|
||||
*/
|
||||
export function info(message: string): void {
|
||||
process.stdout.write(message + os.EOL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin an output group.
|
||||
*
|
||||
* Output until the next `groupEnd` will be foldable in this group
|
||||
*
|
||||
* @param name The name of the output group
|
||||
*/
|
||||
export function startGroup(name: string): void {
|
||||
issue('group', name)
|
||||
}
|
||||
|
||||
/**
|
||||
* End an output group.
|
||||
*/
|
||||
export function endGroup(): void {
|
||||
issue('endgroup')
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an asynchronous function call in a group.
|
||||
*
|
||||
* Returns the same type as the function itself.
|
||||
*
|
||||
* @param name The name of the group
|
||||
* @param fn The function to wrap in the group
|
||||
*/
|
||||
export async function group<T>(name: string, fn: () => Promise<T>): Promise<T> {
|
||||
startGroup(name)
|
||||
|
||||
let result: T
|
||||
|
||||
try {
|
||||
result = await fn()
|
||||
} finally {
|
||||
endGroup()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Wrapper action state
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Saves state for current action, the state can only be retrieved by this action's post job execution.
|
||||
*
|
||||
* @param name name of the state to store
|
||||
* @param value value to store
|
||||
*/
|
||||
export function saveState(name: string, value: string): void {
|
||||
issueCommand('save-state', {name}, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of an state set by this action's main execution.
|
||||
*
|
||||
* @param name name of the state to get
|
||||
* @returns string
|
||||
*/
|
||||
export function getState(name: string): string {
|
||||
return process.env[`STATE_${name}`] || ''
|
||||
}
|
||||
|
||||
+53
-3
@@ -1,7 +1,57 @@
|
||||
# `@actions/exec`
|
||||
|
||||
> Functions necessary for running tools on the command line
|
||||
|
||||
## Usage
|
||||
|
||||
See [src/exec.ts](src/exec.ts).
|
||||
#### Basic
|
||||
|
||||
You can use this package to execute your tools on the command line in a cross platform way:
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
await exec.exec('node index.js');
|
||||
```
|
||||
|
||||
#### Args
|
||||
|
||||
You can also pass in arg arrays:
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
await exec.exec('node', ['index.js', 'foo=bar']);
|
||||
```
|
||||
|
||||
#### Output/options
|
||||
|
||||
Capture output or specify [other options](https://github.com/actions/toolkit/blob/d9347d4ab99fd507c0b9104b2cf79fb44fcc827d/packages/exec/src/interfaces.ts#L5):
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
let myOutput = '';
|
||||
let myError = '';
|
||||
|
||||
const options = {};
|
||||
options.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
myOutput += data.toString();
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
myError += data.toString();
|
||||
}
|
||||
};
|
||||
options.cwd = './lib';
|
||||
|
||||
await exec.exec('node', ['index.js', 'foo=bar'], options);
|
||||
```
|
||||
|
||||
#### Exec tools not in the PATH
|
||||
|
||||
You can specify the full path for tools not in the PATH:
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
await exec.exec('"/path/to/my-tool"', ['arg1']);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# @actions/exec Releases
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Initial release
|
||||
@@ -12,14 +12,19 @@ import * as io from '@actions/io'
|
||||
|
||||
const IS_WINDOWS = process.platform === 'win32'
|
||||
|
||||
let outstream: stream.Writable
|
||||
let errstream: stream.Writable
|
||||
|
||||
describe('@actions/exec', () => {
|
||||
beforeAll(() => {
|
||||
io.mkdirP(getTestTemp())
|
||||
outstream = fs.createWriteStream(path.join(getTestTemp(), 'my.log'))
|
||||
errstream = fs.createWriteStream(path.join(getTestTemp(), 'myerr.log'))
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
process.stdout.write = jest.fn()
|
||||
process.stderr.write = jest.fn()
|
||||
outstream.write = jest.fn()
|
||||
errstream.write = jest.fn()
|
||||
})
|
||||
|
||||
it('Runs exec successfully with arguments split out', async () => {
|
||||
@@ -45,12 +50,12 @@ describe('@actions/exec', () => {
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
if (IS_WINDOWS) {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} /c echo hello${os.EOL}`
|
||||
)
|
||||
expect(process.stdout.write).toBeCalledWith(new Buffer(`hello${os.EOL}`))
|
||||
expect(outstream.write).toBeCalledWith(new Buffer(`hello${os.EOL}`))
|
||||
} else {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} -l -a${os.EOL}`
|
||||
)
|
||||
}
|
||||
@@ -75,12 +80,12 @@ describe('@actions/exec', () => {
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
if (IS_WINDOWS) {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} /c echo hello${os.EOL}`
|
||||
)
|
||||
expect(process.stdout.write).toBeCalledWith(new Buffer(`hello${os.EOL}`))
|
||||
expect(outstream.write).toBeCalledWith(new Buffer(`hello${os.EOL}`))
|
||||
} else {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} -l -a${os.EOL}`
|
||||
)
|
||||
}
|
||||
@@ -105,17 +110,49 @@ describe('@actions/exec', () => {
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
if (IS_WINDOWS) {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} /c echo hello${os.EOL}`
|
||||
)
|
||||
expect(process.stdout.write).toBeCalledWith(new Buffer(`hello${os.EOL}`))
|
||||
expect(outstream.write).toBeCalledWith(new Buffer(`hello${os.EOL}`))
|
||||
} else {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} -l -a${os.EOL}`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('Runs exec successfully with command from PATH', async () => {
|
||||
const execOptions = getExecOptions()
|
||||
const outStream = new StringStream()
|
||||
execOptions.outStream = outStream
|
||||
let output = ''
|
||||
execOptions.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
let exitCode = 1
|
||||
let tool: string
|
||||
let args: string[]
|
||||
if (IS_WINDOWS) {
|
||||
tool = 'cmd'
|
||||
args = ['/c', 'echo', 'hello']
|
||||
} else {
|
||||
tool = 'sh'
|
||||
args = ['-c', 'echo hello']
|
||||
}
|
||||
|
||||
exitCode = await exec.exec(tool, args, execOptions)
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
const rootedTool = await io.which(tool, true)
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${rootedTool} ${args.join(' ')}`
|
||||
)
|
||||
expect(output.trim()).toBe(`hello`)
|
||||
})
|
||||
|
||||
it('Exec fails with error on bad call', async () => {
|
||||
const _testExecOptions = getExecOptions()
|
||||
|
||||
@@ -140,11 +177,11 @@ describe('@actions/exec', () => {
|
||||
|
||||
expect(failed).toBe(true)
|
||||
if (IS_WINDOWS) {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} /c non-existent${os.EOL}`
|
||||
)
|
||||
} else {
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
`[command]${toolpath} -l non-existent${os.EOL}`
|
||||
)
|
||||
}
|
||||
@@ -167,7 +204,7 @@ describe('@actions/exec', () => {
|
||||
)
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
expect(process.stdout.write).toBeCalledWith(
|
||||
expect(outstream.write).toBeCalledWith(
|
||||
new Buffer('this is output to stderr')
|
||||
)
|
||||
})
|
||||
@@ -191,7 +228,7 @@ describe('@actions/exec', () => {
|
||||
})
|
||||
|
||||
expect(failed).toBe(true)
|
||||
expect(process.stderr.write).toBeCalledWith(
|
||||
expect(errstream.write).toBeCalledWith(
|
||||
new Buffer('this is output to stderr')
|
||||
)
|
||||
})
|
||||
@@ -293,7 +330,7 @@ describe('@actions/exec', () => {
|
||||
).toBe(1)
|
||||
|
||||
fs.unlinkSync(semaphorePath)
|
||||
})
|
||||
}, 10000) // this was timing out on some slower hosted macOS runs at default 5s
|
||||
|
||||
it('Handles child process holding streams open and non-zero exit code', async function() {
|
||||
const semaphorePath = path.join(
|
||||
@@ -347,7 +384,7 @@ describe('@actions/exec', () => {
|
||||
).toBe(1)
|
||||
|
||||
fs.unlinkSync(semaphorePath)
|
||||
})
|
||||
}, 10000) // this was timing out on some slower hosted macOS runs at default 5s
|
||||
|
||||
it('Handles child process holding streams open and stderr', async function() {
|
||||
const semaphorePath = path.join(
|
||||
@@ -409,7 +446,165 @@ describe('@actions/exec', () => {
|
||||
fs.unlinkSync(semaphorePath)
|
||||
})
|
||||
|
||||
it('Exec roots relative tool path using unrooted options.cwd', async () => {
|
||||
let exitCode: number
|
||||
let command: string
|
||||
if (IS_WINDOWS) {
|
||||
command = './print-args-cmd' // let ToolRunner resolve the extension
|
||||
} else {
|
||||
command = './print-args-sh.sh'
|
||||
}
|
||||
const execOptions = getExecOptions()
|
||||
execOptions.cwd = 'scripts'
|
||||
const outStream = new StringStream()
|
||||
execOptions.outStream = outStream
|
||||
let output = ''
|
||||
execOptions.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
try {
|
||||
process.chdir(__dirname)
|
||||
exitCode = await exec.exec(`${command} hello world`, [], execOptions)
|
||||
} catch (err) {
|
||||
process.chdir(originalCwd)
|
||||
throw err
|
||||
}
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
const toolPath = path.resolve(
|
||||
__dirname,
|
||||
execOptions.cwd,
|
||||
`${command}${IS_WINDOWS ? '.cmd' : ''}`
|
||||
)
|
||||
if (IS_WINDOWS) {
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"`
|
||||
)
|
||||
} else {
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${toolPath} hello world`
|
||||
)
|
||||
}
|
||||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
||||
})
|
||||
|
||||
it('Exec roots relative tool path using rooted options.cwd', async () => {
|
||||
let command: string
|
||||
if (IS_WINDOWS) {
|
||||
command = './print-args-cmd' // let ToolRunner resolve the extension
|
||||
} else {
|
||||
command = './print-args-sh.sh'
|
||||
}
|
||||
const execOptions = getExecOptions()
|
||||
execOptions.cwd = path.join(__dirname, 'scripts')
|
||||
const outStream = new StringStream()
|
||||
execOptions.outStream = outStream
|
||||
let output = ''
|
||||
execOptions.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
const exitCode = await exec.exec(`${command} hello world`, [], execOptions)
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
const toolPath = path.resolve(
|
||||
__dirname,
|
||||
execOptions.cwd,
|
||||
`${command}${IS_WINDOWS ? '.cmd' : ''}`
|
||||
)
|
||||
if (IS_WINDOWS) {
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"`
|
||||
)
|
||||
} else {
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${toolPath} hello world`
|
||||
)
|
||||
}
|
||||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
||||
})
|
||||
|
||||
it('Exec roots relative tool path using process.cwd', async () => {
|
||||
let exitCode: number
|
||||
let command: string
|
||||
if (IS_WINDOWS) {
|
||||
command = 'scripts/print-args-cmd' // let ToolRunner resolve the extension
|
||||
} else {
|
||||
command = 'scripts/print-args-sh.sh'
|
||||
}
|
||||
const execOptions = getExecOptions()
|
||||
const outStream = new StringStream()
|
||||
execOptions.outStream = outStream
|
||||
let output = ''
|
||||
execOptions.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
try {
|
||||
process.chdir(__dirname)
|
||||
exitCode = await exec.exec(`${command} hello world`, [], execOptions)
|
||||
} catch (err) {
|
||||
process.chdir(originalCwd)
|
||||
throw err
|
||||
}
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
const toolPath = path.resolve(
|
||||
__dirname,
|
||||
`${command}${IS_WINDOWS ? '.cmd' : ''}`
|
||||
)
|
||||
if (IS_WINDOWS) {
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"`
|
||||
)
|
||||
} else {
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${toolPath} hello world`
|
||||
)
|
||||
}
|
||||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
||||
})
|
||||
|
||||
if (IS_WINDOWS) {
|
||||
it('Exec roots relative tool path using process.cwd (Windows path separator)', async () => {
|
||||
let exitCode: number
|
||||
const command = 'scripts\\print-args-cmd' // let ToolRunner resolve the extension
|
||||
const execOptions = getExecOptions()
|
||||
const outStream = new StringStream()
|
||||
execOptions.outStream = outStream
|
||||
let output = ''
|
||||
execOptions.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
try {
|
||||
process.chdir(__dirname)
|
||||
exitCode = await exec.exec(`${command} hello world`, [], execOptions)
|
||||
} catch (err) {
|
||||
process.chdir(originalCwd)
|
||||
throw err
|
||||
}
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
const toolPath = path.resolve(__dirname, `${command}.cmd`)
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"`
|
||||
)
|
||||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
||||
})
|
||||
|
||||
// Win specific quoting tests
|
||||
it('execs .exe with verbatim args (Windows)', async () => {
|
||||
const exePath = process.env.ComSpec
|
||||
@@ -496,7 +691,7 @@ describe('@actions/exec', () => {
|
||||
`[command]"${exePath}" myarg1 myarg2`
|
||||
)
|
||||
expect(output.trim()).toBe("args[0]: 'myarg1'\r\nargs[1]: 'myarg2'")
|
||||
})
|
||||
}, 20000) // slower windows runs timeout, so upping timeout to 20s (from default of 5s)
|
||||
|
||||
it('execs .cmd with a space and with verbatim args (Windows)', async () => {
|
||||
// this test validates the quoting that tool runner adds around the script path.
|
||||
@@ -563,6 +758,42 @@ describe('@actions/exec', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('execs .cmd from path (Windows)', async () => {
|
||||
// this test validates whether a .cmd is resolved from the PATH when the extension is not specified
|
||||
const cmd = 'print-args-cmd' // note, not print-args-cmd.cmd
|
||||
const cmdPath = path.join(__dirname, 'scripts', `${cmd}.cmd`)
|
||||
const args: string[] = ['my arg 1', 'my arg 2']
|
||||
const outStream = new StringStream()
|
||||
let output = ''
|
||||
const options = {
|
||||
outStream: <stream.Writable>outStream,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const originalPath = process.env['Path']
|
||||
try {
|
||||
process.env['Path'] = `${originalPath};${path.dirname(cmdPath)}`
|
||||
const exitCode = await exec.exec(`${cmd}`, args, options)
|
||||
expect(exitCode).toBe(0)
|
||||
expect(outStream.getContents().split(os.EOL)[0]).toBe(
|
||||
`[command]${
|
||||
process.env.ComSpec
|
||||
} /D /S /C "${cmdPath} "my arg 1" "my arg 2""`
|
||||
)
|
||||
expect(output.trim()).toBe(
|
||||
'args[0]: "<quote>my arg 1<quote>"\r\n' +
|
||||
'args[1]: "<quote>my arg 2<quote>"'
|
||||
)
|
||||
} catch (err) {
|
||||
process.env['Path'] = originalPath
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
it('execs .cmd with arg quoting (Windows)', async () => {
|
||||
// this test validates .cmd quoting rules are applied, not the default libuv rules
|
||||
const cmdPath = path.join(
|
||||
@@ -677,7 +908,9 @@ function getExecOptions(): im.ExecOptions {
|
||||
env: {},
|
||||
silent: false,
|
||||
failOnStdErr: false,
|
||||
ignoreReturnCode: false
|
||||
ignoreReturnCode: false,
|
||||
outStream: outstream,
|
||||
errStream: errstream
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
@echo off
|
||||
setlocal
|
||||
set index=0
|
||||
|
||||
:check_arg
|
||||
set arg=%1
|
||||
if not defined arg goto :eof
|
||||
set "arg=%arg:"=<quote>%"
|
||||
echo args[%index%]: "%arg%"
|
||||
set /a index=%index%+1
|
||||
shift
|
||||
goto check_arg
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# store arguments in a special array
|
||||
args=("$@")
|
||||
# get number of elements
|
||||
ELEMENTS=${#args[@]}
|
||||
|
||||
# echo each element
|
||||
for (( i=0;i<$ELEMENTS;i++)); do
|
||||
echo "args[$i]: \"${args[${i}]}\""
|
||||
done
|
||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@actions/exec",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@actions/io": {
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@actions/exec",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.2",
|
||||
"description": "Actions exec lib",
|
||||
"keywords": [
|
||||
"exec",
|
||||
"actions"
|
||||
"github",
|
||||
"actions",
|
||||
"exec"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/exec",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +22,8 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
"url": "git+https://github.com/actions/toolkit.git",
|
||||
"directory": "packages/exec"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
@@ -30,7 +32,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/io": "^0.0.0"
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import * as os from 'os'
|
||||
import * as events from 'events'
|
||||
import * as child from 'child_process'
|
||||
import * as path from 'path'
|
||||
import * as stream from 'stream'
|
||||
import * as im from './interfaces'
|
||||
import * as io from '@actions/io'
|
||||
import * as ioUtil from '@actions/io/lib/io-util'
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
@@ -216,7 +219,7 @@ export class ToolRunner extends events.EventEmitter {
|
||||
// command line from libuv quoting rules would look like:
|
||||
// foo.exe "myarg:\"my val\""
|
||||
//
|
||||
// 3) double-up slashes that preceed a quote,
|
||||
// 3) double-up slashes that precede a quote,
|
||||
// e.g. hello \world => "hello \world"
|
||||
// hello\"world => "hello\\""world"
|
||||
// hello\\"world => "hello\\\\""world"
|
||||
@@ -392,6 +395,24 @@ export class ToolRunner extends events.EventEmitter {
|
||||
* @returns number
|
||||
*/
|
||||
async exec(): Promise<number> {
|
||||
// root the tool path if it is unrooted and contains relative pathing
|
||||
if (
|
||||
!ioUtil.isRooted(this.toolPath) &&
|
||||
(this.toolPath.includes('/') ||
|
||||
(IS_WINDOWS && this.toolPath.includes('\\')))
|
||||
) {
|
||||
// prefer options.cwd if it is specified, however options.cwd may also need to be rooted
|
||||
this.toolPath = path.resolve(
|
||||
process.cwd(),
|
||||
this.options.cwd || process.cwd(),
|
||||
this.toolPath
|
||||
)
|
||||
}
|
||||
|
||||
// if the tool is only a file name, then resolve it from the PATH
|
||||
// otherwise verify it exists (add extension on Windows if necessary)
|
||||
this.toolPath = await io.which(this.toolPath, true)
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
this._debug(`exec tool: ${this.toolPath}`)
|
||||
this._debug('arguments:')
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
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.
|
||||
@@ -1,7 +0,0 @@
|
||||
# `@actions/exit`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
See [src/exit.ts](src/exit.ts).
|
||||
@@ -1,21 +0,0 @@
|
||||
import * as exit from '../src/exit'
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
it('exits successfully', () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation()
|
||||
exit.success()
|
||||
expect(process.exit).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('exits as a failure', () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation()
|
||||
exit.failure()
|
||||
expect(process.exit).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('exits neutrally', () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation()
|
||||
exit.neutral()
|
||||
expect(process.exit).toHaveBeenCalledWith(78)
|
||||
})
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "@actions/exit",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "@actions/exit",
|
||||
"version": "0.0.0",
|
||||
"description": "Functions for safely exiting from GitHub Actions",
|
||||
"main": "lib/exit.js",
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions",
|
||||
"toolkit"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/exit",
|
||||
"license": "MIT",
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* The code to exit an action
|
||||
*/
|
||||
export enum ExitCode {
|
||||
/**
|
||||
* A code indicating that the action was successful
|
||||
*/
|
||||
Success = 0,
|
||||
|
||||
/**
|
||||
* A code indicating that the action was a failure
|
||||
*/
|
||||
Failure = 1,
|
||||
|
||||
/**
|
||||
* A code indicating that the action is complete, but neither succeeded nor failed
|
||||
*/
|
||||
Neutral = 78
|
||||
}
|
||||
|
||||
// TODO: These exit codes may not behave as expected on the new runtime, due to
|
||||
// complexities of async logging and sync exiting.
|
||||
|
||||
/**
|
||||
* Exit the action as a success.
|
||||
*/
|
||||
export function success(): void {
|
||||
process.exit(ExitCode.Success)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the action as a failure.
|
||||
*/
|
||||
export function failure(): void {
|
||||
process.exit(ExitCode.Failure)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the action neither a success or a failure
|
||||
*/
|
||||
export function neutral(): void {
|
||||
process.exit(ExitCode.Neutral)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# `@actions/github`
|
||||
|
||||
> A hydrated Octokit client.
|
||||
|
||||
## Usage
|
||||
|
||||
Returns an Octokit client. See https://octokit.github.io/rest.js for the API.
|
||||
|
||||
```js
|
||||
const github = require('@actions/github');
|
||||
const core = require('@actions/core');
|
||||
|
||||
async function run() {
|
||||
// This should be a token with access to your repository scoped in as a secret.
|
||||
// The YML workflow will need to set myToken with the GitHub Secret Token
|
||||
// myToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#about-the-github_token-secret
|
||||
const myToken = core.getInput('myToken');
|
||||
|
||||
const octokit = new github.GitHub(myToken);
|
||||
|
||||
const { data: pullRequest } = await octokit.pulls.get({
|
||||
owner: 'octokit',
|
||||
repo: 'rest.js',
|
||||
pull_number: 123,
|
||||
mediaType: {
|
||||
format: 'diff'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(pullRequest);
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
You can pass client options (except `auth`, which is handled by the token argument), as specified by [Octokit](https://octokit.github.io/rest.js/), as a second argument to the `GitHub` constructor.
|
||||
|
||||
You can also make GraphQL requests. See https://github.com/octokit/graphql.js for the API.
|
||||
|
||||
```js
|
||||
const result = await octokit.graphql(query, variables);
|
||||
```
|
||||
|
||||
Finally, you can get the context of the current action:
|
||||
|
||||
```js
|
||||
const github = require('@actions/github');
|
||||
|
||||
const context = github.context;
|
||||
|
||||
const newIssue = await octokit.issues.create({
|
||||
...context.repo,
|
||||
title: 'New issue!',
|
||||
body: 'Hello Universe!'
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
# @actions/github Releases
|
||||
|
||||
### 2.0.0
|
||||
|
||||
- Upgrade Octokit version to 4.x to include typescript types [#228](https://github.com/actions/toolkit/pull/228)
|
||||
|
||||
### 1.1.0
|
||||
|
||||
- Accept Octokit.Options in the GitHub constructor [#113](https://github.com/actions/toolkit/pull/113)
|
||||
|
||||
### 1.0.1
|
||||
|
||||
- Simplify WebPack configs by removing dynamic require - [#101](https://github.com/actions/toolkit/pull/101)
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Initial release
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`@actions/context return error for context.repo when repository doesn't exist 1`] = `"context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'"`;
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as path from 'path'
|
||||
import {Context} from '../src/context'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
|
||||
describe('@actions/context', () => {
|
||||
let context: Context
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.GITHUB_EVENT_PATH = path.join(__dirname, 'payload.json')
|
||||
process.env.GITHUB_REPOSITORY = 'actions/toolkit'
|
||||
context = new Context()
|
||||
})
|
||||
|
||||
it('returns the payload object', () => {
|
||||
expect(context.payload).toEqual(require('./payload.json'))
|
||||
})
|
||||
|
||||
it('returns an empty payload if the GITHUB_EVENT_PATH environment variable is falsey', () => {
|
||||
delete process.env.GITHUB_EVENT_PATH
|
||||
|
||||
context = new Context()
|
||||
expect(context.payload).toEqual({})
|
||||
})
|
||||
|
||||
it('returns attributes from the GITHUB_REPOSITORY', () => {
|
||||
expect(context.repo).toEqual({owner: 'actions', repo: 'toolkit'})
|
||||
})
|
||||
|
||||
it('returns attributes from the repository payload', () => {
|
||||
delete process.env.GITHUB_REPOSITORY
|
||||
|
||||
context.payload.repository = {
|
||||
name: 'test',
|
||||
owner: {login: 'user'}
|
||||
}
|
||||
expect(context.repo).toEqual({owner: 'user', repo: 'test'})
|
||||
})
|
||||
|
||||
it("return error for context.repo when repository doesn't exist", () => {
|
||||
delete process.env.GITHUB_REPOSITORY
|
||||
|
||||
context.payload.repository = undefined
|
||||
expect(() => context.repo).toThrowErrorMatchingSnapshot()
|
||||
})
|
||||
|
||||
it('returns issue attributes from the repository', () => {
|
||||
expect(context.issue).toEqual({
|
||||
owner: 'actions',
|
||||
repo: 'toolkit',
|
||||
number: 1
|
||||
})
|
||||
})
|
||||
|
||||
it('works with pullRequest payloads', () => {
|
||||
delete process.env.GITHUB_REPOSITORY
|
||||
context.payload = {
|
||||
pullRequest: {number: 2},
|
||||
repository: {owner: {login: 'user'}, name: 'test'}
|
||||
}
|
||||
expect(context.issue).toEqual({
|
||||
number: 2,
|
||||
owner: 'user',
|
||||
repo: 'test'
|
||||
})
|
||||
})
|
||||
|
||||
it('works with payload.number payloads', () => {
|
||||
delete process.env.GITHUB_REPOSITORY
|
||||
context.payload = {
|
||||
number: 2,
|
||||
repository: {owner: {login: 'user'}, name: 'test'}
|
||||
}
|
||||
expect(context.issue).toEqual({
|
||||
number: 2,
|
||||
owner: 'user',
|
||||
repo: 'test'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"action": "opened",
|
||||
"repository": {
|
||||
"owner": {
|
||||
"login": "user"
|
||||
},
|
||||
"name": "test"
|
||||
},
|
||||
"issue": {
|
||||
"number": 1
|
||||
},
|
||||
"sender": {
|
||||
"type": "User"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testRunner: 'jest-circus/runner',
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
Generated
+5270
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@actions/github",
|
||||
"version": "2.0.0",
|
||||
"description": "Actions github lib",
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/github",
|
||||
"license": "MIT",
|
||||
"main": "lib/github.js",
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git",
|
||||
"directory": "packages/github"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "tsc",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/graphql": "^4.3.1",
|
||||
"@octokit/rest": "^16.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^24.7.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Originally pulled from https://github.com/JasonEtco/actions-toolkit/blob/master/src/context.ts
|
||||
import {WebhookPayload} from './interfaces'
|
||||
import {readFileSync, existsSync} from 'fs'
|
||||
import {EOL} from 'os'
|
||||
|
||||
export class Context {
|
||||
/**
|
||||
* Webhook payload object that triggered the workflow
|
||||
*/
|
||||
payload: WebhookPayload
|
||||
|
||||
eventName: string
|
||||
sha: string
|
||||
ref: string
|
||||
workflow: string
|
||||
action: string
|
||||
actor: string
|
||||
|
||||
/**
|
||||
* Hydrate the context from the environment
|
||||
*/
|
||||
constructor() {
|
||||
this.payload = {}
|
||||
if (process.env.GITHUB_EVENT_PATH) {
|
||||
if (existsSync(process.env.GITHUB_EVENT_PATH)) {
|
||||
this.payload = JSON.parse(
|
||||
readFileSync(process.env.GITHUB_EVENT_PATH, {encoding: 'utf8'})
|
||||
)
|
||||
} else {
|
||||
const path = process.env.GITHUB_EVENT_PATH
|
||||
process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${EOL}`)
|
||||
}
|
||||
}
|
||||
this.eventName = process.env.GITHUB_EVENT_NAME as string
|
||||
this.sha = process.env.GITHUB_SHA as string
|
||||
this.ref = process.env.GITHUB_REF as string
|
||||
this.workflow = process.env.GITHUB_WORKFLOW as string
|
||||
this.action = process.env.GITHUB_ACTION as string
|
||||
this.actor = process.env.GITHUB_ACTOR as string
|
||||
}
|
||||
|
||||
get issue(): {owner: string; repo: string; number: number} {
|
||||
const payload = this.payload
|
||||
|
||||
return {
|
||||
...this.repo,
|
||||
number: (payload.issue || payload.pullRequest || payload).number
|
||||
}
|
||||
}
|
||||
|
||||
get repo(): {owner: string; repo: string} {
|
||||
if (process.env.GITHUB_REPOSITORY) {
|
||||
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
|
||||
return {owner, repo}
|
||||
}
|
||||
|
||||
if (this.payload.repository) {
|
||||
return {
|
||||
owner: this.payload.repository.owner.login,
|
||||
repo: this.payload.repository.name
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Originally pulled from https://github.com/JasonEtco/actions-toolkit/blob/master/src/github.ts
|
||||
import {graphql} from '@octokit/graphql'
|
||||
|
||||
// we need this type to set up a property on the GitHub object
|
||||
// that has token authorization
|
||||
// (it is not exported from octokit by default)
|
||||
import {graphql as GraphQL} from '@octokit/graphql/dist-types/types'
|
||||
|
||||
import Octokit from '@octokit/rest'
|
||||
import * as Context from './context'
|
||||
|
||||
// We need this in order to extend Octokit
|
||||
Octokit.prototype = new Octokit()
|
||||
|
||||
export const context = new Context.Context()
|
||||
|
||||
export class GitHub extends Octokit {
|
||||
graphql: GraphQL
|
||||
|
||||
constructor(token: string, opts: Omit<Octokit.Options, 'auth'> = {}) {
|
||||
super({...opts, auth: `token ${token}`})
|
||||
|
||||
this.graphql = graphql.defaults({
|
||||
headers: {authorization: `token ${token}`}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface PayloadRepository {
|
||||
[key: string]: any
|
||||
full_name?: string
|
||||
name: string
|
||||
owner: {
|
||||
[key: string]: any
|
||||
login: string
|
||||
name?: string
|
||||
}
|
||||
html_url?: string
|
||||
}
|
||||
|
||||
export interface WebhookPayload {
|
||||
[key: string]: any
|
||||
repository?: PayloadRepository
|
||||
issue?: {
|
||||
[key: string]: any
|
||||
number: number
|
||||
html_url?: string
|
||||
body?: string
|
||||
}
|
||||
pull_request?: {
|
||||
[key: string]: any
|
||||
number: number
|
||||
html_url?: string
|
||||
body?: string
|
||||
}
|
||||
sender?: {
|
||||
[key: string]: any
|
||||
type: string
|
||||
}
|
||||
action?: string
|
||||
installation?: {
|
||||
id: number
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
+42
-38
@@ -4,46 +4,50 @@
|
||||
|
||||
## Usage
|
||||
|
||||
#### mkdir -p
|
||||
|
||||
Recursively make a directory. Follows rules specified in [man mkdir](https://linux.die.net/man/1/mkdir) with the `-p` option specified:
|
||||
|
||||
```js
|
||||
const io = require('@actions/io');
|
||||
|
||||
await io.mkdirP('path/to/make');
|
||||
```
|
||||
/**
|
||||
* Copies a file or folder.
|
||||
*
|
||||
* @param source source path
|
||||
* @param dest destination path
|
||||
* @param options optional. See CopyOptions.
|
||||
*/
|
||||
export function cp(source: string, dest: string, options?: CopyOptions): Promise<void>
|
||||
|
||||
/**
|
||||
* Remove a path recursively with force
|
||||
*
|
||||
* @param path path to remove
|
||||
*/
|
||||
export function rmRF(path: string): Promise<void>
|
||||
#### cp/mv
|
||||
|
||||
/**
|
||||
* Make a directory. Creates the full path with folders in between
|
||||
*
|
||||
* @param p path to create
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export function mkdirP(p: string): Promise<void>
|
||||
Copy or move files or folders. Follows rules specified in [man cp](https://linux.die.net/man/1/cp) and [man mv](https://linux.die.net/man/1/mv):
|
||||
|
||||
/**
|
||||
* Moves a path.
|
||||
*
|
||||
* @param source source path
|
||||
* @param dest destination path
|
||||
* @param options optional. See CopyOptions.
|
||||
*/
|
||||
export function mv(source: string, dest: string, options?: CopyOptions): Promise<void>
|
||||
```js
|
||||
const io = require('@actions/io');
|
||||
|
||||
/**
|
||||
* Returns path of a tool had the tool actually been invoked. Resolves via paths.
|
||||
*
|
||||
* @param tool name of the tool
|
||||
* @param options optional. See WhichOptions.
|
||||
* @returns Promise<string> path to tool
|
||||
*/
|
||||
export function which(tool: string, options?: WhichOptions): Promise<string>
|
||||
```
|
||||
// Recursive must be true for directories
|
||||
const options = { recursive: true, force: false }
|
||||
|
||||
await io.cp('path/to/directory', 'path/to/dest', options);
|
||||
await io.mv('path/to/file', 'path/to/dest');
|
||||
```
|
||||
|
||||
#### rm -rf
|
||||
|
||||
Remove a file or folder recursively. Follows rules specified in [man rm](https://linux.die.net/man/1/rm) with the `-r` and `-f` rules specified.
|
||||
|
||||
```js
|
||||
const io = require('@actions/io');
|
||||
|
||||
await io.rmRF('path/to/directory');
|
||||
await io.rmRF('path/to/file');
|
||||
```
|
||||
|
||||
#### which
|
||||
|
||||
Get the path to a tool and resolves via paths. Follows the rules specified in [man which](https://linux.die.net/man/1/which).
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
const io = require('@actions/io');
|
||||
|
||||
const pythonPath: string = await io.which('python', true)
|
||||
|
||||
await exec.exec(`"${pythonPath}"`, ['main.py']);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# @actions/io Releases
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Initial release
|
||||
@@ -59,13 +59,7 @@ describe('cp', () => {
|
||||
await io.mkdirP(root)
|
||||
await fs.writeFile(sourceFile, 'test file content', {encoding: 'utf8'})
|
||||
await fs.writeFile(targetFile, 'correct content', {encoding: 'utf8'})
|
||||
let failed = false
|
||||
try {
|
||||
await io.cp(sourceFile, targetFile, {recursive: false, force: false})
|
||||
} catch {
|
||||
failed = true
|
||||
}
|
||||
expect(failed).toBe(true)
|
||||
await io.cp(sourceFile, targetFile, {recursive: false, force: false})
|
||||
|
||||
expect(await fs.readFile(targetFile, {encoding: 'utf8'})).toBe(
|
||||
'correct content'
|
||||
@@ -94,7 +88,7 @@ describe('cp', () => {
|
||||
})
|
||||
|
||||
it('copies directory into non-existing destination with -r', async () => {
|
||||
const root: string = path.join(getTestTemp(), 'cp_with_-r_nonexisting_dest')
|
||||
const root: string = path.join(getTestTemp(), 'cp_with_-r_nonexistent_dest')
|
||||
const sourceFolder: string = path.join(root, 'cp_source')
|
||||
const sourceFile: string = path.join(sourceFolder, 'cp_source_file')
|
||||
|
||||
@@ -132,6 +126,43 @@ describe('cp', () => {
|
||||
expect(thrown).toBe(true)
|
||||
await assertNotExists(targetFile)
|
||||
})
|
||||
|
||||
it('Copies symlinks correctly', async () => {
|
||||
// create the following layout
|
||||
// sourceFolder
|
||||
// sourceFolder/nested
|
||||
// sourceFolder/nested/sourceFile
|
||||
// sourceFolder/symlinkDirectory -> sourceFile
|
||||
const root: string = path.join(getTestTemp(), 'cp_with_-r_symlinks')
|
||||
const sourceFolder: string = path.join(root, 'cp_source')
|
||||
const nestedFolder: string = path.join(sourceFolder, 'nested')
|
||||
const sourceFile: string = path.join(nestedFolder, 'cp_source_file')
|
||||
const symlinkDirectory: string = path.join(sourceFolder, 'symlinkDirectory')
|
||||
|
||||
const targetFolder: string = path.join(root, 'cp_target')
|
||||
const targetFile: string = path.join(
|
||||
targetFolder,
|
||||
'nested',
|
||||
'cp_source_file'
|
||||
)
|
||||
const symlinkTargetPath: string = path.join(
|
||||
targetFolder,
|
||||
'symlinkDirectory',
|
||||
'cp_source_file'
|
||||
)
|
||||
await io.mkdirP(sourceFolder)
|
||||
await io.mkdirP(nestedFolder)
|
||||
await fs.writeFile(sourceFile, 'test file content', {encoding: 'utf8'})
|
||||
await createSymlinkDir(nestedFolder, symlinkDirectory)
|
||||
await io.cp(sourceFolder, targetFolder, {recursive: true})
|
||||
|
||||
expect(await fs.readFile(targetFile, {encoding: 'utf8'})).toBe(
|
||||
'test file content'
|
||||
)
|
||||
expect(await fs.readFile(symlinkTargetPath, {encoding: 'utf8'})).toBe(
|
||||
'test file content'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('mv', () => {
|
||||
@@ -189,7 +220,7 @@ describe('mv', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('moves directory into existing destination with -r', async () => {
|
||||
it('moves directory into existing destination', async () => {
|
||||
const root: string = path.join(getTestTemp(), ' mv_with_-r_existing_dest')
|
||||
const sourceFolder: string = path.join(root, ' mv_source')
|
||||
const sourceFile: string = path.join(sourceFolder, ' mv_source_file')
|
||||
@@ -203,7 +234,7 @@ describe('mv', () => {
|
||||
await io.mkdirP(sourceFolder)
|
||||
await fs.writeFile(sourceFile, 'test file content', {encoding: 'utf8'})
|
||||
await io.mkdirP(targetFolder)
|
||||
await io.mv(sourceFolder, targetFolder, {recursive: true})
|
||||
await io.mv(sourceFolder, targetFolder)
|
||||
|
||||
expect(await fs.readFile(targetFile, {encoding: 'utf8'})).toBe(
|
||||
'test file content'
|
||||
@@ -211,10 +242,10 @@ describe('mv', () => {
|
||||
await assertNotExists(sourceFile)
|
||||
})
|
||||
|
||||
it('moves directory into non-existing destination with -r', async () => {
|
||||
it('moves directory into non-existing destination', async () => {
|
||||
const root: string = path.join(
|
||||
getTestTemp(),
|
||||
' mv_with_-r_nonexisting_dest'
|
||||
' mv_with_-r_nonexistent_dest'
|
||||
)
|
||||
const sourceFolder: string = path.join(root, ' mv_source')
|
||||
const sourceFile: string = path.join(sourceFolder, ' mv_source_file')
|
||||
@@ -223,39 +254,13 @@ describe('mv', () => {
|
||||
const targetFile: string = path.join(targetFolder, ' mv_source_file')
|
||||
await io.mkdirP(sourceFolder)
|
||||
await fs.writeFile(sourceFile, 'test file content', {encoding: 'utf8'})
|
||||
await io.mv(sourceFolder, targetFolder, {recursive: true})
|
||||
await io.mv(sourceFolder, targetFolder)
|
||||
|
||||
expect(await fs.readFile(targetFile, {encoding: 'utf8'})).toBe(
|
||||
'test file content'
|
||||
)
|
||||
await assertNotExists(sourceFile)
|
||||
})
|
||||
|
||||
it('tries to move directory without -r', async () => {
|
||||
const root: string = path.join(getTestTemp(), 'mv_without_-r')
|
||||
const sourceFolder: string = path.join(root, 'mv_source')
|
||||
const sourceFile: string = path.join(sourceFolder, 'mv_source_file')
|
||||
|
||||
const targetFolder: string = path.join(root, 'mv_target')
|
||||
const targetFile: string = path.join(
|
||||
targetFolder,
|
||||
'mv_source',
|
||||
'mv_source_file'
|
||||
)
|
||||
await io.mkdirP(sourceFolder)
|
||||
await fs.writeFile(sourceFile, 'test file content', {encoding: 'utf8'})
|
||||
|
||||
let thrown = false
|
||||
try {
|
||||
await io.mv(sourceFolder, targetFolder)
|
||||
} catch (err) {
|
||||
thrown = true
|
||||
}
|
||||
|
||||
expect(thrown).toBe(true)
|
||||
await assertExists(sourceFile)
|
||||
await assertNotExists(targetFile)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rmRF', () => {
|
||||
@@ -316,7 +321,7 @@ describe('rmRF', () => {
|
||||
await assertNotExists(testPath)
|
||||
})
|
||||
|
||||
it('removes folder that doesnt exist with rmRF', async () => {
|
||||
it('removes folder that does not exist with rmRF', async () => {
|
||||
const testPath = path.join(getTestTemp(), 'testFolder')
|
||||
await assertNotExists(testPath)
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@actions/io",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@actions/io",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Actions io lib",
|
||||
"keywords": [
|
||||
"io",
|
||||
"actions"
|
||||
"github",
|
||||
"actions",
|
||||
"io"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/io",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +22,8 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
"url": "git+https://github.com/actions/toolkit.git",
|
||||
"directory": "packages/io"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
|
||||
@@ -3,12 +3,16 @@ import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
export const {
|
||||
chmod,
|
||||
copyFile,
|
||||
lstat,
|
||||
mkdir,
|
||||
readdir,
|
||||
readlink,
|
||||
rename,
|
||||
rmdir,
|
||||
stat,
|
||||
symlink,
|
||||
unlink
|
||||
} = fs.promises
|
||||
|
||||
|
||||
+129
-91
@@ -1,5 +1,4 @@
|
||||
import * as childProcess from 'child_process'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import {promisify} from 'util'
|
||||
import * as ioUtil from './io-util'
|
||||
@@ -16,8 +15,17 @@ export interface CopyOptions {
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for cp/mv options
|
||||
*/
|
||||
export interface MoveOptions {
|
||||
/** Optional. Whether to overwrite existing files in the destination. Defaults to true */
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file or folder.
|
||||
* Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js
|
||||
*
|
||||
* @param source source path
|
||||
* @param dest destination path
|
||||
@@ -28,7 +36,41 @@ export async function cp(
|
||||
dest: string,
|
||||
options: CopyOptions = {}
|
||||
): Promise<void> {
|
||||
await move(source, dest, options, {deleteOriginal: false})
|
||||
const {force, recursive} = readCopyOptions(options)
|
||||
|
||||
const destStat = (await ioUtil.exists(dest)) ? await ioUtil.stat(dest) : null
|
||||
// Dest is an existing file, but not forcing
|
||||
if (destStat && destStat.isFile() && !force) {
|
||||
return
|
||||
}
|
||||
|
||||
// If dest is an existing directory, should copy inside.
|
||||
const newDest: string =
|
||||
destStat && destStat.isDirectory()
|
||||
? path.join(dest, path.basename(source))
|
||||
: dest
|
||||
|
||||
if (!(await ioUtil.exists(source))) {
|
||||
throw new Error(`no such file or directory: ${source}`)
|
||||
}
|
||||
const sourceStat = await ioUtil.stat(source)
|
||||
|
||||
if (sourceStat.isDirectory()) {
|
||||
if (!recursive) {
|
||||
throw new Error(
|
||||
`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`
|
||||
)
|
||||
} else {
|
||||
await cpDirRecursive(source, newDest, 0, force)
|
||||
}
|
||||
} else {
|
||||
if (path.relative(source, newDest) === '') {
|
||||
// a file cannot be copied to itself
|
||||
throw new Error(`'${newDest}' and '${source}' are the same file`)
|
||||
}
|
||||
|
||||
await copyFile(source, newDest, force)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,14 +78,31 @@ export async function cp(
|
||||
*
|
||||
* @param source source path
|
||||
* @param dest destination path
|
||||
* @param options optional. See CopyOptions.
|
||||
* @param options optional. See MoveOptions.
|
||||
*/
|
||||
export async function mv(
|
||||
source: string,
|
||||
dest: string,
|
||||
options: CopyOptions = {}
|
||||
options: MoveOptions = {}
|
||||
): Promise<void> {
|
||||
await move(source, dest, options, {deleteOriginal: true})
|
||||
if (await ioUtil.exists(dest)) {
|
||||
let destExists = true
|
||||
if (await ioUtil.isDirectory(dest)) {
|
||||
// If dest is directory copy src into dest
|
||||
dest = path.join(dest, path.basename(source))
|
||||
destExists = await ioUtil.exists(dest)
|
||||
}
|
||||
|
||||
if (destExists) {
|
||||
if (options.force == null || options.force) {
|
||||
await rmRF(dest)
|
||||
} else {
|
||||
throw new Error('Destination already exists')
|
||||
}
|
||||
}
|
||||
}
|
||||
await mkdirP(path.dirname(dest))
|
||||
await ioUtil.rename(source, dest)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,9 +226,9 @@ export async function which(tool: string, check?: boolean): Promise<string> {
|
||||
|
||||
// build the list of directories
|
||||
//
|
||||
// Note, technically "where" checks the current directory on Windows. From a task lib perspective,
|
||||
// Note, technically "where" checks the current directory on Windows. From a toolkit perspective,
|
||||
// it feels like we should not do this. Checking the current directory seems like more of a use
|
||||
// case of a shell, and the which() function exposed by the task lib should strive for consistency
|
||||
// case of a shell, and the which() function exposed by the toolkit should strive for consistency
|
||||
// across platforms.
|
||||
const directories: string[] = []
|
||||
|
||||
@@ -198,92 +257,71 @@ export async function which(tool: string, check?: boolean): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
// Copies contents of source into dest, making any necessary folders along the way.
|
||||
// Deletes the original copy if deleteOriginal is true
|
||||
async function copyDirectoryContents(
|
||||
source: string,
|
||||
dest: string,
|
||||
force: boolean,
|
||||
deleteOriginal = false
|
||||
): Promise<void> {
|
||||
if (await ioUtil.isDirectory(source)) {
|
||||
if (await ioUtil.exists(dest)) {
|
||||
if (!(await ioUtil.isDirectory(dest))) {
|
||||
throw new Error(`${dest} is not a directory`)
|
||||
}
|
||||
} else {
|
||||
await mkdirP(dest)
|
||||
}
|
||||
|
||||
// Copy all child files, and directories recursively
|
||||
const sourceChildren: string[] = await ioUtil.readdir(source)
|
||||
|
||||
for (const newSource of sourceChildren) {
|
||||
const newDest = path.join(dest, path.basename(newSource))
|
||||
await copyDirectoryContents(
|
||||
path.resolve(source, newSource),
|
||||
newDest,
|
||||
force,
|
||||
deleteOriginal
|
||||
)
|
||||
}
|
||||
|
||||
if (deleteOriginal) {
|
||||
await ioUtil.rmdir(source)
|
||||
}
|
||||
} else {
|
||||
if (force) {
|
||||
await ioUtil.copyFile(source, dest)
|
||||
} else {
|
||||
await ioUtil.copyFile(source, dest, fs.constants.COPYFILE_EXCL)
|
||||
}
|
||||
if (deleteOriginal) {
|
||||
await ioUtil.unlink(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function move(
|
||||
source: string,
|
||||
dest: string,
|
||||
options: CopyOptions = {},
|
||||
moveOptions: {deleteOriginal: boolean}
|
||||
): Promise<void> {
|
||||
const {force, recursive} = readCopyOptions(options)
|
||||
|
||||
if (await ioUtil.isDirectory(source)) {
|
||||
if (!recursive) {
|
||||
throw new Error(`non-recursive cp failed, ${source} is a directory`)
|
||||
}
|
||||
|
||||
// If directory exists, move source inside it. Otherwise, create it and move contents of source inside.
|
||||
if (await ioUtil.exists(dest)) {
|
||||
if (!(await ioUtil.isDirectory(dest))) {
|
||||
throw new Error(`${dest} is not a directory`)
|
||||
}
|
||||
|
||||
dest = path.join(dest, path.basename(source))
|
||||
}
|
||||
|
||||
await copyDirectoryContents(source, dest, force, moveOptions.deleteOriginal)
|
||||
} else {
|
||||
if ((await ioUtil.exists(dest)) && (await ioUtil.isDirectory(dest))) {
|
||||
dest = path.join(dest, path.basename(source))
|
||||
}
|
||||
if (force) {
|
||||
await ioUtil.copyFile(source, dest)
|
||||
} else {
|
||||
await ioUtil.copyFile(source, dest, fs.constants.COPYFILE_EXCL)
|
||||
}
|
||||
|
||||
if (moveOptions.deleteOriginal) {
|
||||
await ioUtil.unlink(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readCopyOptions(options: CopyOptions): Required<CopyOptions> {
|
||||
const force = options.force == null ? true : options.force
|
||||
const recursive = Boolean(options.recursive)
|
||||
return {force, recursive}
|
||||
}
|
||||
|
||||
async function cpDirRecursive(
|
||||
sourceDir: string,
|
||||
destDir: string,
|
||||
currentDepth: number,
|
||||
force: boolean
|
||||
): Promise<void> {
|
||||
// Ensure there is not a run away recursive copy
|
||||
if (currentDepth >= 255) return
|
||||
currentDepth++
|
||||
|
||||
await mkdirP(destDir)
|
||||
|
||||
const files: string[] = await ioUtil.readdir(sourceDir)
|
||||
|
||||
for (const fileName of files) {
|
||||
const srcFile = `${sourceDir}/${fileName}`
|
||||
const destFile = `${destDir}/${fileName}`
|
||||
const srcFileStat = await ioUtil.lstat(srcFile)
|
||||
|
||||
if (srcFileStat.isDirectory()) {
|
||||
// Recurse
|
||||
await cpDirRecursive(srcFile, destFile, currentDepth, force)
|
||||
} else {
|
||||
await copyFile(srcFile, destFile, force)
|
||||
}
|
||||
}
|
||||
|
||||
// Change the mode for the newly created directory
|
||||
await ioUtil.chmod(destDir, (await ioUtil.stat(sourceDir)).mode)
|
||||
}
|
||||
|
||||
// Buffered file copy
|
||||
async function copyFile(
|
||||
srcFile: string,
|
||||
destFile: string,
|
||||
force: boolean
|
||||
): Promise<void> {
|
||||
if ((await ioUtil.lstat(srcFile)).isSymbolicLink()) {
|
||||
// unlink/re-link it
|
||||
try {
|
||||
await ioUtil.lstat(destFile)
|
||||
await ioUtil.unlink(destFile)
|
||||
} catch (e) {
|
||||
// Try to override file permission
|
||||
if (e.code === 'EPERM') {
|
||||
await ioUtil.chmod(destFile, '0666')
|
||||
await ioUtil.unlink(destFile)
|
||||
}
|
||||
// other errors = it doesn't exist, no work to do
|
||||
}
|
||||
|
||||
// Copy over symlink
|
||||
const symlinkFull: string = await ioUtil.readlink(srcFile)
|
||||
await ioUtil.symlink(
|
||||
symlinkFull,
|
||||
destFile,
|
||||
ioUtil.IS_WINDOWS ? 'junction' : null
|
||||
)
|
||||
} else if (!(await ioUtil.exists(destFile)) || force) {
|
||||
await ioUtil.copyFile(srcFile, destFile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,79 @@
|
||||
|
||||
## Usage
|
||||
|
||||
See [src/tool-cache.ts](src/tool-cache.ts).
|
||||
#### Download
|
||||
|
||||
You can use this to download tools (or other files) from a download URL:
|
||||
|
||||
```js
|
||||
const tc = require('@actions/tool-cache');
|
||||
|
||||
const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz');
|
||||
```
|
||||
|
||||
#### Extract
|
||||
|
||||
These can then be extracted in platform specific ways:
|
||||
|
||||
```js
|
||||
const tc = require('@actions/tool-cache');
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const node12Path = tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-win-x64.zip');
|
||||
const node12ExtractedFolder = await tc.extractZip(node12Path, 'path/to/extract/to');
|
||||
|
||||
// Or alternately
|
||||
const node12Path = tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-win-x64.7z');
|
||||
const node12ExtractedFolder = await tc.extract7z(node12Path, 'path/to/extract/to');
|
||||
}
|
||||
else {
|
||||
const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz');
|
||||
const node12ExtractedFolder = await tc.extractTar(node12Path, 'path/to/extract/to');
|
||||
}
|
||||
```
|
||||
|
||||
#### Cache
|
||||
|
||||
Finally, you can cache these directories in our tool-cache. This is useful if you want to switch back and forth between versions of a tool, or save a tool between runs for private runners (private runners are still in development but are on the roadmap).
|
||||
|
||||
You'll often want to add it to the path as part of this step:
|
||||
|
||||
```js
|
||||
const tc = require('@actions/tool-cache');
|
||||
const core = require('@actions/core');
|
||||
|
||||
const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz');
|
||||
const node12ExtractedFolder = await tc.extractTar(node12Path, 'path/to/extract/to');
|
||||
|
||||
const cachedPath = await tc.cacheDir(node12ExtractedFolder, 'node', '12.7.0');
|
||||
core.addPath(cachedPath);
|
||||
```
|
||||
|
||||
You can also cache files for reuse.
|
||||
|
||||
```js
|
||||
const tc = require('@actions/tool-cache');
|
||||
|
||||
const cachedPath = await tc.cacheFile('path/to/exe', 'destFileName.exe', 'myExeName', '1.1.0');
|
||||
```
|
||||
|
||||
#### Find
|
||||
|
||||
Finally, you can find directories and files you've previously cached:
|
||||
|
||||
```js
|
||||
const tc = require('@actions/tool-cache');
|
||||
const core = require('@actions/core');
|
||||
|
||||
const nodeDirectory = tc.find('node', '12.x', 'x64');
|
||||
core.addPath(nodeDirectory);
|
||||
```
|
||||
|
||||
You can even find all cached versions of a tool:
|
||||
|
||||
```js
|
||||
const tc = require('@actions/tool-cache');
|
||||
|
||||
const allNodeVersions = tc.findAllVersions('node');
|
||||
console.log(`Versions of node available: ${allNodeVersions}`);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# @actions/tool-cache Releases
|
||||
|
||||
### 1.1.2
|
||||
|
||||
- [Use zip and unzip from PATH](https://github.com/actions/toolkit/pull/161)
|
||||
- [Support custom flags for `extractTar`](https://github.com/actions/toolkit/pull/48)
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Initial release
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -7,8 +7,8 @@ import * as exec from '@actions/exec'
|
||||
const cachePath = path.join(__dirname, 'CACHE')
|
||||
const tempPath = path.join(__dirname, 'TEMP')
|
||||
// Set temp and tool directories before importing (used to set global state)
|
||||
process.env['RUNNER_TEMPDIRECTORY'] = tempPath
|
||||
process.env['RUNNER_TOOLSDIRECTORY'] = cachePath
|
||||
process.env['RUNNER_TEMP'] = tempPath
|
||||
process.env['RUNNER_TOOL_CACHE'] = cachePath
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
import * as tc from '../src/tool-cache'
|
||||
@@ -142,6 +142,36 @@ describe('@actions/tool-cache', function() {
|
||||
}
|
||||
})
|
||||
|
||||
it('extracts a 7z to a directory that does not exist', async () => {
|
||||
const tempDir = path.join(__dirname, 'test-install-7z')
|
||||
const destDir = path.join(tempDir, 'not-exist')
|
||||
try {
|
||||
await io.mkdirP(tempDir)
|
||||
|
||||
// copy the 7z file to the test dir
|
||||
const _7zFile: string = path.join(tempDir, 'test.7z')
|
||||
await io.cp(path.join(__dirname, 'data', 'test.7z'), _7zFile)
|
||||
|
||||
// extract/cache
|
||||
const extPath: string = await tc.extract7z(_7zFile, destDir)
|
||||
await tc.cacheDir(extPath, 'my-7z-contents', '1.1.0')
|
||||
const toolPath: string = tc.find('my-7z-contents', '1.1.0')
|
||||
|
||||
expect(extPath).toContain('not-exist')
|
||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||
).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
||||
).toBeTruthy()
|
||||
} finally {
|
||||
await io.rmRF(tempDir)
|
||||
}
|
||||
})
|
||||
|
||||
it('extract 7z using custom 7z tool', async function() {
|
||||
const tempDir = path.join(
|
||||
__dirname,
|
||||
@@ -197,6 +227,95 @@ describe('@actions/tool-cache', function() {
|
||||
await io.rmRF(tempDir)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
it('extract .tar.gz', async () => {
|
||||
const tempDir = path.join(tempPath, 'test-install-tar.gz')
|
||||
|
||||
await io.mkdirP(tempDir)
|
||||
|
||||
// copy the .tar.gz file to the test dir
|
||||
const _tgzFile: string = path.join(tempDir, 'test.tar.gz')
|
||||
await io.cp(path.join(__dirname, 'data', 'test.tar.gz'), _tgzFile)
|
||||
|
||||
// extract/cache
|
||||
const extPath: string = await tc.extractTar(_tgzFile)
|
||||
await tc.cacheDir(extPath, 'my-tgz-contents', '1.1.0')
|
||||
const toolPath: string = tc.find('my-tgz-contents', '1.1.0')
|
||||
|
||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||
).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
||||
).toBeTruthy()
|
||||
expect(
|
||||
fs.readFileSync(
|
||||
path.join(toolPath, 'folder', 'nested-file.txt'),
|
||||
'utf8'
|
||||
)
|
||||
).toBe('folder/nested-file.txt contents')
|
||||
})
|
||||
|
||||
it('extract .tar.gz to a directory that does not exist', async () => {
|
||||
const tempDir = path.join(tempPath, 'test-install-tar.gz')
|
||||
const destDir = path.join(tempDir, 'not-exist')
|
||||
|
||||
await io.mkdirP(tempDir)
|
||||
|
||||
// copy the .tar.gz file to the test dir
|
||||
const _tgzFile: string = path.join(tempDir, 'test.tar.gz')
|
||||
await io.cp(path.join(__dirname, 'data', 'test.tar.gz'), _tgzFile)
|
||||
|
||||
// extract/cache
|
||||
const extPath: string = await tc.extractTar(_tgzFile, destDir)
|
||||
await tc.cacheDir(extPath, 'my-tgz-contents', '1.1.0')
|
||||
const toolPath: string = tc.find('my-tgz-contents', '1.1.0')
|
||||
|
||||
expect(extPath).toContain('not-exist')
|
||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||
).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
||||
).toBeTruthy()
|
||||
expect(
|
||||
fs.readFileSync(
|
||||
path.join(toolPath, 'folder', 'nested-file.txt'),
|
||||
'utf8'
|
||||
)
|
||||
).toBe('folder/nested-file.txt contents')
|
||||
})
|
||||
|
||||
it('extract .tar.xz', async () => {
|
||||
const tempDir = path.join(tempPath, 'test-install-tar.xz')
|
||||
|
||||
await io.mkdirP(tempDir)
|
||||
|
||||
// copy the .tar.gz file to the test dir
|
||||
const _txzFile: string = path.join(tempDir, 'test.tar.xz')
|
||||
await io.cp(path.join(__dirname, 'data', 'test.tar.xz'), _txzFile)
|
||||
|
||||
// extract/cache
|
||||
const extPath: string = await tc.extractTar(_txzFile, undefined, 'x')
|
||||
await tc.cacheDir(extPath, 'my-txz-contents', '1.1.0')
|
||||
const toolPath: string = tc.find('my-txz-contents', '1.1.0')
|
||||
|
||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(toolPath, 'bar.txt'))).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'foo', 'hello.txt'))
|
||||
).toBeTruthy()
|
||||
expect(
|
||||
fs.readFileSync(path.join(toolPath, 'foo', 'hello.txt'), 'utf8')
|
||||
).toBe('foo/hello: world')
|
||||
})
|
||||
}
|
||||
|
||||
it('installs a zip and finds it', async () => {
|
||||
@@ -231,7 +350,7 @@ describe('@actions/tool-cache', function() {
|
||||
]
|
||||
await exec.exec(`"${powershellPath}"`, args)
|
||||
} else {
|
||||
const zipPath: string = path.join(__dirname, 'externals', 'zip')
|
||||
const zipPath: string = await io.which('zip')
|
||||
await exec.exec(`"${zipPath}`, [zipFile, '-r', '.'], {cwd: stagingDir})
|
||||
}
|
||||
|
||||
@@ -282,7 +401,7 @@ describe('@actions/tool-cache', function() {
|
||||
]
|
||||
await exec.exec(`"${powershellPath}"`, args)
|
||||
} else {
|
||||
const zipPath = path.join(__dirname, 'externals', 'zip')
|
||||
const zipPath: string = await io.which('zip')
|
||||
await exec.exec(zipPath, [zipFile, '-r', '.'], {cwd: stagingDir})
|
||||
}
|
||||
|
||||
@@ -307,6 +426,60 @@ describe('@actions/tool-cache', function() {
|
||||
}
|
||||
})
|
||||
|
||||
it('extract zip to a directory that does not exist', async function() {
|
||||
const tempDir = path.join(__dirname, 'test-install-zip')
|
||||
try {
|
||||
await io.mkdirP(tempDir)
|
||||
|
||||
// stage the layout for a zip file:
|
||||
// file.txt
|
||||
// folder/nested-file.txt
|
||||
const stagingDir = path.join(tempDir, 'zip-staging')
|
||||
await io.mkdirP(path.join(stagingDir, 'folder'))
|
||||
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
|
||||
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
|
||||
|
||||
// create the zip
|
||||
const zipFile = path.join(tempDir, 'test.zip')
|
||||
await io.rmRF(zipFile)
|
||||
if (IS_WINDOWS) {
|
||||
const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes
|
||||
const escapedZipFile = zipFile.replace(/'/g, "''")
|
||||
const powershellPath = await io.which('powershell', true)
|
||||
const args = [
|
||||
'-NoLogo',
|
||||
'-Sta',
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-ExecutionPolicy',
|
||||
'Unrestricted',
|
||||
'-Command',
|
||||
`$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')`
|
||||
]
|
||||
await exec.exec(`"${powershellPath}"`, args)
|
||||
} else {
|
||||
const zipPath: string = await io.which('zip')
|
||||
await exec.exec(zipPath, [zipFile, '-r', '.'], {cwd: stagingDir})
|
||||
}
|
||||
|
||||
const destDir = path.join(tempDir, 'not-exist')
|
||||
|
||||
const extPath: string = await tc.extractZip(zipFile, destDir)
|
||||
await tc.cacheDir(extPath, 'foo', '1.1.0')
|
||||
const toolPath: string = tc.find('foo', '1.1.0')
|
||||
|
||||
expect(extPath).toContain('not-exist')
|
||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||
expect(
|
||||
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
||||
).toBeTruthy()
|
||||
} finally {
|
||||
await io.rmRF(tempDir)
|
||||
}
|
||||
})
|
||||
|
||||
it('works with a 502 temporary failure', async function() {
|
||||
nock('http://example.com')
|
||||
.get('/temp502')
|
||||
|
||||
Generated
+22
-22
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/tool-cache",
|
||||
"version": "0.0.0",
|
||||
"version": "1.1.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -14,21 +14,21 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "12.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
||||
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||
"version": "12.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
|
||||
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.0.tgz",
|
||||
"integrity": "sha512-OO0srjOGH99a4LUN2its3+r6CBYcplhJ466yLqs+zvAWgphCpS8hYZEZ797tRDP/QKcqTdb/YCN6ifASoAWkrQ==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
|
||||
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz",
|
||||
"integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==",
|
||||
"version": "3.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
|
||||
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
@@ -97,9 +97,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
@@ -118,9 +118,9 @@
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"nock": {
|
||||
@@ -167,9 +167,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.1.0.tgz",
|
||||
"integrity": "sha512-kCqEOOHoBcFs/2Ccuk4Xarm/KiWRSLEX9CAZF8xkJ6ZPlIoTZ8V5f7J16vYLJqDbR7KrxTJpR2lqjIEm2Qx9cQ=="
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.4",
|
||||
@@ -183,9 +183,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"typed-rest-client": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.4.0.tgz",
|
||||
"integrity": "sha512-f+3+X13CIpkv0WvFERkXq4aH5BYzyeYclf8t+X7oa/YaE80EjYW12kphY0aEQBaL9RzChP0MSbsVhB4X+bzyDw==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.5.0.tgz",
|
||||
"integrity": "sha512-DVZRlmsfnTjp6ZJaatcdyvvwYwbWvR4YDNFDqb+qdTxpvaVP99YCpBkA8rxsLtAPjBVoDe4fNsnMIdZTiPuKWg==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.4",
|
||||
"underscore": "1.8.3"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@actions/tool-cache",
|
||||
"version": "0.0.0",
|
||||
"version": "1.1.2",
|
||||
"description": "Actions tool-cache lib",
|
||||
"keywords": [
|
||||
"exec",
|
||||
"actions"
|
||||
"github",
|
||||
"actions",
|
||||
"exec"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/exec",
|
||||
"license": "MIT",
|
||||
@@ -22,7 +23,8 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
"url": "git+https://github.com/actions/toolkit.git",
|
||||
"directory": "packages/tool-cache"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
@@ -32,17 +34,17 @@
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.1.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/io": "^1.0.1",
|
||||
"semver": "^6.1.0",
|
||||
"typed-rest-client": "^1.4.0",
|
||||
"uuid": "^3.3.2",
|
||||
"@actions/core": "^0.0.0",
|
||||
"@actions/io": "^0.0.0",
|
||||
"@actions/exec": "^0.0.0"
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nock": "^10.0.6",
|
||||
"@types/nock": "^10.0.3",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/uuid": "^3.4.4"
|
||||
"@types/uuid": "^3.4.4",
|
||||
"nock": "^10.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
@@ -21,8 +21,8 @@ const IS_WINDOWS = process.platform === 'win32'
|
||||
const userAgent = 'actions/tool-cache'
|
||||
|
||||
// On load grab temp directory and cache directory and remove them from env (currently don't want to expose this)
|
||||
let tempDirectory: string = process.env['RUNNER_TEMPDIRECTORY'] || ''
|
||||
let cacheRoot: string = process.env['RUNNER_TOOLSDIRECTORY'] || ''
|
||||
let tempDirectory: string = process.env['RUNNER_TEMP'] || ''
|
||||
let cacheRoot: string = process.env['RUNNER_TOOL_CACHE'] || ''
|
||||
// If directories not found, place them in common temp locations
|
||||
if (!tempDirectory || !cacheRoot) {
|
||||
let baseLocation: string
|
||||
@@ -130,7 +130,7 @@ export async function extract7z(
|
||||
ok(IS_WINDOWS, 'extract7z() not supported on current OS')
|
||||
ok(file, 'parameter "file" is required')
|
||||
|
||||
dest = dest || (await _createExtractFolder(dest))
|
||||
dest = await _createExtractFolder(dest)
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
process.chdir(dest)
|
||||
@@ -183,20 +183,25 @@ export async function extract7z(
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a tar
|
||||
* Extract a compressed tar archive
|
||||
*
|
||||
* @param file path to the tar
|
||||
* @param dest destination directory. Optional.
|
||||
* @param flags flags for the tar command to use for extraction. Defaults to 'xz' (extracting gzipped tars). Optional.
|
||||
* @returns path to the destination directory
|
||||
*/
|
||||
export async function extractTar(file: string, dest?: string): Promise<string> {
|
||||
export async function extractTar(
|
||||
file: string,
|
||||
dest?: string,
|
||||
flags: string = 'xz'
|
||||
): Promise<string> {
|
||||
if (!file) {
|
||||
throw new Error("parameter 'file' is required")
|
||||
}
|
||||
|
||||
dest = dest || (await _createExtractFolder(dest))
|
||||
dest = await _createExtractFolder(dest)
|
||||
const tarPath: string = await io.which('tar', true)
|
||||
await exec(`"${tarPath}"`, ['xzC', dest, '-f', file])
|
||||
await exec(`"${tarPath}"`, [flags, '-C', dest, '-f', file])
|
||||
|
||||
return dest
|
||||
}
|
||||
@@ -213,7 +218,7 @@ export async function extractZip(file: string, dest?: string): Promise<string> {
|
||||
throw new Error("parameter 'file' is required")
|
||||
}
|
||||
|
||||
dest = dest || (await _createExtractFolder(dest))
|
||||
dest = await _createExtractFolder(dest)
|
||||
|
||||
if (IS_WINDOWS) {
|
||||
await extractZipWin(file, dest)
|
||||
@@ -246,7 +251,7 @@ async function extractZipWin(file: string, dest: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function extractZipNix(file: string, dest: string): Promise<void> {
|
||||
const unzipPath = path.join(__dirname, '..', 'scripts', 'externals', 'unzip')
|
||||
const unzipPath = await io.which('unzip')
|
||||
await exec(`"${unzipPath}"`, [file], {cwd: dest})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
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.
|
||||
@@ -1,7 +0,0 @@
|
||||
# `@actions/toolkit`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
See [src/toolkit.ts](src/toolkit.ts).
|
||||
@@ -1,39 +0,0 @@
|
||||
import * as exitPkg from '@actions/exit'
|
||||
import {Signale} from 'signale'
|
||||
import {Exit} from '../src/exit'
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
jest.mock('@actions/exit')
|
||||
|
||||
const tests: [keyof Exit, keyof Signale][] = [
|
||||
['success', 'success'],
|
||||
['neutral', 'info'],
|
||||
['failure', 'fatal']
|
||||
]
|
||||
|
||||
describe.each(tests)('%s', (method, log) => {
|
||||
let logger: Signale
|
||||
let exit: Exit
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a logger to mock
|
||||
logger = new Signale()
|
||||
logger.success = jest.fn()
|
||||
logger.info = jest.fn()
|
||||
logger.fatal = jest.fn()
|
||||
|
||||
process.exit = jest.fn<never, [number]>()
|
||||
exit = new Exit(logger)
|
||||
})
|
||||
|
||||
it('exits with the expected method', () => {
|
||||
exit[method]()
|
||||
expect(exitPkg[method]).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('logs the expected message', () => {
|
||||
exit[method]('hello')
|
||||
expect(logger[log]).toHaveBeenCalledWith('hello')
|
||||
})
|
||||
})
|
||||
@@ -1,52 +0,0 @@
|
||||
import * as exitPkg from '@actions/exit'
|
||||
import {Signale} from 'signale'
|
||||
import {Toolkit} from '../src/toolkit'
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
jest.mock('@actions/exit')
|
||||
|
||||
describe('.run', () => {
|
||||
it('runs a sync function', async () => {
|
||||
const cb = jest.fn(() => true)
|
||||
const value = await Toolkit.run(cb)
|
||||
expect(cb).toHaveBeenCalledWith(expect.any(Toolkit))
|
||||
expect(value).toBe(true)
|
||||
})
|
||||
|
||||
it('runs an async function', async () => {
|
||||
const cb = jest.fn(async () => true)
|
||||
const value = await Toolkit.run(cb)
|
||||
expect(cb).toHaveBeenCalledWith(expect.any(Toolkit))
|
||||
expect(value).toBe(true)
|
||||
})
|
||||
|
||||
it('logs and fails when an error occurs', async () => {
|
||||
const err = new Error()
|
||||
const exitFailure = jest.fn()
|
||||
|
||||
await Toolkit.run(async tk => {
|
||||
tk.exit.failure = exitFailure
|
||||
throw err
|
||||
})
|
||||
|
||||
expect(exitFailure).toHaveBeenCalledWith(err)
|
||||
})
|
||||
})
|
||||
|
||||
it('asserts required keys are present', async () => {
|
||||
const missingKey = '__DOES_NOT_EXIST__'
|
||||
|
||||
Reflect.deleteProperty(process.env, missingKey)
|
||||
|
||||
const logger = new Signale()
|
||||
logger.fatal = jest.fn()
|
||||
jest.spyOn(process, 'exit').mockImplementation()
|
||||
|
||||
new Toolkit({logger, requiredEnv: [missingKey]})
|
||||
|
||||
expect(exitPkg.failure).toHaveBeenCalled()
|
||||
expect(logger.fatal)
|
||||
.toHaveBeenCalledWith(`The following environment variables are required for this action to run:
|
||||
- __DOES_NOT_EXIST__`)
|
||||
})
|
||||
Generated
-454
@@ -1,454 +0,0 @@
|
||||
{
|
||||
"name": "@actions/toolkit",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-4.0.0.tgz",
|
||||
"integrity": "sha512-b8sptNUekjREtCTJFpOfSIL4SKh65WaakcyxWzRcSPOk5RxkZJ/S8884NGZFxZ+jCB2rDURU66pSHn14cVgWVg==",
|
||||
"requires": {
|
||||
"deepmerge": "3.2.0",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"universal-user-agent": "^2.0.1",
|
||||
"url-template": "^2.0.8"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-3.0.0.tgz",
|
||||
"integrity": "sha512-DZqmbm66tq+a9FtcKrn0sjrUpi0UaZ9QPUCxxyk/4CJ2rseTMpAWRf6gCwOSUCzZcx/4XVIsDk+kz5BVdaeenA==",
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^4.0.0",
|
||||
"deprecation": "^1.0.1",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"node-fetch": "^2.3.0",
|
||||
"once": "^1.4.0",
|
||||
"universal-user-agent": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"@octokit/rest": {
|
||||
"version": "16.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.25.0.tgz",
|
||||
"integrity": "sha512-QKIzP0gNYjyIGmY3Gpm3beof0WFwxFR+HhRZ+Wi0fYYhkEUvkJiXqKF56Pf5glzzfhEwOrggfluEld5F/ZxsKw==",
|
||||
"requires": {
|
||||
"@octokit/request": "3.0.0",
|
||||
"atob-lite": "^2.0.0",
|
||||
"before-after-hook": "^1.4.0",
|
||||
"btoa-lite": "^1.0.0",
|
||||
"deprecation": "^1.0.1",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"lodash.uniq": "^4.5.0",
|
||||
"octokit-pagination-methods": "^1.1.0",
|
||||
"once": "^1.4.0",
|
||||
"universal-user-agent": "^2.0.0",
|
||||
"url-template": "^2.0.8"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"atob-lite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz",
|
||||
"integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY="
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.4.0.tgz",
|
||||
"integrity": "sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg=="
|
||||
},
|
||||
"btoa-lite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
|
||||
"integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"requires": {
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz",
|
||||
"integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow=="
|
||||
},
|
||||
"deprecation": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-1.0.1.tgz",
|
||||
"integrity": "sha512-ccVHpE72+tcIKaGMql33x5MAjKQIZrk+3x2GbJ7TeraUCZWHoT+KSZpoC+JQFsUBlSTXUrBaGiF0j6zVTepPLg=="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"execa": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
|
||||
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
|
||||
"requires": {
|
||||
"cross-spawn": "^6.0.0",
|
||||
"get-stream": "^4.0.0",
|
||||
"is-stream": "^1.1.0",
|
||||
"npm-run-path": "^2.0.0",
|
||||
"p-finally": "^1.0.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"figures": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
|
||||
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
|
||||
"requires": {
|
||||
"locate-path": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
||||
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
|
||||
"requires": {
|
||||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.15",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
|
||||
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
|
||||
},
|
||||
"load-json-file": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
|
||||
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"parse-json": "^4.0.0",
|
||||
"pify": "^3.0.0",
|
||||
"strip-bom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
|
||||
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
|
||||
"requires": {
|
||||
"p-locate": "^2.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||
},
|
||||
"lodash.set": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
|
||||
"integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM="
|
||||
},
|
||||
"lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
|
||||
},
|
||||
"macos-release": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.2.0.tgz",
|
||||
"integrity": "sha512-iV2IDxZaX8dIcM7fG6cI46uNmHUxHE4yN+Z8tKHAW1TBPMZDIKHf/3L+YnOuj/FK9il14UaVdHmiQ1tsi90ltA=="
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
|
||||
"integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA=="
|
||||
},
|
||||
"npm-run-path": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
|
||||
"requires": {
|
||||
"path-key": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"octokit-pagination-methods": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz",
|
||||
"integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-name": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz",
|
||||
"integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==",
|
||||
"requires": {
|
||||
"macos-release": "^2.2.0",
|
||||
"windows-release": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
|
||||
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
|
||||
"requires": {
|
||||
"p-try": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
||||
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
|
||||
"requires": {
|
||||
"p-limit": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
|
||||
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
|
||||
"requires": {
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-better-errors": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
|
||||
},
|
||||
"pify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
|
||||
},
|
||||
"pkg-conf": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz",
|
||||
"integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=",
|
||||
"requires": {
|
||||
"find-up": "^2.0.0",
|
||||
"load-json-file": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"requires": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"signale": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz",
|
||||
"integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==",
|
||||
"requires": {
|
||||
"chalk": "^2.3.2",
|
||||
"figures": "^2.0.0",
|
||||
"pkg-conf": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
|
||||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"universal-user-agent": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.3.tgz",
|
||||
"integrity": "sha512-eRHEHhChCBHrZsA4WEhdgiOKgdvgrMIHwnwnqD0r5C6AO8kwKcG7qSku3iXdhvHL3YvsS9ZkSGN8h/hIpoFC8g==",
|
||||
"requires": {
|
||||
"os-name": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"url-template": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
|
||||
"integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"windows-release": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz",
|
||||
"integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==",
|
||||
"requires": {
|
||||
"execa": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"name": "@actions/toolkit",
|
||||
"version": "0.0.0",
|
||||
"description": "Base actions tools for the GitHub API and GitHub context",
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions",
|
||||
"toolkit"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/toolkit",
|
||||
"license": "MIT",
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/exit": "^0.0.0",
|
||||
"@octokit/rest": "^16.25.0",
|
||||
"signale": "^1.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as exit from '@actions/exit'
|
||||
import {Signale} from 'signale'
|
||||
|
||||
/**
|
||||
* A class that wraps some basic methods of exiting from an action
|
||||
*/
|
||||
export class Exit {
|
||||
constructor(private readonly logger: Signale) {}
|
||||
|
||||
/**
|
||||
* Stop the action with a "success" status.
|
||||
*/
|
||||
success(message?: string): void {
|
||||
if (message) this.logger.success(message)
|
||||
exit.success()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the action with a "neutral" status.
|
||||
*/
|
||||
neutral(message?: string): void {
|
||||
if (message) this.logger.info(message)
|
||||
exit.neutral()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the action with a "failed" status.
|
||||
*/
|
||||
failure(message?: string): void {
|
||||
if (message) this.logger.fatal(message)
|
||||
exit.failure()
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import {LoggerFunc, Signale} from 'signale'
|
||||
import {Exit} from './exit'
|
||||
|
||||
export type ActionFn = (tools: Toolkit) => unknown
|
||||
|
||||
/**
|
||||
* Options used to customize an instance of [[Toolkit]]
|
||||
*/
|
||||
export interface ToolkitOptions {
|
||||
/**
|
||||
* A custom Signale instance to use
|
||||
*/
|
||||
logger?: Signale
|
||||
|
||||
/**
|
||||
* A list of environment variable names this action requires in order to run
|
||||
*
|
||||
* If any of them are missing, the action will fail and log the missing keys.
|
||||
*/
|
||||
requiredEnv?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of tools for the Actions runtime
|
||||
*/
|
||||
export class Toolkit {
|
||||
/**
|
||||
* Run an asynchronous function that accepts a toolkit as its argument.
|
||||
*
|
||||
* If an error occurs, the error will be logged and the action will exit as a
|
||||
* failure.
|
||||
*/
|
||||
static async run(func: ActionFn, opts?: ToolkitOptions): Promise<void> {
|
||||
const tools = new Toolkit(opts)
|
||||
|
||||
try {
|
||||
const ret = func(tools)
|
||||
return ret instanceof Promise ? await ret : ret
|
||||
} catch (err) {
|
||||
tools.exit.failure(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A logger for the toolkit, an instance of [Signale](https://github.com/klaussinani/signale)
|
||||
*/
|
||||
readonly logger: Signale & LoggerFunc
|
||||
|
||||
/**
|
||||
* A wrapper around an instance of [[Exit]]
|
||||
*/
|
||||
readonly exit: Exit
|
||||
|
||||
/**
|
||||
* The authentication token for the GitHub API
|
||||
*/
|
||||
readonly token: string = process.env.GITHUB_TOKEN || ''
|
||||
|
||||
constructor(opts: ToolkitOptions = {}) {
|
||||
const logger = opts.logger || new Signale({config: {underlineLabel: false}})
|
||||
this.logger = this.wrapLogger(logger)
|
||||
this.exit = new Exit(this.logger)
|
||||
|
||||
if (opts.requiredEnv) {
|
||||
this.checkRequiredEnv(opts.requiredEnv)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the given keys are in the environment.
|
||||
*/
|
||||
private checkRequiredEnv(keys: string[]): void {
|
||||
const missingEnv = keys.filter(key => !process.env.hasOwnProperty(key))
|
||||
|
||||
if (missingEnv.length === 0) return
|
||||
|
||||
const list = missingEnv.map(key => `- ${key}`).join('\n')
|
||||
|
||||
this.exit.failure(
|
||||
`The following environment variables are required for this action to run:\n${list}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a Signale logger so that its a callable class.
|
||||
*/
|
||||
private wrapLogger(logger: Signale): Signale & LoggerFunc {
|
||||
// Create a callable function
|
||||
const fn = logger.info.bind(logger)
|
||||
// Add the log methods onto the function
|
||||
const wrapped = Object.assign(fn, logger)
|
||||
// Clone the prototype
|
||||
Object.setPrototypeOf(wrapped, logger)
|
||||
return wrapped
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Reference in New Issue
Block a user