Compare commits

..

4 Commits

Author SHA1 Message Date
Yang Chen d27f7ec817 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.3.0.
  Force use Java 8 for sdkmanager and avdmanager.
  Update checkout action to v2.
2020-01-20 03:57:13 +11:00
Yang Chen 3f52989fc0 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.2.0.
  Stop pinning emulator build in workflow.
  Add support for pinning specific emulator build.
  Explicitly set GPU mode to swiftshader_indirect.
  Fix script execution order. Run test fixture in workflow.
2019-12-15 04:32:53 +11:00
Yang Chen da41ec93d8 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.1.0.
  Support multi-line script.
  Skip workflow for markdown file changes.
  Reference Article
2019-12-14 00:36:51 +11:00
Yang Chen f079acaf6d Release v2. 2019-12-07 18:09:02 +11:00
228 changed files with 20065 additions and 12729 deletions
+3
View File
@@ -5,6 +5,9 @@
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"globals": {
-13
View File
@@ -1,13 +0,0 @@
name: Validate action typings
on:
push:
pull_request:
workflow_dispatch:
jobs:
validate-typings:
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v7
- uses: krzema12/github-actions-typing@v2
-128
View File
@@ -1,128 +0,0 @@
name: Main workflow
on:
workflow_call:
pull_request:
paths-ignore:
- '**.md'
push:
branches:
- main
paths-ignore:
- '**.md'
jobs:
test:
runs-on: ${{ matrix.os }}
env:
JAVA_TOOL_OPTIONS: -Xmx4g
timeout-minutes: 15
strategy:
matrix:
include:
- os: ubuntu-22.04
api-level: 23
target: default
arch: x86
- os: ubuntu-24.04
api-level: 24
target: playstore
arch: x86
- os: macos-15-intel
api-level: 31
target: default
arch: x86_64
- os: ubuntu-24.04
api-level: 34
target: aosp_atd
arch: x86_64
- os: ubuntu-latest
api-level: 35
target: google_apis
arch: x86_64
- os: ubuntu-latest
api-level: 34-ext10
target: android-automotive
arch: x86_64
system-image-api-level: 34-ext9
steps:
- name: checkout
uses: actions/checkout@v7
- name: build, test and lint
run: |
npm install
npm run build
npm run lint
npm test
- uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: 25
- uses: actions/cache@v6
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
~/.android/debug.keystore
key: avd-${{ matrix.api-level }}-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.arch }}
- uses: gradle/actions/setup-gradle@v6
- name: assemble tests
working-directory: test-fixture
run: ./gradlew assembleAndroidTest
- name: enable KVM for linux runners
if: runner.os == 'Linux'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run emulator to generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: ./
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
system-image-api-level: ${{ matrix.system-image-api-level }}
profile: Galaxy Nexus
cores: 2
sdcard-path-or-size: 100M
avd-name: test
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
working-directory: ./test-fixture/
channel: canary
script: echo "Generated AVD snapshot for caching."
- name: run action
uses: ./
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
system-image-api-level: ${{ matrix.system-image-api-level }}
profile: Galaxy Nexus
cores: 2
ram-size: 2048M
sdcard-path-or-size: 100M
avd-name: test
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
working-directory: ./test-fixture/
channel: canary
pre-emulator-launch-script: |
echo "Running pre emulator launch script. Printing the working directory now:"
pwd
script: |
echo $GITHUB_REPOSITORY
adb devices
./gradlew connectedDebugAndroidTest
-79
View File
@@ -1,79 +0,0 @@
name: Manually triggered workflow
on:
workflow_dispatch:
inputs:
os:
description: 'OS'
required: true
default: 'ubuntu-latest'
api-level:
description: 'API level of the platform and system image (if not overridden with system-image-api-level input) - e.g. 33, 35-ext15, Baklava'
required: true
default: '34'
system-image-api-level:
description: 'API level of the system image - e.g. 34-ext10, 35-ext15'
target:
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop'
required: true
default: 'default'
arch:
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
default: 'x86_64'
emulator-options:
description: 'command-line options used when launching the emulator'
default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
emulator-build:
description: 'build number of a specific version of the emulator binary to use'
channel:
description: 'Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`'
default: 'stable'
script:
description: 'custom script to run - e.g. `./gradlew connectedCheck`'
required: true
default: './gradlew connectedDebugAndroidTest'
jobs:
test:
runs-on: ${{ github.event.inputs.os }}
env:
JAVA_TOOL_OPTIONS: -Xmx4g
timeout-minutes: 15
steps:
- name: checkout
uses: actions/checkout@v7
- name: build, test and lint
run: |
npm install
npm run build
npm run lint
npm test
- uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: 25
- uses: gradle/actions/setup-gradle@v6
- name: enable KVM for linux runners
if: runner.os == 'Linux'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run action
uses: ./
with:
api-level: ${{ github.event.inputs.api-level }}
target: ${{ github.event.inputs.target }}
arch: ${{ github.event.inputs.arch }}
system-image-api-level: ${{ github.event.inputs.system-image-api-level }}
profile: Galaxy Nexus
emulator-options: ${{ github.event.inputs.emulator-options }}
emulator-build: ${{ github.event.inputs.emulator-build }}
channel: ${{ github.event.inputs.channel }}
working-directory: ./test-fixture/
script: ${{ github.event.inputs.script }}
-9
View File
@@ -1,9 +0,0 @@
name: Re-Run Tests on PR Comment Workflow
on:
issue_comment:
types: [created]
jobs:
rerun-tests-job:
if: github.event.issue.pull_request && contains(github.event.comment.body, 'run tests') # if comment is created on a PR, can also use the syntax if: contains(github.event.comment.html_url, '/pull/')
uses: ./.github/workflows/main.yml
+47
View File
@@ -0,0 +1,47 @@
name: Main workflow
on:
pull_request:
push:
branches:
- master
paths-ignore:
- '**.md'
jobs:
test:
runs-on: macOS-latest
timeout-minutes: 10
strategy:
matrix:
api-level: [21, 23, 29]
steps:
- name: checkout
uses: actions/checkout@v2
- name: build, test and lint
run: |
npm install
npm run build
npm run lint
npm test
- name: Prepare test fixture
run: cp -a ./test-fixture/* ./
- name: Java 13
uses: actions/setup-java@v1
with:
java-version: 13
- name: run action
uses: ./
with:
api-level: ${{ matrix.api-level }}
target: google_apis
arch: x86
profile: Nexus 6
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
./gradlew help
./gradlew connectedDebugAndroidTest
+1 -11
View File
@@ -1,7 +1,7 @@
__tests__/runner/*
# comment out in distribution branches
node_modules/
# node_modules/
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
@@ -91,13 +91,3 @@ typings/
# DynamoDB Local files
.dynamodb/
# Mac
.DS_Store
# Vscode
.vscode
# IntelliJ IDEA
.idea
*.iml
-1
View File
@@ -1 +0,0 @@
24
-241
View File
@@ -1,246 +1,5 @@
# Change Log
## Unreleased
* Update SDK command-line tools to `23.0`. - [#492](https://github.com/ReactiveCircus/android-emulator-runner/pull/492)
## v2.38.0
* Update SDK build tools to `37.0.0`. - [#475](https://github.com/ReactiveCircus/android-emulator-runner/pull/475)
## v2.37.0
* Update SDK build tools to `36.0.0`, update SDK command-line tools to `20.0`. - [#467](https://github.com/ReactiveCircus/android-emulator-runner/pull/467)
* Run action on Node 24. - [#466](https://github.com/ReactiveCircus/android-emulator-runner/pull/466)
* Update Node and npm packages. - [#466](https://github.com/ReactiveCircus/android-emulator-runner/pull/466)
## v2.36.0
* Remove `--abi` option to better support 16-KB-based system images (#456).
## v2.35.0
* Optimize config.ini updates and efficiency improvements report (#436).
* Fix `pre-emulator-launch-script` (#439).
* Allow `google_apis_ps16k` as a valid target (#440).
## v2.34.0
* Allow arbitrary value for `api-level` such as `Baklava` and `35-ext15`. - [428](https://github.com/ReactiveCircus/android-emulator-runner/pull/428)
* Add option to specify `system-image-api-level` to use a system image with a different API level than the platform. - [428](https://github.com/ReactiveCircus/android-emulator-runner/pull/428)
* Support automotive and desktop targets: `android-automotive`, `android-automotive-playstore`, `android-desktop`. - [428](https://github.com/ReactiveCircus/android-emulator-runner/pull/428)
## v2.33.0
* Fix missing AVD directory in `ubuntu-24.04` runner. - [415](https://github.com/ReactiveCircus/android-emulator-runner/pull/415)
* Update SDK build tools to `35.0.0`, update SDK command-line tools to `16.0`. - [#356](https://github.com/ReactiveCircus/android-emulator-runner/pull/356)
* Stop changing owner of the SDK directory. - [406](https://github.com/ReactiveCircus/android-emulator-runner/pull/406) [#409](https://github.com/ReactiveCircus/android-emulator-runner/pull/409)
* Upgrade to latest npm dependencies. - [#401](https://github.com/ReactiveCircus/android-emulator-runner/pull/401)
## v2.32.0
* Add `port` parameter for customizing the emulator port to use. - [#383](https://github.com/ReactiveCircus/android-emulator-runner/pull/383)
## v2.31.0
* Support setting `VanillaIceCream` as `api-level`. - [#378](https://github.com/ReactiveCircus/android-emulator-runner/pull/378)
* Install `platforms` for the specified `api-level`. - [#384](https://github.com/ReactiveCircus/android-emulator-runner/pull/384)
## v2.30.1
* Run action on Node 20. - [#371](https://github.com/ReactiveCircus/android-emulator-runner/pull/371)
## v2.30.0
* Update to Node 20. - [#369](https://github.com/ReactiveCircus/android-emulator-runner/pull/369)
## v2.29.0
* Fixed emulator download URL. - [#343](https://github.com/ReactiveCircus/android-emulator-runner/pull/343)
* Upgrade to latest npm dependencies. - [#347](https://github.com/ReactiveCircus/android-emulator-runner/pull/347) [#355](https://github.com/ReactiveCircus/android-emulator-runner/pull/355)
* Update SDK command-line tools to `11.0`. - [#356](https://github.com/ReactiveCircus/android-emulator-runner/pull/356)
* Update SDK build tools to `34.0.0`. - [#356](https://github.com/ReactiveCircus/android-emulator-runner/pull/356)
## v2.28.0
* Add `emulator-boot-timeout` to support configuring maximum time waiting for emulator boot. - [#326](https://github.com/ReactiveCircus/android-emulator-runner/pull/326)
* Support non-integer `api-level`. - [#317](https://github.com/ReactiveCircus/android-emulator-runner/pull/317)
* Replace deprecated `ANDROID_SDK_ROOT` with `ANDROID_HOME`. - [304](https://github.com/ReactiveCircus/android-emulator-runner/pull/304)
* Update SDK command-line tools to `9.0`. - [#331](https://github.com/ReactiveCircus/android-emulator-runner/pull/331)
* Update SDK build tools to `33.0.2`. - [#331](https://github.com/ReactiveCircus/android-emulator-runner/pull/331)
## v2.27.0
* Added `pre-emulator-launch-script` to support running script after creating the AVD and before launching the emulator. - [#247](https://github.com/ReactiveCircus/android-emulator-runner/pull/247) @nilsreichardt.
* Update to Node 16. - [#276](https://github.com/ReactiveCircus/android-emulator-runner/pull/276) @mattjohnsonpint.
* Update NPM dependencies. - [#282](https://github.com/ReactiveCircus/android-emulator-runner/pull/282) @mattjohnsonpint.
* Update README with more context on hardware acceleration on GitHub hosted runners. - [#279](https://github.com/ReactiveCircus/android-emulator-runner/pull/279) @mrk-han.
## v2.26.0
* Support [github-actions-typing](https://github.com/krzema12/github-actions-typing). - [#257](https://github.com/ReactiveCircus/android-emulator-runner/pull/257) @LeoColman.
## v2.25.0
* Auto detect hardware acceleration on Linux. - [#254](https://github.com/ReactiveCircus/android-emulator-runner/pull/254) @notbigdata.
* Update build tools to `33.0.0`.
* Update SDK command-line tools to `7.0`.
## v2.24.0
* Add option to specify `heap-size` for the AVD. - [#245](https://github.com/ReactiveCircus/android-emulator-runner/pull/245) @timusus.
## v2.23.0
* Update build tools to `32.0.0`. - [#212](https://github.com/ReactiveCircus/android-emulator-runner/pull/212)
* Update SDK command-line tools to `6.0`. - [#213](https://github.com/ReactiveCircus/android-emulator-runner/pull/213)
* Add option to specify `disk-size` for the AVD. - [#219](https://github.com/ReactiveCircus/android-emulator-runner/pull/219) @ViliusSutkus89.
* Improve logging by grouping log lines. - [#224](https://github.com/ReactiveCircus/android-emulator-runner/pull/224) @michaelkaye.
## v2.22.0
* Add option to enable hardware keyboard. - [#209](https://github.com/ReactiveCircus/android-emulator-runner/pull/209) (upstreamed from the [Doist fork](https://github.com/Doist/android-emulator-runner/commit/4b6ca99f0d657662beca3eb0c22d8e254fbd5b31)).
* Update README and fix typos. - [#203](https://github.com/ReactiveCircus/android-emulator-runner/pull/203) @JPrendy.
## v2.21.0
* Support new [ATD](https://developer.android.com/studio/preview/features#gmd-atd) targets optimized to reduce CPU and memory resources when running instrumented tests: `aosp_atd`, `google_atd`. - [#198](https://github.com/ReactiveCircus/android-emulator-runner/pull/198). Note that currently these targets require the following:
* `api-level: 30`
* `arch: x86` or `arch: arm64-v8a`
* `channel: canary`
## v2.20.0
* Support non-mobile targets: `android-wear`, `android-wear-cn`, `android-tv` and `google-tv`. - [#180](https://github.com/ReactiveCircus/android-emulator-runner/pull/180) @alexvanyo.
* Update SDK command-line tools to `5.0`. - [#174](https://github.com/ReactiveCircus/android-emulator-runner/pull/174)
* Update build tools to `31.0.0`. - [#174](https://github.com/ReactiveCircus/android-emulator-runner/pull/174)
* Add option to specify the `channel` to download SDK components from: `stable` (default), `beta`, `dev` and `canary`. - [#185](https://github.com/ReactiveCircus/android-emulator-runner/pull/185)
## v2.19.1
* Accept all Android SDK Licenses to fix `sdkmanager` installation. - [#172](https://github.com/ReactiveCircus/android-emulator-runner/pull/172) @marcuspridham.
## v2.19.0
* Add option to specify `ram-size` for the AVD. - [#165](https://github.com/ReactiveCircus/android-emulator-runner/pull/165).
## v2.18.1
* Added support for setting modern emulator build ids for `emulator-build`. Not all build ids are supported until we are able to figure out at which build id the download URL pattern changed. `7425822` (version 30.7.3) is currently the last known working build id.
## v2.18.0
* Add `force-avd-creation` which when set to `false` will skip avd creation if avd with same name exists. This enables AVD snapshot caching which can significantly reduce emulator startup time. See [README.md](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md#usage) for a sample workflow. - [#159](https://github.com/ReactiveCircus/android-emulator-runner/pull/159)
## v2.17.0
* Add option to toggle Linux hardware acceleration - [#154](https://github.com/ReactiveCircus/android-emulator-runner/pull/154) @stevestotter
## v2.16.0
* Avoid wrapping script code in quotes - [#134](https://github.com/ReactiveCircus/android-emulator-runner/pull/134) @hostilefork
* Add option to disable spellcheck - [#143](https://github.com/ReactiveCircus/android-emulator-runner/pull/143) @AfzalivE
* Add support for arm64-v8a for Apple Silicon Macs - [#146](https://github.com/ReactiveCircus/android-emulator-runner/pull/146) @Jeehut
## v2.15.0
* Added support for specifying the number of cores to use for the emulator - [#130](https://github.com/ReactiveCircus/android-emulator-runner/pull/130).
## v2.14.3
* Support `macos-11.0` (Big Sur) runner - [#124](https://github.com/ReactiveCircus/android-emulator-runner/pull/124).
## v2.14.2
* Support API 28 system images with `google_apis` or `google_apis_playstore` target - [#117](https://github.com/ReactiveCircus/android-emulator-runner/pull/117).
## v2.14.1
* Fix hang during AVD creation when `profile` is not specified - [#113](https://github.com/ReactiveCircus/android-emulator-runner/issues/113).
## v2.14.0
* Support specifying SD card path or size via `sdcard-path-or-size`.
* Update npm packages.
* Remove usages of deprecated `$ANDROID_HOME`.
## v2.13.0
* Updated to SDK command-line tools `3.0`.
## v2.12.0
Added support for using the `playstore` system images:
```
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
target: playstore
arch: x86
script: ./gradlew connectedCheck
```
## v2.11.1
* Update SDK command-line tools to `2.1`.
* Update @actions/core to `1.2.6`.
## v2.11.0
* Support running multiple instances of the action sequentially in a single job - [#73](https://github.com/ReactiveCircus/android-emulator-runner/issues/73).
## v2.10.0
* Support Android 11 (API 30) system images.
* Bump build tools to `30.0.0`.
## v2.9.0
* Updated to SDK command-line tools `2.0`.
## v2.8.0
* Added support for specifying a custom name used for creating the **AVD** - [#59](https://github.com/ReactiveCircus/android-emulator-runner/issues/59).
## v2.7.0
* Added support for specifying versions of **NDK** and **CMake** to install.
## v2.6.2
* Fixed an issue where the Linux command-line tools binary is used for `macos`.
## v2.6.1
* Fixed SDK license issue on Linux when downloading API 28+ system images - [#42](https://github.com/ReactiveCircus/android-emulator-runner/issues/42).
## v2.6.0
* Added support for Linux VMs (no hardware acceleration) - [#15](https://github.com/ReactiveCircus/android-emulator-runner/issues/15).
## v2.5.0
* Added support for API 15-19 system images - [#26](https://github.com/ReactiveCircus/android-emulator-runner/issues/26).
* Switched to the new SDK command-line tools which supports running `sdkmanager` and `avdmanager` with Java 9+ - [#25](https://github.com/ReactiveCircus/android-emulator-runner/issues/25).
## v2.4.0
* Added support for setting custom `working-directory` - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository - [#22](https://github.com/ReactiveCircus/android-emulator-runner/issues/22).
## v2.3.2
* Fixed an issue where environment variables are escaped in script - [#19](https://github.com/ReactiveCircus/android-emulator-runner/issues/19).
## v2.3.1
* Bumped Android Build tools to 29.0.3.
## v2.3.0
* Added support for running the action with Java 9+ by forcing SDK manager and AVD manager to use Java 8.
+35 -231
View File
@@ -4,33 +4,14 @@
<a href="https://github.com/ReactiveCircus/android-emulator-runner"><img alt="GitHub Actions status" src="https://github.com/ReactiveCircus/android-emulator-runner/workflows/Main%20workflow/badge.svg"></a>
</p>
A GitHub Action for installing, configuring and running hardware-accelerated Android Emulators on Linux and macOS virtual machines.
A GitHub Action for installing, configuring and running Android Emulators on macOS virtual machines.
The old ARM-based emulators were slow and are no longer supported by Google. The modern Intel Atom (x86 and x86_64) emulators can be fast, but rely on two forms of hardware acceleration to reach their peak potential: [Graphics Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-graphics), e.g. `emulator -gpu host` and [Virtual Machine(VM) Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-vm), e.g. `emulator -accel on`. **Note:** GPU and VM Acceleration are two different and non-mutually exclusive forms of Hardware Acceleration.
The old ARM-based emulators were slow and are no longer supported by Google. The modern Intel Atom (x86 and x86_64) emulators require hardware acceleration (HAXM on Mac & Windows, QEMU on Linux) from the host to run fast. This presents a challenge on CI as to be able to run hardware accelerated emulators within a docker container, **KVM** must be supported by the host VM which isn't the case for cloud-based CI providers due to infrastructural limits. If you want to learn more about this, here's an article I wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76).
This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76).
The **masOS** VM provided by **GitHub Actions** has **HAXM** installed so we are able to create a new AVD instance, launch an emulator with hardware acceleration, and run our Android
tests directly on the VM.
## Running hardware accelerated emulators on Linux runners
GitHub's [larger Linux runners support running hardware accelerated emulators](https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/). It is now recommended to use the **Ubuntu** (`ubuntu-latest`) runners which are 2-3 times faster than the **macOS** ones which are also a lot more expensive. Remember to enable KVM in your workflow before running this action:
```
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
```
## A note on VM Acceleration and why we don't need HAXM anymore
According to [this documentation](https://developer.android.com/studio/run/emulator-acceleration#vm-mac), "on Mac OS X v10.10 Yosemite and higher, the Android Emulator uses the built-in [Hypervisor.Framework](https://developer.apple.com/documentation/hypervisor) by default, and falls back to using Intel HAXM if Hypervisor.Framework fails to initialize." This means that **HAXM is only needed to achieve VM Acceleration if this default Hypervisor is not available on macOS machines.**
**Note**: Manually enabling and downloading HAXM is not recommended because it is redundant and not needed (see above), and for users of macOS 10.13 High Sierra and higher: macOS 10.13 [disables installation of kernel extensions by default](https://developer.apple.com/library/archive/technotes/tn2459/_index.html#//apple_ref/doc/uid/DTS40017658). Because Intel HAXM is a kernel extension, we would need to manually enable its installation on the base runner VM. Furthermore, manually trying to install HAXM on a Github Runner [brings up a popup](https://github.com/ReactiveCircus/android-emulator-runner/discussions/286#discussioncomment-4026120) which further hinders tests from running.
## Purpose
This action helps automate and configure the process of setting up an emulator and running your tests by doing the following:
This action automates the process by doing the following:
- Install / update the required **Android SDK** components including `build-tools`, `platform-tools`, `platform` (for the required API level), `emulator` and `system-images` (for the required API level).
- Create a new instance of **AVD** with the provided [configurations](#configurations).
@@ -39,239 +20,62 @@ This action helps automate and configure the process of setting up an emulator a
- Run a custom script provided by user once the Emulator is up and running - e.g. `./gradlew connectedCheck`.
- Kill the Emulator and finish the action.
## Usage & Examples
## Usage
Note that this action must be run on a **macOS** VM, e.g. `macOS-latest` or `macOS-10.14`.
A workflow that uses **android-emulator-runner** to run your instrumented tests on **API 29**:
```yml
```
jobs:
test:
runs-on: ubuntu-latest
runs-on: macOS-latest
steps:
- name: checkout
uses: actions/checkout@v6
- name: checkout
uses: actions/checkout@v2
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedCheck
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedCheck
```
We can also leverage GitHub Actions's build matrix to test across multiple configurations:
```yml
```
jobs:
test:
runs-on: ubuntu-latest
runs-on: macOS-latest
strategy:
matrix:
api-level: [21, 23, 29]
target: [default, google_apis]
steps:
- name: checkout
uses: actions/checkout@v6
- name: checkout
uses: actions/checkout@v2
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: pixel_7_pro
script: ./gradlew connectedCheck
```
If you need specific versions of **NDK** and **CMake** installed:
```yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v6
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
ndk: 21.0.6113669
cmake: 3.10.2.4988404
script: ./gradlew connectedCheck
```
If you need a specific [SDK Extensions](https://developer.android.com/guide/sdk-extensions) for the system image but not the platform:
```yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v6
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
system-image-api-level: 34-ext9
target: android-automotive
script: ./gradlew connectedCheck
```
We can significantly reduce emulator startup time by setting up AVD snapshot caching:
1. add a `gradle/actions/setup-gradle@v5` step for caching Gradle, more details see [#229](https://github.com/ReactiveCircus/android-emulator-runner/issues/229)
2. add an `actions/cache@v5` step for caching the `avd`
3. add a `reactivecircus/android-emulator-runner@v2` step to generate a clean snapshot - specify `emulator-options` without `no-snapshot`
4. add another `reactivecircus/android-emulator-runner@v2` step to run your tests using existing AVD / snapshot - specify `emulator-options` with `no-snapshot-save`
```yml
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [21, 23, 29]
steps:
- name: checkout
uses: actions/checkout@v6
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Gradle cache
uses: gradle/actions/setup-gradle@v5
- name: AVD cache
uses: actions/cache@v5
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ matrix.api-level }}
- name: create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./gradlew connectedCheck
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
script: ./gradlew connectedCheck
```
## Configurations
| **Input** | **Required** | **Default** | **Description** |
|-|-|-|-|
| `api-level` | Required | N/A | API level of the platform and system image - e.g. `23`, `33`, `35-ext15`, `Baklava`. **Minimum API level supported is 15**. |
| `system-image-api-level` | Optional | same as `api-level` | API level of the system image - e.g. `34-ext10`, `35-ext15`. |
| `target` | Optional | `default` | Target of the system image - e.g. `default`, `google_apis`, `google_apis_ps16k`, `google_apis_playstore`, `google_apis_playstore_ps16k`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd`, `google_atd`, `android-automotive`, `android-automotive-playstore`, `android-desktop`. Please run `sdkmanager --list` to see the available targets. |
| `arch` | Optional | `x86` | CPU architecture of the system image - `x86`, `x86_64` or `arm64-v8a`. Note that `x86_64` image is only available for API 21+. `arm64-v8a` images require Android 4.2+ and are limited to fewer API levels (e.g. 30). |
| `profile` | Optional | N/A | Hardware profile id used for creating the AVD - e.g. `pixel_7_pro`. For a list of all profiles available, run `avdmanager list device`. |
| `cores` | Optional | 2 | Number of cores to use for the emulator (`hw.cpu.ncore` in config.ini). |
| `ram-size` | Optional | N/A | Size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M` |
| `heap-size` | Optional | N/A | Heap size to use for this AVD, in KB or MB, denoted with K or M. - e.g. `512M` |
| `sdcard-path-or-size` | Optional | N/A | Path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`. |
| `disk-size` | Optional | N/A | Disk size, or partition size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G. - e.g. `2048M` |
| `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. |
| `force-avd-creation` | Optional | `true` | Whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`. |
| `emulator-boot-timeout` | Optional | `600` | Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes. |
| `emulator-port` | Optional | `5554` | Emulator port to use. Allows to run this action on multiple workers on a single machine at the same time. This input is available for the script as `EMULATOR_PORT` enviromental variable. This port is automatically used by android device related tasks in gradle |
| | **Required** | **Default** | **Description** |
|----------------------|--------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `api-level` | Required | N/A | API level of the platform system image - e.g. 23 for Android Marshmallow, 29 for Android 10. **Minimum API level supported is 21**. |
| `target` | Optional | `default` | Target of the system image - `default` or `google_apis`. |
| `arch` | Optional | `x86` | CPU architecture of the system image - `x86` or `x86_64`. |
| `profile` | Optional | N/A | Hardware profile used for creating the AVD - e.g. `Nexus 6`. For a list of all profiles available, run `$ANDROID_HOME/tools/bin/avdmanager list` and refer to the results under "Available Android Virtual Devices". |
| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. |
| `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. |
| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. |
| `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`.|
| `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. |
| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. |
| `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. |
| `ndk` | Optional | N/A | Version of NDK to install - e.g. `21.0.6113669` |
| `cmake` | Optional | N/A | Version of CMake to install - e.g. `3.10.2.4988404` |
| `channel` | Optional | stable | Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary` |
| `script` | Required | N/A | Custom script to run - e.g. to run Android instrumented tests on the emulator: `./gradlew connectedCheck` |
| `pre-emulator-launch-script` | Optional | N/A | Custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh` |
Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`.
## Who is using Android Emulator Runner?
These are some of the open-source projects using (or used) **Android Emulator Runner**:
- [coil-kt/coil](https://github.com/coil-kt/coil/blob/master/.github/workflows)
- [cashapp/sqldelight](https://github.com/cashapp/sqldelight/blob/master/.github/workflows)
- [square/workflow-kotlin](https://github.com/square/workflow-kotlin/tree/main/.github/workflows)
- [square/retrofit](https://github.com/square/retrofit/blob/master/.github/workflows)
- [natario1/CameraView](https://github.com/natario1/CameraView/tree/master/.github/workflows)
- [natario1/Transcoder](https://github.com/natario1/Transcoder/tree/master/.github/workflows)
- [chrisbanes/insetter](https://github.com/chrisbanes/insetter/tree/main/.github/workflows)
- [slackhq/keeper](https://github.com/slackhq/keeper/tree/main/.github/workflows)
- [android/compose-samples](https://github.com/android/compose-samples/tree/main/.github/workflows)
- [ReactiveCircus/streamlined](https://github.com/ReactiveCircus/streamlined/tree/main/.github/workflows)
- [ReactiveCircus/FlowBinding](https://github.com/ReactiveCircus/FlowBinding/tree/main/.github/workflows)
- [JakeWharton/RxBinding](https://github.com/JakeWharton/RxBinding/tree/master/.github/workflows)
- [vinaygaba/Learn-Jetpack-Compose-By-Example](https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example/tree/master/.github/workflows)
- [ashishb/adb-enhanced](https://github.com/ashishb/adb-enhanced/tree/master/.github/workflows)
- [vgaidarji/ci-matters](https://github.com/vgaidarji/ci-matters/blob/master/.github/workflows/main.yaml)
- [simpledotorg/simple-android](https://github.com/simpledotorg/simple-android/tree/master/.github/workflows)
- [cashapp/copper](https://github.com/cashapp/copper/blob/trunk/.github/workflows/build.yaml)
- [square/radiography](https://github.com/square/radiography/blob/main/.github/workflows/android.yml)
- [Shopify/android-testify](https://github.com/Shopify/android-testify/blob/master/.github/workflows/sample_build.yml)
- [square/leakcanary](https://github.com/square/leakcanary/tree/main/.github/workflows)
- [hash-checker/hash-checker](https://github.com/hash-checker/hash-checker/tree/master/.github/workflows)
- [hash-checker/hash-checker-lite](https://github.com/hash-checker/hash-checker-lite/tree/master/.github/workflows)
- [Kiwix/kiwix-android](https://github.com/kiwix/kiwix-android/blob/develop/.github/workflows)
- [wikimedia/apps-android-wikipedia](https://github.com/wikimedia/apps-android-wikipedia/blob/main/.github/workflows)
- [google/android-fhir](https://github.com/google/android-fhir/tree/master/.github/workflows)
- [google/accompanist](https://github.com/google/accompanist/blob/main/.github/workflows)
- [dotanuki-labs/norris](https://github.com/dotanuki-labs/norris/blob/master/.github/workflows/main.yml)
- [tinylog-org/tinylog](https://github.com/tinylog-org/tinylog/blob/v3.0/.github/workflows/build.yaml)
- [hzi-braunschweig/SORMAS-Project](https://github.com/hzi-braunschweig/SORMAS-Project/blob/development/.github/workflows/sormas_app_ci.yml)
- [ACRA/acra](https://github.com/ACRA/acra/blob/master/.github/workflows/test.yml)
- [bitfireAT/davx5-ose](https://github.com/bitfireAT/davx5-ose/blob/dev-ose/.github/workflows/test-dev.yml)
- [robolectric/robolectric](https://github.com/robolectric/robolectric/blob/master/.github/workflows/tests.yml)
- [home-assistant/android](https://github.com/home-assistant/android/blob/master/.github/workflows/pr.yml)
- [composablehorizons/compose-unstyled](https://github.com/composablehorizons/compose-unstyled/tree/main/.github/workflows)
- [f3d-app/f3d-android](https://github.com/f3d-app/f3d-android/blob/master/.github/workflows/ci.yml)
- [freeorion/freeorion](https://github.com/freeorion/freeorion/blob/master/.github/workflows/_build-android.yml)
If you are using **Android Emulator Runner** and want your project included in the list, please feel free to open a pull request.
+4 -4
View File
@@ -1,10 +1,10 @@
# GitHub Action - Android Emulator Runner
Refer to the [recommendations for versioning and releasing actions](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations).
Refer to the [recommendeations for versioning and releasing actions](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md#recommendations).
## New major release
- From `main` branch, run `npm run build && npm test --clean && npm run lint` to make sure `lib/*.js` are up-to-date.
- From `master` branch, run `npm run build && npm test --clean && npm run lint` to make sure `lib/*.js` are up-to-date.
- Update `CHANGELOG.md`, push change with "Prepare for release X.Y.Z." (where X.Y.Z is the new version).
- Create a new branch e.g. `release/v1`, comment out `node_modules/` in `.gitignore`, commit the change (do not commit yet `node_modules`).
- Run `npm prune --production`.
@@ -17,9 +17,9 @@ Refer to the [recommendations for versioning and releasing actions](https://gith
## New minor / patch release
- From `main` branch, run `npm run build && npm test --clean && npm run lint` to make sure `lib/*.js` are up-to-date.
- From `master` branch, run `npm run build && npm test --clean && npm run lint` to make sure `lib/*.js` are up-to-date.
- Update `CHANGELOG.md`, push change with "Prepare for release X.Y.Z." (where X.Y.Z is the new version).
- Merge from `main` into the release branch e.g. `release/v1`.
- Merge from `master` into the release branch e.g. `release/v1`.
- Run `npm prune --production`.
- Commit merged changes (and the pruned `node_modules`).
- Push to remote.
-17
View File
@@ -1,17 +0,0 @@
import * as mapper from '../src/channel-id-mapper';
describe('channel id mapper tests', () => {
it('Throws if channelName is unknown', () => {
const func = () => {
mapper.getChannelId('unknown-channel');
};
expect(func).toThrowError(`Unexpected channel name: 'unknown-channel'.`);
});
it('Returns expected channelId from channelName', () => {
expect(mapper.getChannelId('stable')).toBe(0);
expect(mapper.getChannelId('beta')).toBe(1);
expect(mapper.getChannelId('dev')).toBe(2);
expect(mapper.getChannelId('canary')).toBe(3);
});
});
+56 -199
View File
@@ -1,5 +1,60 @@
import * as validator from '../src/input-validator';
import { MAX_PORT, MIN_PORT } from '../src/input-validator';
describe('api-level validator tests', () => {
it('Throws if api-level is not a number', () => {
const func = () => {
validator.checkApiLevel('api');
};
expect(func).toThrowError(`Unexpected API level: 'api'.`);
});
it('Throws if api-level is not an integer', () => {
const func = () => {
validator.checkApiLevel('29.1');
};
expect(func).toThrowError(`Unexpected API level: '29.1'.`);
});
it('Throws if api-level is lower than min API supported', () => {
const func = () => {
validator.checkApiLevel('20');
};
expect(func).toThrowError(`Minimum API level supported is ${validator.MIN_API_LEVEL}.`);
});
it('Validates successfully with valid api-level', () => {
const func1 = () => {
validator.checkApiLevel('21');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkApiLevel('29');
};
expect(func2).not.toThrow();
});
});
describe('target validator tests', () => {
it('Throws if target is unknown', () => {
const func = () => {
validator.checkTarget('some-target');
};
expect(func).toThrowError(`Value for input.target 'some-target' is unknown. Supported options: ${validator.VALID_TARGETS}`);
});
it('Validates successfully with valid target', () => {
const func1 = () => {
validator.checkTarget('default');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkTarget('google_apis');
};
expect(func2).not.toThrow();
});
});
describe('arch validator tests', () => {
it('Throws if arch is unknown', () => {
@@ -22,85 +77,6 @@ describe('arch validator tests', () => {
});
});
describe('channel validator tests', () => {
it('Throws if channel is unknown', () => {
const func = () => {
validator.checkChannel('some-channel');
};
expect(func).toThrowError(`Value for input.channel 'some-channel' is unknown. Supported options: ${validator.VALID_CHANNELS}`);
});
it('Validates successfully with valid channel', () => {
const func1 = () => {
validator.checkChannel('stable');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkChannel('beta');
};
expect(func2).not.toThrow();
const func3 = () => {
validator.checkChannel('dev');
};
expect(func3).not.toThrow();
const func4 = () => {
validator.checkChannel('canary');
};
expect(func4).not.toThrow();
});
});
describe('force-avd-creation validator tests', () => {
it('Throws if force-avd-creation is not a boolean', () => {
const func = () => {
validator.checkForceAvdCreation('yes');
};
expect(func).toThrowError(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
});
it('Validates successfully if force-avd-creation is either true or false', () => {
const func1 = () => {
validator.checkForceAvdCreation('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkForceAvdCreation('false');
};
expect(func2).not.toThrow();
});
});
describe('emulator-port validator tests', () => {
it('Validates if emulator-port is even and in range', () => {
const func = () => {
validator.checkPort(5554);
};
expect(func).not.toThrow();
});
it('Throws if emulator-port is lower than MIN_PORT', () => {
const func = () => {
validator.checkPort(MIN_PORT - 2);
};
expect(func).toThrow();
});
it('Throws if emulator-port is higher than MAX_PORT', () => {
const func = () => {
validator.checkPort(MAX_PORT + 2);
};
expect(func).toThrow();
});
it('Throws if emulator-port is odd', () => {
const func = () => {
validator.checkPort(5555);
};
expect(func).toThrow();
});
});
describe('disable-animations validator tests', () => {
it('Throws if disable-animations is not a boolean', () => {
const func = () => {
@@ -122,74 +98,6 @@ describe('disable-animations validator tests', () => {
});
});
describe('disable-spellchecker validator tests', () => {
it('Throws if disable-spellchecker is not a boolean', () => {
const func = () => {
validator.checkDisableSpellchecker('yes');
};
expect(func).toThrowError(`Input for input.disable-spellchecker should be either 'true' or 'false'.`);
});
it('Validates successfully if disable-spellchecker is either true or false', () => {
const func1 = () => {
validator.checkDisableSpellchecker('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkDisableSpellchecker('false');
};
expect(func2).not.toThrow();
});
});
describe('disable-linux-hw-accel validator tests', () => {
it('Throws if disable-linux-hw-accel is not a boolean', () => {
const func = () => {
validator.checkDisableLinuxHardwareAcceleration('yes');
};
expect(func).toThrowError(`Input for input.disable-linux-hw-accel should be either 'true' or 'false' or 'auto'.`);
});
it('Validates successfully if disable-linux-hw-accel is either true or false or auto', () => {
const func1 = () => {
validator.checkDisableLinuxHardwareAcceleration('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkDisableLinuxHardwareAcceleration('false');
};
expect(func2).not.toThrow();
const func3 = () => {
validator.checkDisableLinuxHardwareAcceleration('auto');
};
expect(func3).not.toThrow();
});
});
describe('enable-hw-keyboard validator tests', () => {
it('Throws if enable-hw-keyboard is not a boolean', () => {
const func = () => {
validator.checkEnableHardwareKeyboard('yes');
};
expect(func).toThrowError(`Input for input.enable-hw-keyboard should be either 'true' or 'false'.`);
});
it('Validates successfully if enable-hardware-keyboard is either true or false', () => {
const func1 = () => {
validator.checkEnableHardwareKeyboard('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkEnableHardwareKeyboard('false');
};
expect(func2).not.toThrow();
});
});
describe('emulator-build validator tests', () => {
it('Throws if emulator-build is not a number', () => {
const func = () => {
@@ -212,54 +120,3 @@ describe('emulator-build validator tests', () => {
expect(func).not.toThrow();
});
});
describe('checkDiskSize validator tests', () => {
it('Empty size is acceptable, means default', () => {
const func = () => {
validator.checkDiskSize('');
};
expect(func).not.toThrow();
});
it('Numbers means bytes', () => {
expect(() => {
validator.checkDiskSize('8000000000');
}).not.toThrow();
});
it('Uppercase size modifier', () => {
expect(() => {
validator.checkDiskSize('8000000K');
}).not.toThrow();
expect(() => {
validator.checkDiskSize('8000M');
}).not.toThrow();
expect(() => {
validator.checkDiskSize('8G');
}).not.toThrow();
});
it('Lowercase size modifier', () => {
expect(() => {
validator.checkDiskSize('8000000k');
}).not.toThrow();
expect(() => {
validator.checkDiskSize('8000m');
}).not.toThrow();
expect(() => {
validator.checkDiskSize('8g');
}).not.toThrow();
});
it('Modifier without a number is unacceptable', () => {
expect(() => {
validator.checkDiskSize('G');
}).toThrowError(`Unexpected disk size: 'G'.`);
});
it('Double modifier is unacceptable', () => {
expect(() => {
validator.checkDiskSize('14gg');
}).toThrowError(`Unexpected disk size: '14gg'.`);
});
});
-75
View File
@@ -1,75 +0,0 @@
inputs:
api-level:
type: string
system-image-api-level:
type: string
target:
type: enum
allowed-values:
- default
- google_apis
- google_apis_playstore
- aosp_atd
- google_atd
- android-wear
- android-wear-cn
- android-tv
- google-tv
- android-automotive
- android-automotive-playstore
- android-desktop
arch:
type: enum
allowed-values:
- x86
- x86_64
- arm64-v8a
profile:
type: string
cores:
type: integer
ram-size:
type: string
heap-size:
type: string
sdcard-path-or-size:
type: string
disk-size:
type: string
avd-name:
type: string
force-avd-creation:
type: boolean
emulator-boot-timeout:
type: integer
emulator-port:
type: integer
emulator-options:
type: string
disable-animations:
type: boolean
disable-spellchecker:
type: boolean
disable-linux-hw-accel:
type: string
enable-hw-keyboard:
type: boolean
emulator-build:
type: string
working-directory:
type: string
ndk:
type: string
cmake:
type: string
channel:
type: enum
allowed-values:
- stable
- beta
- dev
- canary
script:
type: string
pre-emulator-launch-script:
type: string
+9 -55
View File
@@ -1,78 +1,32 @@
name: 'Android Emulator Runner'
description: 'Installs, configures and starts an Android Emulator directly on hardware-accelerated runners.'
description: 'Installs, configures and starts an Android Emulator directly on macOS virtual machines.'
author: 'Reactive Circus'
branding:
icon: 'smartphone'
icon: 'smartphone'
color: 'green'
inputs:
api-level:
description: 'API level of the platform and system image - e.g. 23, 33, 35-ext15, Baklava'
description: 'API level of the platform and system image - e.g. 23 for Android Marshmallow, 29 for Android 10'
required: true
system-image-api-level:
description: 'API level of the system image - e.g. 34-ext10, 35-ext15. If not set the `api-level` input will be used.'
required: false
target:
description: 'target of the system image - e.g. default, google_apis, google_apis_ps16k, google_apis_playstore, google_apis_playstore_16k, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop'
description: 'target of the system image - default or google_apis'
default: 'default'
arch:
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
description: 'CPU architecture of the system image - x86 or x86_64'
default: 'x86'
profile:
description: 'hardware profile used for creating the AVD - e.g. `Nexus 6`'
cores:
description: 'the number of cores to use for the emulator'
default: 2
ram-size:
description: 'size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`'
heap-size:
description: 'size of heap to use for this AVD in MB. - e.g. `512M`'
sdcard-path-or-size:
description: 'path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`'
disk-size:
description: 'disk size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G'
avd-name:
description: 'custom AVD name used for creating the Android Virtual Device'
default: 'test'
force-avd-creation:
description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`'
default: 'true'
emulator-boot-timeout:
description: 'Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes'
default: '600'
emulator-port:
description: 'Port to run emulator on, allows to run multiple emulators on the same physical machine'
default: '5554'
description: 'Hardware profile used for creating the AVD - e.g. `Nexus 6`.'
emulator-options:
description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`'
description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`.'
default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
disable-animations:
description: 'whether to disable animations - true or false'
default: 'true'
disable-spellchecker:
description: 'whether to disable the Android spell checker framework, a common source of flakiness in text fields - `true` or `false`'
default: 'false'
disable-linux-hw-accel:
description: 'whether to disable hardware acceleration on Linux machines - `true` or `false` or `auto`'
default: 'auto'
enable-hw-keyboard:
description: 'whether to enable hardware keyboard - `true` or `false`.'
default: 'false'
emulator-build:
description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0'
working-directory:
description: 'A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository'
ndk:
description: 'version of NDK to install - e.g. 21.0.6113669'
cmake:
description: 'version of CMake to install - e.g. 3.10.2.4988404'
channel:
description: 'Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`'
default: 'stable'
description: 'build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0.'
script:
description: 'custom script to run - e.g. `./gradlew connectedCheck`'
required: true
pre-emulator-launch-script:
description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`'
runs:
using: 'node24'
using: 'node12'
main: 'lib/main.js'
-20
View File
@@ -1,20 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getChannelId = getChannelId;
function getChannelId(channelName) {
if (channelName === 'stable') {
return 0;
}
else if (channelName === 'beta') {
return 1;
}
else if (channelName === 'dev') {
return 2;
}
else if (channelName === 'canary') {
return 3;
}
else {
throw new Error(`Unexpected channel name: '${channelName}'.`);
}
}
+83 -149
View File
@@ -1,180 +1,114 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAvd = createAvd;
exports.launchEmulator = launchEmulator;
exports.killEmulator = killEmulator;
const exec = __importStar(require("@actions/exec"));
const fs = __importStar(require("fs"));
const EMULATOR_BOOT_TIMEOUT_SECONDS = 300;
const AVD_MANAGER_PATH = `${process.env.ANDROID_HOME}/tools/bin/avdmanager`;
const ADB_PATH = `${process.env.ANDROID_HOME}/platform-tools/adb`;
/**
* Creates a new AVD instance with the specified configurations.
* Creates and launches a new AVD instance with the specified configurations.
*/
async function createAvd(arch, avdName, cores, diskSize, enableHardwareKeyboard, forceAvdCreation, heapSize, profile, ramSize, sdcardPathOrSize, systemImageApiLevel, target) {
try {
console.log(`::group::Create AVD`);
// create a new AVD if AVD directory does not already exist or forceAvdCreation is true
const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
if (!fs.existsSync(avdPath) || forceAvdCreation) {
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
console.log(`Creating AVD.`);
await exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --package 'system-images;android-${systemImageApiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`);
function launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations) {
return __awaiter(this, void 0, void 0, function* () {
// create a new AVD
if (profile.trim() !== '') {
console.log(`Creating AVD with custom profile ${profile}`);
yield exec.exec(`${AVD_MANAGER_PATH} create avd --force -n test --abi "${target}/${arch}" --package "system-images;android-${apiLevel};${target};${arch}" --device "${profile}"`);
}
if (cores || ramSize || heapSize || enableHardwareKeyboard || diskSize) {
const configEntries = [];
if (cores) {
configEntries.push(`hw.cpu.ncore=${cores}`);
}
if (ramSize) {
configEntries.push(`hw.ramSize=${ramSize}`);
}
if (heapSize) {
configEntries.push(`hw.heapSize=${heapSize}`);
}
if (enableHardwareKeyboard) {
configEntries.push('hw.keyboard=yes');
}
if (diskSize) {
configEntries.push(`disk.dataPartition.size=${diskSize}`);
}
if (configEntries.length > 0) {
const configContent = configEntries.join('\\n') + '\\n';
await exec.exec(`sh -c \\"printf '${configContent}' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini"`);
}
}
}
finally {
console.log(`::endgroup::`);
}
}
/**
* Launches an existing AVD instance with the specified configurations.
*/
async function launchEmulator(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellChecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port) {
try {
console.log(`::group::Launch Emulator`);
// turn off hardware acceleration on Linux
if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
console.log('Disabling Linux hardware acceleration.');
emulatorOptions += ' -accel off';
else {
console.log(`Creating AVD without custom profile.`);
yield exec.exec(`sh -c \\"echo no | ${AVD_MANAGER_PATH} create avd --force -n test --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}'"`);
}
// start emulator
console.log('Starting emulator.');
await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], {
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd test ${emulatorOptions} &"`, [], {
listeners: {
stderr: (data) => {
if (data.toString().includes('invalid command-line parameter')) {
throw new Error(data.toString());
}
},
},
}
}
});
// wait for emulator to complete booting
await waitForDevice(port, emulatorBootTimeout);
await adb(port, `shell input keyevent 82`);
yield waitForDevice();
yield exec.exec(`${ADB_PATH} shell input keyevent 82`);
// disable animations
if (disableAnimations) {
console.log('Disabling animations.');
await adb(port, `shell settings put global window_animation_scale 0.0`);
await adb(port, `shell settings put global transition_animation_scale 0.0`);
await adb(port, `shell settings put global animator_duration_scale 0.0`);
yield exec.exec(`${ADB_PATH} shell settings put global window_animation_scale 0.0`);
yield exec.exec(`${ADB_PATH} shell settings put global transition_animation_scale 0.0`);
yield exec.exec(`${ADB_PATH} shell settings put global animator_duration_scale 0.0`);
}
if (disableSpellChecker) {
await adb(port, `shell settings put secure spell_checker_enabled 0`);
}
if (enableHardwareKeyboard) {
await adb(port, `shell settings put secure show_ime_with_hard_keyboard 0`);
}
}
finally {
console.log(`::endgroup::`);
}
});
}
exports.launchEmulator = launchEmulator;
/**
* Kills the running emulator on the default port.
* Kills the running emulator on the defaut port.
*/
async function killEmulator(port) {
try {
console.log(`::group::Terminate Emulator`);
await adb(port, `emu kill`);
}
catch (error) {
console.log(error instanceof Error ? error.message : error);
}
finally {
console.log(`::endgroup::`);
}
}
async function adb(port, command) {
return await exec.exec(`adb -s emulator-${port} ${command}`);
function killEmulator() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield exec.exec(`${ADB_PATH} -s emulator-5554 emu kill`);
}
catch (error) {
console.log(error.message);
}
});
}
exports.killEmulator = killEmulator;
/**
* Wait for emulator to boot.
*/
async function waitForDevice(port, emulatorBootTimeout) {
let booted = false;
let attempts = 0;
const retryInterval = 2; // retry every 2 seconds
const maxAttempts = emulatorBootTimeout / 2;
while (!booted) {
try {
let result = '';
await exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], {
listeners: {
stdout: (data) => {
result += data.toString();
},
},
});
if (result.trim() === '1') {
console.log('Emulator booted.');
booted = true;
break;
function waitForDevice() {
return __awaiter(this, void 0, void 0, function* () {
let booted = false;
let attempts = 0;
const retryInterval = 2; // retry every 2 seconds
const maxAttemps = EMULATOR_BOOT_TIMEOUT_SECONDS / 2;
while (!booted) {
try {
let result = '';
yield exec.exec(`${ADB_PATH} shell getprop sys.boot_completed`, [], {
listeners: {
stdout: (data) => {
result += data.toString();
}
}
});
if (result.trim() === '1') {
console.log('Emulator booted.');
booted = true;
break;
}
}
catch (error) {
console.log(error.message);
}
if (attempts < maxAttemps) {
yield delay(retryInterval * 1000);
}
else {
throw new Error(`Timeout waiting for emulator to boot.`);
}
attempts++;
}
catch (error) {
console.warn(error instanceof Error ? error.message : error);
}
if (attempts < maxAttempts) {
await delay(retryInterval * 1000);
}
else {
throw new Error(`Timeout waiting for emulator to boot.`);
}
attempts++;
}
});
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
return new Promise(resolve => setTimeout(resolve, ms));
}
+21 -78
View File
@@ -1,95 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_PORT = exports.MIN_PORT = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.MIN_API_LEVEL = void 0;
exports.playstoreTargetSubstitution = playstoreTargetSubstitution;
exports.checkArch = checkArch;
exports.checkChannel = checkChannel;
exports.checkForceAvdCreation = checkForceAvdCreation;
exports.checkPort = checkPort;
exports.checkDisableAnimations = checkDisableAnimations;
exports.checkDisableSpellchecker = checkDisableSpellchecker;
exports.checkDisableLinuxHardwareAcceleration = checkDisableLinuxHardwareAcceleration;
exports.checkEnableHardwareKeyboard = checkEnableHardwareKeyboard;
exports.checkEmulatorBuild = checkEmulatorBuild;
exports.checkDiskSize = checkDiskSize;
exports.MIN_API_LEVEL = 15;
exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary'];
exports.MIN_PORT = 5554;
exports.MAX_PORT = 5584;
function playstoreTargetSubstitution(target) {
// "playstore" is an allowed shorthand for "google_apis_playstore" images
// this is idempotent - return same even if run multiple times on same target
if (target === 'playstore')
return 'google_apis_playstore';
if (target === 'playstore_ps16k')
return 'google_apis_playstore_ps16k';
return target;
exports.MIN_API_LEVEL = 21;
exports.VALID_TARGETS = ['default', 'google_apis'];
exports.VALID_ARCHS = ['x86', 'x86_64'];
function checkApiLevel(apiLevel) {
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) {
throw new Error(`Unexpected API level: '${apiLevel}'.`);
}
if (Number(apiLevel) < exports.MIN_API_LEVEL) {
throw new Error(`Minimum API level supported is ${exports.MIN_API_LEVEL}.`);
}
}
exports.checkApiLevel = checkApiLevel;
function checkTarget(target) {
if (!exports.VALID_TARGETS.includes(target)) {
throw new Error(`Value for input.target '${target}' is unknown. Supported options: ${exports.VALID_TARGETS}.`);
}
}
exports.checkTarget = checkTarget;
function checkArch(arch) {
if (!exports.VALID_ARCHS.includes(arch)) {
throw new Error(`Value for input.arch '${arch}' is unknown. Supported options: ${exports.VALID_ARCHS}.`);
}
}
function checkChannel(channel) {
if (!exports.VALID_CHANNELS.includes(channel)) {
throw new Error(`Value for input.channel '${channel}' is unknown. Supported options: ${exports.VALID_CHANNELS}.`);
}
}
function checkForceAvdCreation(forceAvdCreation) {
if (!isValidBoolean(forceAvdCreation)) {
throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
}
}
function checkPort(port) {
if (port < exports.MIN_PORT || port > exports.MAX_PORT) {
throw new Error(`Emulator port is outside of the supported port range [${exports.MIN_PORT}, ${exports.MAX_PORT}], was ${port}`);
}
if (port % 2 == 1) {
throw new Error(`Emulator port has to be even, was ${port}`);
}
}
exports.checkArch = checkArch;
function checkDisableAnimations(disableAnimations) {
if (!isValidBoolean(disableAnimations)) {
if (disableAnimations !== 'true' && disableAnimations !== 'false') {
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
}
}
function checkDisableSpellchecker(disableSpellchecker) {
if (!isValidBoolean(disableSpellchecker)) {
throw new Error(`Input for input.disable-spellchecker should be either 'true' or 'false'.`);
}
}
function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration) {
if (!(isValidBoolean(disableLinuxHardwareAcceleration) || disableLinuxHardwareAcceleration === 'auto')) {
throw new Error(`Input for input.disable-linux-hw-accel should be either 'true' or 'false' or 'auto'.`);
}
}
function checkEnableHardwareKeyboard(enableHardwareKeyboard) {
if (!isValidBoolean(enableHardwareKeyboard)) {
throw new Error(`Input for input.enable-hw-keyboard should be either 'true' or 'false'.`);
}
}
exports.checkDisableAnimations = checkDisableAnimations;
function checkEmulatorBuild(emulatorBuild) {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
}
}
function isValidBoolean(value) {
return value === 'true' || value === 'false';
}
function checkDiskSize(diskSize) {
// Disk size can be empty - the default value
if (diskSize) {
// Can also be number of bytes
if (isNaN(Number(diskSize)) || !Number.isInteger(Number(diskSize))) {
// Disk size can have a size multiplier at the end K, M or G
const diskSizeUpperCase = diskSize.toUpperCase();
if (diskSizeUpperCase.endsWith('K') || diskSizeUpperCase.endsWith('M') || diskSizeUpperCase.endsWith('G')) {
const diskSizeNoModifier = diskSize.slice(0, -1);
if (0 == diskSizeNoModifier.length || isNaN(Number(diskSizeNoModifier)) || !Number.isInteger(Number(diskSizeNoModifier))) {
throw new Error(`Unexpected disk size: '${diskSize}'.`);
}
}
}
}
}
exports.checkEmulatorBuild = checkEmulatorBuild;
+60
View File
@@ -0,0 +1,60 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
/**
* Returns the current $JAVA_HOME path.
*/
function getCurrentJavaHome() {
return __awaiter(this, void 0, void 0, function* () {
let defaultJavaHome = '';
yield exec.exec(`sh -c \\"echo $JAVA_HOME"`, [], {
listeners: {
stdout: (data) => {
defaultJavaHome += data.toString();
}
}
});
return defaultJavaHome.trim();
});
}
exports.getCurrentJavaHome = getCurrentJavaHome;
/**
* Returns the Java 8 $JAVA_HOME path.
*/
function getJavaHomeV8() {
return __awaiter(this, void 0, void 0, function* () {
let javaHomeV8 = '';
yield exec.exec(`/usr/libexec/java_home -v 1.8`, [], {
listeners: {
stdout: (data) => {
javaHomeV8 += data.toString();
}
}
});
return javaHomeV8.trim();
});
}
exports.getJavaHomeV8 = getJavaHomeV8;
/**
* Sets $JAVA_HOME to the specified path.
*/
function setJavaHome(path) {
core.exportVariable('JAVA_HOME', path);
}
exports.setJavaHome = setJavaHome;
+83 -212
View File
@@ -1,37 +1,19 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const sdk_installer_1 = require("./sdk-installer");
@@ -39,194 +21,83 @@ const input_validator_1 = require("./input-validator");
const emulator_manager_1 = require("./emulator-manager");
const exec = __importStar(require("@actions/exec"));
const script_parser_1 = require("./script-parser");
const channel_id_mapper_1 = require("./channel-id-mapper");
const fs_1 = require("fs");
async function run() {
let port = input_validator_1.MIN_PORT;
try {
console.log(`::group::Configure emulator`);
let linuxSupportKVM = false;
// only support running on macOS or Linux
if (process.platform !== 'darwin') {
if (process.platform === 'linux') {
try {
(0, fs_1.accessSync)('/dev/kvm', fs_1.constants.R_OK | fs_1.constants.W_OK);
linuxSupportKVM = true;
}
catch {
console.warn(`You're running a Linux VM where hardware acceleration is not available. Please consider using a macOS VM instead to take advantage of native hardware acceleration support provided by HAXM.`);
}
const java_version_manager_1 = require("./java-version-manager");
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
// only support running on macOS
if (process.platform !== 'darwin') {
throw new Error('This action is expected to be run within a macOS virtual machine to enable hardware acceleration.');
}
else {
throw new Error('Unsupported virtual machine: please use either macos or ubuntu VM.');
// API level of the platform and system image
const apiLevelInput = core.getInput('api-level', { required: true });
input_validator_1.checkApiLevel(apiLevelInput);
const apiLevel = Number(apiLevelInput);
console.log(`API level: ${apiLevel}`);
// target of the system image
const target = core.getInput('target');
input_validator_1.checkTarget(target);
console.log(`target: ${target}`);
// CPU architecture of the system image
const arch = core.getInput('arch');
input_validator_1.checkArch(arch);
console.log(`CPU architecture: ${arch}`);
// Hardware profile used for creating the AVD
const profile = core.getInput('profile');
console.log(`Hardware profile: ${profile}`);
// emulator options
const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`);
// disable animations
const disableAnimationsInput = core.getInput('disable-animations');
input_validator_1.checkDisableAnimations(disableAnimationsInput);
const disableAnimations = disableAnimationsInput === 'true';
console.log(`disable animations: ${disableAnimations}`);
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
input_validator_1.checkEmulatorBuild(emulatorBuildInput);
console.log(`using emulator build: ${emulatorBuildInput}`);
}
}
// API level of the platform and system image
const apiLevel = core.getInput('api-level', { required: true });
console.log(`API level: ${apiLevel}`);
let systemImageApiLevel = core.getInput('system-image-api-level');
if (!systemImageApiLevel) {
systemImageApiLevel = apiLevel;
}
console.log(`System image API level: ${systemImageApiLevel}`);
// target of the system image
const target = (0, input_validator_1.playstoreTargetSubstitution)(core.getInput('target'));
console.log(`target: ${target}`);
// CPU architecture of the system image
const arch = core.getInput('arch');
(0, input_validator_1.checkArch)(arch);
console.log(`CPU architecture: ${arch}`);
// Hardware profile used for creating the AVD
const profile = core.getInput('profile');
console.log(`Hardware profile: ${profile}`);
// Number of cores to use for emulator
const cores = core.getInput('cores');
console.log(`Cores: ${cores}`);
// RAM to use for AVD
const ramSize = core.getInput('ram-size');
console.log(`RAM size: ${ramSize}`);
// Heap size to use for AVD
const heapSize = core.getInput('heap-size');
console.log(`Heap size: ${heapSize}`);
// SD card path or size used for creating the AVD
const sdcardPathOrSize = core.getInput('sdcard-path-or-size');
console.log(`SD card path or size: ${sdcardPathOrSize}`);
const diskSize = core.getInput('disk-size');
(0, input_validator_1.checkDiskSize)(diskSize);
console.log(`Disk size: ${diskSize}`);
// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);
// force AVD creation
const forceAvdCreationInput = core.getInput('force-avd-creation');
(0, input_validator_1.checkForceAvdCreation)(forceAvdCreationInput);
const forceAvdCreation = forceAvdCreationInput === 'true';
console.log(`force avd creation: ${forceAvdCreation}`);
// Emulator boot timeout seconds
const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10);
console.log(`Emulator boot timeout: ${emulatorBootTimeout}`);
// Emulator port to use
port = parseInt(core.getInput('emulator-port'), 10);
(0, input_validator_1.checkPort)(port);
console.log(`emulator port: ${port}`);
// emulator options
const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`);
// disable animations
const disableAnimationsInput = core.getInput('disable-animations');
(0, input_validator_1.checkDisableAnimations)(disableAnimationsInput);
const disableAnimations = disableAnimationsInput === 'true';
console.log(`disable animations: ${disableAnimations}`);
// disable spellchecker
const disableSpellcheckerInput = core.getInput('disable-spellchecker');
(0, input_validator_1.checkDisableSpellchecker)(disableSpellcheckerInput);
const disableSpellchecker = disableSpellcheckerInput === 'true';
console.log(`disable spellchecker: ${disableSpellchecker}`);
// disable linux hardware acceleration
let disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel');
(0, input_validator_1.checkDisableLinuxHardwareAcceleration)(disableLinuxHardwareAccelerationInput);
if (disableLinuxHardwareAccelerationInput === 'auto' && process.platform === 'linux') {
disableLinuxHardwareAccelerationInput = linuxSupportKVM ? 'false' : 'true';
}
const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true';
console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`);
// enable hardware keyboard
const enableHardwareKeyboardInput = core.getInput('enable-hw-keyboard');
(0, input_validator_1.checkEnableHardwareKeyboard)(enableHardwareKeyboardInput);
const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true';
console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`);
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
(0, input_validator_1.checkEmulatorBuild)(emulatorBuildInput);
console.log(`using emulator build: ${emulatorBuildInput}`);
}
const emulatorBuild = !emulatorBuildInput ? undefined : emulatorBuildInput;
// custom working directory
const workingDirectoryInput = core.getInput('working-directory');
if (workingDirectoryInput) {
console.log(`custom working directory: ${workingDirectoryInput}`);
}
const workingDirectory = !workingDirectoryInput ? undefined : workingDirectoryInput;
// version of NDK to install
const ndkInput = core.getInput('ndk');
if (ndkInput) {
console.log(`version of NDK to install: ${ndkInput}`);
}
const ndkVersion = !ndkInput ? undefined : ndkInput;
// version of CMake to install
const cmakeInput = core.getInput('cmake');
if (cmakeInput) {
console.log(`version of CMake to install: ${cmakeInput}`);
}
const cmakeVersion = !cmakeInput ? undefined : cmakeInput;
// channelId (up to and including) to download the SDK packages from
const channelName = core.getInput('channel');
(0, input_validator_1.checkChannel)(channelName);
const channelId = (0, channel_id_mapper_1.getChannelId)(channelName);
console.log(`Channel: ${channelId} (${channelName})`);
// custom script to run
const scriptInput = core.getInput('script', { required: true });
const scripts = (0, script_parser_1.parseScript)(scriptInput);
console.log(`Script:`);
scripts.forEach(async (script) => {
console.log(`${script}`);
});
// custom pre emulator launch script
const preEmulatorLaunchScriptInput = core.getInput('pre-emulator-launch-script');
const preEmulatorLaunchScripts = !preEmulatorLaunchScriptInput ? undefined : (0, script_parser_1.parseScript)(preEmulatorLaunchScriptInput);
console.log(`Pre emulator launch script:`);
preEmulatorLaunchScripts?.forEach(async (script) => {
console.log(`${script}`);
});
console.log(`::endgroup::`);
// install SDK
await (0, sdk_installer_1.installAndroidSdk)(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
// create AVD
await (0, emulator_manager_1.createAvd)(arch, avdName, cores, diskSize, enableHardwareKeyboard, forceAvdCreation, heapSize, profile, ramSize, sdcardPathOrSize, systemImageApiLevel, target);
// execute pre emulator launch script if set
if (preEmulatorLaunchScripts !== undefined) {
console.log(`::group::Run pre emulator launch script`);
const emulatorBuild = !emulatorBuildInput ? undefined : emulatorBuildInput;
// custom script to run
const scriptInput = core.getInput('script', { required: true });
const scripts = script_parser_1.parseScript(scriptInput);
console.log(`Script:`);
scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () {
console.log(`${script}`);
}));
// use Java 8 for sdkmanager and avdmanager
const defaultJavaHome = yield java_version_manager_1.getCurrentJavaHome();
java_version_manager_1.setJavaHome(yield java_version_manager_1.getJavaHomeV8());
// install SDK
yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild);
try {
for (const preEmulatorLaunchScript of preEmulatorLaunchScripts) {
// use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${preEmulatorLaunchScript}"`)
await exec.exec('sh', ['-c', preEmulatorLaunchScript], {
cwd: workingDirectory,
});
// launch an emulator
yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations);
}
catch (error) {
core.setFailed(error.message);
}
// use default JAVA_HOME for running custom script
java_version_manager_1.setJavaHome(defaultJavaHome);
// execute the custom script
try {
for (const script of scripts) {
yield exec.exec(`${script}`);
}
}
catch (error) {
core.setFailed(error instanceof Error ? error.message : error);
}
console.log(`::endgroup::`);
}
// launch an emulator
await (0, emulator_manager_1.launchEmulator)(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellchecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port);
// execute the custom script
try {
// move to custom working directory if set
if (workingDirectory) {
process.chdir(workingDirectory);
}
for (const script of scripts) {
// use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${script}"`)
await exec.exec('sh', ['-c', script], {
env: { ...process.env, EMULATOR_PORT: `${port}`, ANDROID_SERIAL: `emulator-${port}` },
});
core.setFailed(error.message);
}
// finally kill the emulator
yield emulator_manager_1.killEmulator();
}
catch (error) {
core.setFailed(error instanceof Error ? error.message : error);
// kill the emulator so the action can exit
yield emulator_manager_1.killEmulator();
core.setFailed(error.message);
}
// finally kill the emulator
await (0, emulator_manager_1.killEmulator)(port);
}
catch (error) {
// kill the emulator so the action can exit
await (0, emulator_manager_1.killEmulator)(port);
core.setFailed(error instanceof Error ? error.message : error);
}
});
}
run();
+1 -1
View File
@@ -1,6 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseScript = parseScript;
/**
* Convert a (potentially multi-line) script to an array of single-line script(s).
*/
@@ -17,3 +16,4 @@ function parseScript(rawScript) {
}
return scripts;
}
exports.parseScript = parseScript;
+31 -99
View File
@@ -1,112 +1,44 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.installAndroidSdk = installAndroidSdk;
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
const io = __importStar(require("@actions/io"));
const tc = __importStar(require("@actions/tool-cache"));
const fs = __importStar(require("fs"));
const BUILD_TOOLS_VERSION = '37.0.0';
// SDK command-line tools 23.0
const CMDLINE_TOOLS_VERSION = '16111833';
const CMDLINE_TOOLS_URL_MAC = `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${CMDLINE_TOOLS_VERSION}_latest.zip`;
const CMDLINE_TOOLS_URL_LINUX = `https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip`;
const BUILD_TOOLS_VERSION = '29.0.2';
/**
* Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator,
* and the system image for the chosen API level, CPU arch, and target.
*/
async function installAndroidSdk(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) {
try {
console.log(`::group::Install Android SDK`);
const isOnMac = process.platform === 'darwin';
const isArm = process.arch === 'arm64';
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
if (!fs.existsSync(cmdlineToolsPath)) {
console.log('Installing new cmdline-tools.');
const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX;
const downloadPath = await tc.downloadTool(sdkUrl);
await tc.extractZip(downloadPath, cmdlineToolsPath);
await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`);
}
// add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`);
// set standard AVD path
await io.mkdirP(`${process.env.HOME}/.android/avd`);
core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
// accept all Android SDK licenses
await exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`);
function installAndroidSdk(apiLevel, target, arch, emulatorBuild) {
return __awaiter(this, void 0, void 0, function* () {
const sdkmangerPath = `${process.env.ANDROID_HOME}/tools/bin/sdkmanager`;
console.log('Installing latest build tools, platform tools, and platform.');
await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}'> /dev/null"`);
console.log('Installing latest emulator.');
await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`);
yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`);
if (emulatorBuild) {
console.log(`Installing emulator build ${emulatorBuild}.`);
// TODO find out the correct download URLs for all build ids
var downloadUrlSuffix;
const majorBuildVersion = Number(emulatorBuild);
if (majorBuildVersion >= 8000000) {
if (isArm) {
downloadUrlSuffix = `_aarch64-${emulatorBuild}`;
}
else {
downloadUrlSuffix = `_x64-${emulatorBuild}`;
}
}
else if (majorBuildVersion >= 7000000) {
downloadUrlSuffix = `_x64-${emulatorBuild}`;
}
else {
downloadUrlSuffix = `-${emulatorBuild}`;
}
await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`);
await exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_HOME}`);
await io.rmRF('emulator.zip');
yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-darwin-${emulatorBuild}.zip`);
yield exec.exec(`rm -rf ${process.env.ANDROID_HOME}/emulator`);
yield exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`);
yield exec.exec(`rm -f emulator.zip`);
}
else {
console.log('Installing latest emulator.');
yield exec.exec(`sh -c \\"${sdkmangerPath} --install emulator > /dev/null"`);
}
console.log('Installing system images.');
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${systemImageApiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
if (ndkVersion) {
console.log(`Installing NDK ${ndkVersion}.`);
await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`);
}
if (cmakeVersion) {
console.log(`Installing CMake ${cmakeVersion}.`);
await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`);
}
}
finally {
console.log(`::endgroup::`);
}
yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`);
});
}
exports.installAndroidSdk = installAndroidSdk;
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../uuid/bin/uuid
+7
View File
@@ -0,0 +1,7 @@
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.
+81
View File
@@ -0,0 +1,81 @@
# `@actions/core`
> Core functions for setting results, logging, registering secrets and exporting variables across actions
## Usage
#### Inputs/Outputs
You can use this library to get inputs or set outputs:
```
const core = require('@actions/core');
const myInput = core.getInput('inputName', { required: true });
// Do stuff
core.setOutput('outputKey', 'outputVal');
```
#### Exporting variables/secrets
You can also export variables and secrets for future steps. Variables get set in the environment automatically, while secrets must be scoped into the environment from a workflow using `{{ secret.FOO }}`. Secrets will also be masked from the logs:
```
const core = require('@actions/core');
// Do stuff
core.exportVariable('envVar', 'Val');
core.exportSecret('secretVar', variableWithSecretValue);
```
#### PATH Manipulation
You can explicitly add items to the path for all remaining steps in a workflow:
```
const core = require('@actions/core');
core.addPath('pathToTool');
```
#### Exit codes
You should use this library to set the failing exit code for your action:
```
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}`);
}
```
#### Logging
Finally, this library provides some utilities for logging:
```
const core = require('@actions/core');
const myInput = core.getInput('input');
try {
core.debug('Inside try block');
if (!myInput) {
core.warning('myInput wasnt set');
}
// Do stuff
}
catch (err) {
core.error('Error ${err}, action may still succeed though');
}
```
+16
View File
@@ -0,0 +1,16 @@
interface CommandProperties {
[key: string]: string;
}
/**
* Commands
*
* Command Format:
* ##[name key=value;key=value]message
*
* Examples:
* ##[warning]This is the user warning message
* ##[set-secret name=mypassword]definatelyNotAPassword!
*/
export declare function issueCommand(command: string, properties: CommandProperties, message: string): void;
export declare function issue(name: string, message: string): void;
export {};
+66
View File
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
/**
* Commands
*
* Command Format:
* ##[name key=value;key=value]message
*
* Examples:
* ##[warning]This is the user warning message
* ##[set-secret name=mypassword]definatelyNotAPassword!
*/
function issueCommand(command, properties, message) {
const cmd = new Command(command, properties, message);
process.stdout.write(cmd.toString() + os.EOL);
}
exports.issueCommand = issueCommand;
function issue(name, message) {
issueCommand(name, {}, message);
}
exports.issue = issue;
const CMD_PREFIX = '##[';
class Command {
constructor(command, properties, message) {
if (!command) {
command = 'missing.command';
}
this.command = command;
this.properties = properties;
this.message = message;
}
toString() {
let cmdStr = CMD_PREFIX + this.command;
if (this.properties && Object.keys(this.properties).length > 0) {
cmdStr += ' ';
for (const key in this.properties) {
if (this.properties.hasOwnProperty(key)) {
const val = this.properties[key];
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 += ']';
// safely append the message - avoid blowing up when attempting to
// call .replace() if message is not a string for some reason
const message = `${this.message || ''}`;
cmdStr += escapeData(message);
return cmdStr;
}
}
function escapeData(s) {
return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A');
}
function escape(s) {
return s
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A')
.replace(/]/g, '%5D')
.replace(/;/g, '%3B');
}
//# sourceMappingURL=command.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":";;AAAA,yBAAwB;AAQxB;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA6B,EAC7B,OAAe;IAEf,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;AAC/C,CAAC;AAPD,oCAOC;AAED,SAAgB,KAAK,CAAC,IAAY,EAAE,OAAe;IACjD,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;AACjC,CAAC;AAFD,sBAEC;AAED,MAAM,UAAU,GAAG,KAAK,CAAA;AAExB,MAAM,OAAO;IAKX,YAAY,OAAe,EAAE,UAA6B,EAAE,OAAe;QACzE,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,iBAAiB,CAAA;SAC5B;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,QAAQ;QACN,IAAI,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,OAAO,CAAA;QAEtC,IAAI,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9D,MAAM,IAAI,GAAG,CAAA;YACb,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE;gBACjC,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;oBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;oBAChC,IAAI,GAAG,EAAE;wBACP,8DAA8D;wBAC9D,6DAA6D;wBAC7D,MAAM,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,EAAE,CAAC,GAAG,CAAA;qBAC9C;iBACF;aACF;SACF;QAED,MAAM,IAAI,GAAG,CAAA;QAEb,kEAAkE;QAClE,6DAA6D;QAC7D,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAA;QACvC,MAAM,IAAI,UAAU,CAAC,OAAO,CAAC,CAAA;QAE7B,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC;SACL,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AACzB,CAAC"}
+73
View File
@@ -0,0 +1,73 @@
/**
* Interface for getInput options
*/
export interface InputOptions {
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
required?: boolean;
}
/**
* The code to exit an action
*/
export declare enum ExitCode {
/**
* A code indicating that the action was successful
*/
Success = 0,
/**
* A code indicating that the action was a failure
*/
Failure = 1
}
/**
* 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
*/
export declare 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
*/
export declare function exportSecret(name: string, val: string): void;
/**
* Prepends inputPath to the PATH (for this action and future actions)
* @param inputPath
*/
export declare function addPath(inputPath: string): void;
/**
* Gets the value of an input. The value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
export declare function getInput(name: string, options?: InputOptions): string;
/**
* Sets the value of an output.
*
* @param name name of the output to set
* @param value value to store
*/
export declare function setOutput(name: string, value: string): void;
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
export declare function setFailed(message: string): void;
/**
* Writes debug message to user log
* @param message debug message
*/
export declare function debug(message: string): void;
/**
* Adds an error issue
* @param message error issue message
*/
export declare function error(message: string): void;
/**
* Adds an warning issue
* @param message warning issue message
*/
export declare function warning(message: string): void;
+116
View File
@@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("./command");
const path = require("path");
/**
* The code to exit an action
*/
var ExitCode;
(function (ExitCode) {
/**
* A code indicating that the action was successful
*/
ExitCode[ExitCode["Success"] = 0] = "Success";
/**
* A code indicating that the action was a failure
*/
ExitCode[ExitCode["Failure"] = 1] = "Failure";
})(ExitCode = exports.ExitCode || (exports.ExitCode = {}));
//-----------------------------------------------------------------------
// Variables
//-----------------------------------------------------------------------
/**
* 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
*/
function exportVariable(name, val) {
process.env[name] = val;
command_1.issueCommand('set-env', { name }, val);
}
exports.exportVariable = exportVariable;
/**
* 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
*/
function exportSecret(name, val) {
exportVariable(name, val);
command_1.issueCommand('set-secret', {}, val);
}
exports.exportSecret = exportSecret;
/**
* Prepends inputPath to the PATH (for this action and future actions)
* @param inputPath
*/
function addPath(inputPath) {
command_1.issueCommand('add-path', {}, inputPath);
process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`;
}
exports.addPath = addPath;
/**
* Gets the value of an input. The value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
function getInput(name, options) {
const val = process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || '';
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
return val.trim();
}
exports.getInput = getInput;
/**
* Sets the value of an output.
*
* @param name name of the output to set
* @param value value to store
*/
function setOutput(name, value) {
command_1.issueCommand('set-output', { name }, value);
}
exports.setOutput = setOutput;
//-----------------------------------------------------------------------
// Results
//-----------------------------------------------------------------------
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
function setFailed(message) {
process.exitCode = ExitCode.Failure;
error(message);
}
exports.setFailed = setFailed;
//-----------------------------------------------------------------------
// Logging Commands
//-----------------------------------------------------------------------
/**
* Writes debug message to user log
* @param message debug message
*/
function debug(message) {
command_1.issueCommand('debug', {}, message);
}
exports.debug = debug;
/**
* Adds an error issue
* @param message error issue message
*/
function error(message) {
command_1.issue('error', message);
}
exports.error = error;
/**
* Adds an warning issue
* @param message warning issue message
*/
function warning(message) {
command_1.issue('warning', message);
}
exports.warning = warning;
//# sourceMappingURL=core.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":";;AAAA,uCAA6C;AAE7C,6BAA4B;AAU5B;;GAEG;AACH,IAAY,QAUX;AAVD,WAAY,QAAQ;IAClB;;OAEG;IACH,6CAAW,CAAA;IAEX;;OAEG;IACH,6CAAW,CAAA;AACb,CAAC,EAVW,QAAQ,GAAR,gBAAQ,KAAR,gBAAQ,QAUnB;AAED,yEAAyE;AACzE,YAAY;AACZ,yEAAyE;AAEzE;;;;GAIG;AACH,SAAgB,cAAc,CAAC,IAAY,EAAE,GAAW;IACtD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAA;IACvB,sBAAY,CAAC,SAAS,EAAE,EAAC,IAAI,EAAC,EAAE,GAAG,CAAC,CAAA;AACtC,CAAC;AAHD,wCAGC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAAC,IAAY,EAAE,GAAW;IACpD,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACzB,sBAAY,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;AACrC,CAAC;AAHD,oCAGC;AAED;;;GAGG;AACH,SAAgB,OAAO,CAAC,SAAiB;IACvC,sBAAY,CAAC,UAAU,EAAE,EAAE,EAAE,SAAS,CAAC,CAAA;IACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAA;AAC7E,CAAC;AAHD,0BAGC;AAED;;;;;;GAMG;AACH,SAAgB,QAAQ,CAAC,IAAY,EAAE,OAAsB;IAC3D,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;IACpE,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE;QACvC,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAA;KAC5D;IAED,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;AACnB,CAAC;AARD,4BAQC;AAED;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,sBAAY,CAAC,YAAY,EAAE,EAAC,IAAI,EAAC,EAAE,KAAK,CAAC,CAAA;AAC3C,CAAC;AAFD,8BAEC;AAED,yEAAyE;AACzE,UAAU;AACV,yEAAyE;AAEzE;;;;GAIG;AACH,SAAgB,SAAS,CAAC,OAAe;IACvC,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAA;IACnC,KAAK,CAAC,OAAO,CAAC,CAAA;AAChB,CAAC;AAHD,8BAGC;AAED,yEAAyE;AACzE,mBAAmB;AACnB,yEAAyE;AAEzE;;;GAGG;AACH,SAAgB,KAAK,CAAC,OAAe;IACnC,sBAAY,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AAFD,sBAEC;AAED;;;GAGG;AACH,SAAgB,KAAK,CAAC,OAAe;IACnC,eAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AACzB,CAAC;AAFD,sBAEC;AAED;;;GAGG;AACH,SAAgB,OAAO,CAAC,OAAe;IACrC,eAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;AAC3B,CAAC;AAFD,0BAEC"}
+66
View File
@@ -0,0 +1,66 @@
{
"_args": [
[
"@actions/core@1.0.0",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "@actions/core@1.0.0",
"_id": "@actions/core@1.0.0",
"_inBundle": false,
"_integrity": "sha512-aMIlkx96XH4E/2YZtEOeyrYQfhlas9jIRkfGPqMwXD095Rdkzo4lB6ZmbxPQSzD+e1M+Xsm98ZhuSMYGv/AlqA==",
"_location": "/@actions/core",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/core@1.0.0",
"name": "@actions/core",
"escapedName": "@actions%2fcore",
"scope": "@actions",
"rawSpec": "1.0.0",
"saveSpec": null,
"fetchSpec": "1.0.0"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/@actions/core/-/core-1.0.0.tgz",
"_spec": "1.0.0",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"description": "Actions core lib",
"devDependencies": {
"@types/node": "^12.0.2"
},
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"gitHead": "a40bce7c8d382aa3dbadaa327acbc696e9390e55",
"homepage": "https://github.com/actions/toolkit/tree/master/packages/core",
"keywords": [
"core",
"actions"
],
"license": "MIT",
"main": "lib/core.js",
"name": "@actions/core",
"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"
},
"version": "1.0.0"
}
+7
View File
@@ -0,0 +1,7 @@
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.
+60
View File
@@ -0,0 +1,60 @@
# `@actions/exec`
## Usage
#### 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 use it in conjunction with the `which` function from `@actions/io` to execute tools that are not in the PATH:
```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']);
```
+12
View File
@@ -0,0 +1,12 @@
import * as im from './interfaces';
/**
* Exec a command.
* Output will be streamed to the live console.
* Returns promise with return code
*
* @param commandLine command to execute (can include additional args). Must be correctly escaped.
* @param args optional arguments for tool. Escaping is handled by the lib.
* @param options optional exec options. See ExecOptions
* @returns Promise<number> exit code
*/
export declare function exec(commandLine: string, args?: string[], options?: im.ExecOptions): Promise<number>;
+37
View File
@@ -0,0 +1,37 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const tr = require("./toolrunner");
/**
* Exec a command.
* Output will be streamed to the live console.
* Returns promise with return code
*
* @param commandLine command to execute (can include additional args). Must be correctly escaped.
* @param args optional arguments for tool. Escaping is handled by the lib.
* @param options optional exec options. See ExecOptions
* @returns Promise<number> exit code
*/
function exec(commandLine, args, options) {
return __awaiter(this, void 0, void 0, function* () {
const commandArgs = tr.argStringToArray(commandLine);
if (commandArgs.length === 0) {
throw new Error(`Parameter 'commandLine' cannot be null or empty.`);
}
// Path to tool to execute should be first arg
const toolPath = commandArgs[0];
args = commandArgs.slice(1).concat(args || []);
const runner = new tr.ToolRunner(toolPath, args, options);
return runner.exec();
});
}
exports.exec = exec;
//# sourceMappingURL=exec.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,mCAAkC;AAElC;;;;;;;;;GASG;AACH,SAAsB,IAAI,CACxB,WAAmB,EACnB,IAAe,EACf,OAAwB;;QAExB,MAAM,WAAW,GAAG,EAAE,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACpD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;YAC5B,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;SACpE;QACD,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;QAC/B,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;QAC9C,MAAM,MAAM,GAAkB,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QACxE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAA;IACtB,CAAC;CAAA;AAdD,oBAcC"}
+35
View File
@@ -0,0 +1,35 @@
/// <reference types="node" />
import * as stream from 'stream';
/**
* Interface for exec options
*/
export interface ExecOptions {
/** optional working directory. defaults to current */
cwd?: string;
/** optional envvar dictionary. defaults to current process's env */
env?: {
[key: string]: string;
};
/** optional. defaults to false */
silent?: boolean;
/** optional out stream to use. Defaults to process.stdout */
outStream?: stream.Writable;
/** optional err stream to use. Defaults to process.stderr */
errStream?: stream.Writable;
/** optional. whether to skip quoting/escaping arguments if needed. defaults to false. */
windowsVerbatimArguments?: boolean;
/** optional. whether to fail if output to stderr. defaults to false */
failOnStdErr?: boolean;
/** optional. defaults to failing on non zero. ignore will not fail leaving it up to the caller */
ignoreReturnCode?: boolean;
/** optional. How long in ms to wait for STDIO streams to close after the exit event of the process before terminating. defaults to 10000 */
delay?: number;
/** optional. Listeners for output. Callback functions that will be called on these events */
listeners?: {
stdout?: (data: Buffer) => void;
stderr?: (data: Buffer) => void;
stdline?: (data: string) => void;
errline?: (data: string) => void;
debug?: (data: string) => void;
};
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=interfaces.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":""}
+37
View File
@@ -0,0 +1,37 @@
/// <reference types="node" />
import * as events from 'events';
import * as im from './interfaces';
export declare class ToolRunner extends events.EventEmitter {
constructor(toolPath: string, args?: string[], options?: im.ExecOptions);
private toolPath;
private args;
private options;
private _debug;
private _getCommandString;
private _processLineBuffer;
private _getSpawnFileName;
private _getSpawnArgs;
private _endsWith;
private _isCmdFile;
private _windowsQuoteCmdArg;
private _uvQuoteCmdArg;
private _cloneExecOptions;
private _getSpawnOptions;
/**
* Exec a tool.
* Output will be streamed to the live console.
* Returns promise with return code
*
* @param tool path to tool to exec
* @param options optional exec options. See ExecOptions
* @returns number
*/
exec(): Promise<number>;
}
/**
* Convert an arg string to an array of args. Handles escaping
*
* @param argString string of arguments
* @returns string[] array of arguments
*/
export declare function argStringToArray(argString: string): string[];
+574
View File
@@ -0,0 +1,574 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
const events = require("events");
const child = require("child_process");
/* eslint-disable @typescript-eslint/unbound-method */
const IS_WINDOWS = process.platform === 'win32';
/*
* Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way.
*/
class ToolRunner extends events.EventEmitter {
constructor(toolPath, args, options) {
super();
if (!toolPath) {
throw new Error("Parameter 'toolPath' cannot be null or empty.");
}
this.toolPath = toolPath;
this.args = args || [];
this.options = options || {};
}
_debug(message) {
if (this.options.listeners && this.options.listeners.debug) {
this.options.listeners.debug(message);
}
}
_getCommandString(options, noPrefix) {
const toolPath = this._getSpawnFileName();
const args = this._getSpawnArgs(options);
let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool
if (IS_WINDOWS) {
// Windows + cmd file
if (this._isCmdFile()) {
cmd += toolPath;
for (const a of args) {
cmd += ` ${a}`;
}
}
// Windows + verbatim
else if (options.windowsVerbatimArguments) {
cmd += `"${toolPath}"`;
for (const a of args) {
cmd += ` ${a}`;
}
}
// Windows (regular)
else {
cmd += this._windowsQuoteCmdArg(toolPath);
for (const a of args) {
cmd += ` ${this._windowsQuoteCmdArg(a)}`;
}
}
}
else {
// OSX/Linux - this can likely be improved with some form of quoting.
// creating processes on Unix is fundamentally different than Windows.
// on Unix, execvp() takes an arg array.
cmd += toolPath;
for (const a of args) {
cmd += ` ${a}`;
}
}
return cmd;
}
_processLineBuffer(data, strBuffer, onLine) {
try {
let s = strBuffer + data.toString();
let n = s.indexOf(os.EOL);
while (n > -1) {
const line = s.substring(0, n);
onLine(line);
// the rest of the string ...
s = s.substring(n + os.EOL.length);
n = s.indexOf(os.EOL);
}
strBuffer = s;
}
catch (err) {
// streaming lines to console is best effort. Don't fail a build.
this._debug(`error processing line. Failed with error ${err}`);
}
}
_getSpawnFileName() {
if (IS_WINDOWS) {
if (this._isCmdFile()) {
return process.env['COMSPEC'] || 'cmd.exe';
}
}
return this.toolPath;
}
_getSpawnArgs(options) {
if (IS_WINDOWS) {
if (this._isCmdFile()) {
let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`;
for (const a of this.args) {
argline += ' ';
argline += options.windowsVerbatimArguments
? a
: this._windowsQuoteCmdArg(a);
}
argline += '"';
return [argline];
}
}
return this.args;
}
_endsWith(str, end) {
return str.endsWith(end);
}
_isCmdFile() {
const upperToolPath = this.toolPath.toUpperCase();
return (this._endsWith(upperToolPath, '.CMD') ||
this._endsWith(upperToolPath, '.BAT'));
}
_windowsQuoteCmdArg(arg) {
// for .exe, apply the normal quoting rules that libuv applies
if (!this._isCmdFile()) {
return this._uvQuoteCmdArg(arg);
}
// otherwise apply quoting rules specific to the cmd.exe command line parser.
// the libuv rules are generic and are not designed specifically for cmd.exe
// command line parser.
//
// for a detailed description of the cmd.exe command line parser, refer to
// http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912
// need quotes for empty arg
if (!arg) {
return '""';
}
// determine whether the arg needs to be quoted
const cmdSpecialChars = [
' ',
'\t',
'&',
'(',
')',
'[',
']',
'{',
'}',
'^',
'=',
';',
'!',
"'",
'+',
',',
'`',
'~',
'|',
'<',
'>',
'"'
];
let needsQuotes = false;
for (const char of arg) {
if (cmdSpecialChars.some(x => x === char)) {
needsQuotes = true;
break;
}
}
// short-circuit if quotes not needed
if (!needsQuotes) {
return arg;
}
// the following quoting rules are very similar to the rules that by libuv applies.
//
// 1) wrap the string in quotes
//
// 2) double-up quotes - i.e. " => ""
//
// this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately
// doesn't work well with a cmd.exe command line.
//
// note, replacing " with "" also works well if the arg is passed to a downstream .NET console app.
// for example, the command line:
// foo.exe "myarg:""my val"""
// is parsed by a .NET console app into an arg array:
// [ "myarg:\"my val\"" ]
// which is the same end result when applying libuv quoting rules. although the actual
// command line from libuv quoting rules would look like:
// foo.exe "myarg:\"my val\""
//
// 3) double-up slashes that precede a quote,
// e.g. hello \world => "hello \world"
// hello\"world => "hello\\""world"
// hello\\"world => "hello\\\\""world"
// hello world\ => "hello world\\"
//
// technically this is not required for a cmd.exe command line, or the batch argument parser.
// the reasons for including this as a .cmd quoting rule are:
//
// a) this is optimized for the scenario where the argument is passed from the .cmd file to an
// external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule.
//
// b) it's what we've been doing previously (by deferring to node default behavior) and we
// haven't heard any complaints about that aspect.
//
// note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be
// escaped when used on the command line directly - even though within a .cmd file % can be escaped
// by using %%.
//
// the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts
// the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing.
//
// one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would
// often work, since it is unlikely that var^ would exist, and the ^ character is removed when the
// variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args
// to an external program.
//
// an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file.
// % can be escaped within a .cmd file.
let reverse = '"';
let quoteHit = true;
for (let i = arg.length; i > 0; i--) {
// walk the string in reverse
reverse += arg[i - 1];
if (quoteHit && arg[i - 1] === '\\') {
reverse += '\\'; // double the slash
}
else if (arg[i - 1] === '"') {
quoteHit = true;
reverse += '"'; // double the quote
}
else {
quoteHit = false;
}
}
reverse += '"';
return reverse
.split('')
.reverse()
.join('');
}
_uvQuoteCmdArg(arg) {
// Tool runner wraps child_process.spawn() and needs to apply the same quoting as
// Node in certain cases where the undocumented spawn option windowsVerbatimArguments
// is used.
//
// Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV,
// see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details),
// pasting copyright notice from Node within this function:
//
// Copyright Joyent, Inc. and other Node contributors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
if (!arg) {
// Need double quotation for empty argument
return '""';
}
if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) {
// No quotation needed
return arg;
}
if (!arg.includes('"') && !arg.includes('\\')) {
// No embedded double quotes or backslashes, so I can just wrap
// quote marks around the whole thing.
return `"${arg}"`;
}
// Expected input/output:
// input : hello"world
// output: "hello\"world"
// input : hello""world
// output: "hello\"\"world"
// input : hello\world
// output: hello\world
// input : hello\\world
// output: hello\\world
// input : hello\"world
// output: "hello\\\"world"
// input : hello\\"world
// output: "hello\\\\\"world"
// input : hello world\
// output: "hello world\\" - note the comment in libuv actually reads "hello world\"
// but it appears the comment is wrong, it should be "hello world\\"
let reverse = '"';
let quoteHit = true;
for (let i = arg.length; i > 0; i--) {
// walk the string in reverse
reverse += arg[i - 1];
if (quoteHit && arg[i - 1] === '\\') {
reverse += '\\';
}
else if (arg[i - 1] === '"') {
quoteHit = true;
reverse += '\\';
}
else {
quoteHit = false;
}
}
reverse += '"';
return reverse
.split('')
.reverse()
.join('');
}
_cloneExecOptions(options) {
options = options || {};
const result = {
cwd: options.cwd || process.cwd(),
env: options.env || process.env,
silent: options.silent || false,
windowsVerbatimArguments: options.windowsVerbatimArguments || false,
failOnStdErr: options.failOnStdErr || false,
ignoreReturnCode: options.ignoreReturnCode || false,
delay: options.delay || 10000
};
result.outStream = options.outStream || process.stdout;
result.errStream = options.errStream || process.stderr;
return result;
}
_getSpawnOptions(options, toolPath) {
options = options || {};
const result = {};
result.cwd = options.cwd;
result.env = options.env;
result['windowsVerbatimArguments'] =
options.windowsVerbatimArguments || this._isCmdFile();
if (options.windowsVerbatimArguments) {
result.argv0 = `"${toolPath}"`;
}
return result;
}
/**
* Exec a tool.
* Output will be streamed to the live console.
* Returns promise with return code
*
* @param tool path to tool to exec
* @param options optional exec options. See ExecOptions
* @returns number
*/
exec() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
this._debug(`exec tool: ${this.toolPath}`);
this._debug('arguments:');
for (const arg of this.args) {
this._debug(` ${arg}`);
}
const optionsNonNull = this._cloneExecOptions(this.options);
if (!optionsNonNull.silent && optionsNonNull.outStream) {
optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL);
}
const state = new ExecState(optionsNonNull, this.toolPath);
state.on('debug', (message) => {
this._debug(message);
});
const fileName = this._getSpawnFileName();
const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName));
const stdbuffer = '';
if (cp.stdout) {
cp.stdout.on('data', (data) => {
if (this.options.listeners && this.options.listeners.stdout) {
this.options.listeners.stdout(data);
}
if (!optionsNonNull.silent && optionsNonNull.outStream) {
optionsNonNull.outStream.write(data);
}
this._processLineBuffer(data, stdbuffer, (line) => {
if (this.options.listeners && this.options.listeners.stdline) {
this.options.listeners.stdline(line);
}
});
});
}
const errbuffer = '';
if (cp.stderr) {
cp.stderr.on('data', (data) => {
state.processStderr = true;
if (this.options.listeners && this.options.listeners.stderr) {
this.options.listeners.stderr(data);
}
if (!optionsNonNull.silent &&
optionsNonNull.errStream &&
optionsNonNull.outStream) {
const s = optionsNonNull.failOnStdErr
? optionsNonNull.errStream
: optionsNonNull.outStream;
s.write(data);
}
this._processLineBuffer(data, errbuffer, (line) => {
if (this.options.listeners && this.options.listeners.errline) {
this.options.listeners.errline(line);
}
});
});
}
cp.on('error', (err) => {
state.processError = err.message;
state.processExited = true;
state.processClosed = true;
state.CheckComplete();
});
cp.on('exit', (code) => {
state.processExitCode = code;
state.processExited = true;
this._debug(`Exit code ${code} received from tool '${this.toolPath}'`);
state.CheckComplete();
});
cp.on('close', (code) => {
state.processExitCode = code;
state.processExited = true;
state.processClosed = true;
this._debug(`STDIO streams have closed for tool '${this.toolPath}'`);
state.CheckComplete();
});
state.on('done', (error, exitCode) => {
if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
}
if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
}
cp.removeAllListeners();
if (error) {
reject(error);
}
else {
resolve(exitCode);
}
});
});
});
}
}
exports.ToolRunner = ToolRunner;
/**
* Convert an arg string to an array of args. Handles escaping
*
* @param argString string of arguments
* @returns string[] array of arguments
*/
function argStringToArray(argString) {
const args = [];
let inQuotes = false;
let escaped = false;
let arg = '';
function append(c) {
// we only escape double quotes.
if (escaped && c !== '"') {
arg += '\\';
}
arg += c;
escaped = false;
}
for (let i = 0; i < argString.length; i++) {
const c = argString.charAt(i);
if (c === '"') {
if (!escaped) {
inQuotes = !inQuotes;
}
else {
append(c);
}
continue;
}
if (c === '\\' && escaped) {
append(c);
continue;
}
if (c === '\\' && inQuotes) {
escaped = true;
continue;
}
if (c === ' ' && !inQuotes) {
if (arg.length > 0) {
args.push(arg);
arg = '';
}
continue;
}
append(c);
}
if (arg.length > 0) {
args.push(arg.trim());
}
return args;
}
exports.argStringToArray = argStringToArray;
class ExecState extends events.EventEmitter {
constructor(options, toolPath) {
super();
this.processClosed = false; // tracks whether the process has exited and stdio is closed
this.processError = '';
this.processExitCode = 0;
this.processExited = false; // tracks whether the process has exited
this.processStderr = false; // tracks whether stderr was written to
this.delay = 10000; // 10 seconds
this.done = false;
this.timeout = null;
if (!toolPath) {
throw new Error('toolPath must not be empty');
}
this.options = options;
this.toolPath = toolPath;
if (options.delay) {
this.delay = options.delay;
}
}
CheckComplete() {
if (this.done) {
return;
}
if (this.processClosed) {
this._setResult();
}
else if (this.processExited) {
this.timeout = setTimeout(ExecState.HandleTimeout, this.delay, this);
}
}
_debug(message) {
this.emit('debug', message);
}
_setResult() {
// determine whether there is an error
let error;
if (this.processExited) {
if (this.processError) {
error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`);
}
else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) {
error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`);
}
else if (this.processStderr && this.options.failOnStdErr) {
error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`);
}
}
// clear the timeout
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.done = true;
this.emit('done', error, this.processExitCode);
}
static HandleTimeout(state) {
if (state.done) {
return;
}
if (!state.processClosed && state.processExited) {
const message = `The STDIO streams did not close within ${state.delay /
1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`;
state._debug(message);
}
state._setResult();
}
}
//# sourceMappingURL=toolrunner.js.map
File diff suppressed because one or more lines are too long
+68
View File
@@ -0,0 +1,68 @@
{
"_args": [
[
"@actions/exec@1.0.1",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "@actions/exec@1.0.1",
"_id": "@actions/exec@1.0.1",
"_inBundle": false,
"_integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ==",
"_location": "/@actions/exec",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/exec@1.0.1",
"name": "@actions/exec",
"escapedName": "@actions%2fexec",
"scope": "@actions",
"rawSpec": "1.0.1",
"saveSpec": null,
"fetchSpec": "1.0.1"
},
"_requiredBy": [
"/",
"/@actions/tool-cache"
],
"_resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz",
"_spec": "1.0.1",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"description": "Actions exec lib",
"devDependencies": {
"@actions/io": "^1.0.1"
},
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"gitHead": "a2ab4bcf78e4f7080f0d45856e6eeba16f0bbc52",
"homepage": "https://github.com/actions/toolkit/tree/master/packages/exec",
"keywords": [
"github",
"actions",
"exec"
],
"license": "MIT",
"main": "lib/exec.js",
"name": "@actions/exec",
"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"
},
"version": "1.0.1"
}
+7
View File
@@ -0,0 +1,7 @@
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.
+53
View File
@@ -0,0 +1,53 @@
# `@actions/io`
> Core functions for cli filesystem scenarios
## 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');
```
#### cp/mv
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):
```js
const io = require('@actions/io');
// 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']);
```
+29
View File
@@ -0,0 +1,29 @@
/// <reference types="node" />
import * as fs from 'fs';
export declare const chmod: typeof fs.promises.chmod, copyFile: typeof fs.promises.copyFile, lstat: typeof fs.promises.lstat, mkdir: typeof fs.promises.mkdir, readdir: typeof fs.promises.readdir, readlink: typeof fs.promises.readlink, rename: typeof fs.promises.rename, rmdir: typeof fs.promises.rmdir, stat: typeof fs.promises.stat, symlink: typeof fs.promises.symlink, unlink: typeof fs.promises.unlink;
export declare const IS_WINDOWS: boolean;
export declare function exists(fsPath: string): Promise<boolean>;
export declare function isDirectory(fsPath: string, useStat?: boolean): Promise<boolean>;
/**
* On OSX/Linux, true if path starts with '/'. On Windows, true for paths like:
* \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases).
*/
export declare function isRooted(p: string): boolean;
/**
* Recursively create a directory at `fsPath`.
*
* This implementation is optimistic, meaning it attempts to create the full
* path first, and backs up the path stack from there.
*
* @param fsPath The path to create
* @param maxDepth The maximum recursion depth
* @param depth The current recursion depth
*/
export declare function mkdirP(fsPath: string, maxDepth?: number, depth?: number): Promise<void>;
/**
* Best effort attempt to determine whether a file exists and is executable.
* @param filePath file path to check
* @param extensions additional file extensions to try
* @return if file exists and is executable, returns the file path. otherwise empty string.
*/
export declare function tryGetExecutablePath(filePath: string, extensions: string[]): Promise<string>;
+195
View File
@@ -0,0 +1,195 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
const assert_1 = require("assert");
const fs = require("fs");
const path = require("path");
_a = fs.promises, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink;
exports.IS_WINDOWS = process.platform === 'win32';
function exists(fsPath) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield exports.stat(fsPath);
}
catch (err) {
if (err.code === 'ENOENT') {
return false;
}
throw err;
}
return true;
});
}
exports.exists = exists;
function isDirectory(fsPath, useStat = false) {
return __awaiter(this, void 0, void 0, function* () {
const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath);
return stats.isDirectory();
});
}
exports.isDirectory = isDirectory;
/**
* On OSX/Linux, true if path starts with '/'. On Windows, true for paths like:
* \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases).
*/
function isRooted(p) {
p = normalizeSeparators(p);
if (!p) {
throw new Error('isRooted() parameter "p" cannot be empty');
}
if (exports.IS_WINDOWS) {
return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello
); // e.g. C: or C:\hello
}
return p.startsWith('/');
}
exports.isRooted = isRooted;
/**
* Recursively create a directory at `fsPath`.
*
* This implementation is optimistic, meaning it attempts to create the full
* path first, and backs up the path stack from there.
*
* @param fsPath The path to create
* @param maxDepth The maximum recursion depth
* @param depth The current recursion depth
*/
function mkdirP(fsPath, maxDepth = 1000, depth = 1) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.ok(fsPath, 'a path argument must be provided');
fsPath = path.resolve(fsPath);
if (depth >= maxDepth)
return exports.mkdir(fsPath);
try {
yield exports.mkdir(fsPath);
return;
}
catch (err) {
switch (err.code) {
case 'ENOENT': {
yield mkdirP(path.dirname(fsPath), maxDepth, depth + 1);
yield exports.mkdir(fsPath);
return;
}
default: {
let stats;
try {
stats = yield exports.stat(fsPath);
}
catch (err2) {
throw err;
}
if (!stats.isDirectory())
throw err;
}
}
}
});
}
exports.mkdirP = mkdirP;
/**
* Best effort attempt to determine whether a file exists and is executable.
* @param filePath file path to check
* @param extensions additional file extensions to try
* @return if file exists and is executable, returns the file path. otherwise empty string.
*/
function tryGetExecutablePath(filePath, extensions) {
return __awaiter(this, void 0, void 0, function* () {
let stats = undefined;
try {
// test file exists
stats = yield exports.stat(filePath);
}
catch (err) {
if (err.code !== 'ENOENT') {
// eslint-disable-next-line no-console
console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`);
}
}
if (stats && stats.isFile()) {
if (exports.IS_WINDOWS) {
// on Windows, test for valid extension
const upperExt = path.extname(filePath).toUpperCase();
if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) {
return filePath;
}
}
else {
if (isUnixExecutable(stats)) {
return filePath;
}
}
}
// try each extension
const originalFilePath = filePath;
for (const extension of extensions) {
filePath = originalFilePath + extension;
stats = undefined;
try {
stats = yield exports.stat(filePath);
}
catch (err) {
if (err.code !== 'ENOENT') {
// eslint-disable-next-line no-console
console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`);
}
}
if (stats && stats.isFile()) {
if (exports.IS_WINDOWS) {
// preserve the case of the actual file (since an extension was appended)
try {
const directory = path.dirname(filePath);
const upperName = path.basename(filePath).toUpperCase();
for (const actualName of yield exports.readdir(directory)) {
if (upperName === actualName.toUpperCase()) {
filePath = path.join(directory, actualName);
break;
}
}
}
catch (err) {
// eslint-disable-next-line no-console
console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`);
}
return filePath;
}
else {
if (isUnixExecutable(stats)) {
return filePath;
}
}
}
}
return '';
});
}
exports.tryGetExecutablePath = tryGetExecutablePath;
function normalizeSeparators(p) {
p = p || '';
if (exports.IS_WINDOWS) {
// convert slashes on Windows
p = p.replace(/\//g, '\\');
// remove redundant slashes
return p.replace(/\\\\+/g, '\\');
}
// remove redundant slashes
return p.replace(/\/\/+/g, '/');
}
// on Mac/Linux, test the execute bit
// R W X R W X R W X
// 256 128 64 32 16 8 4 2 1
function isUnixExecutable(stats) {
return ((stats.mode & 1) > 0 ||
((stats.mode & 8) > 0 && stats.gid === process.getgid()) ||
((stats.mode & 64) > 0 && stats.uid === process.getuid()));
}
//# sourceMappingURL=io-util.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"io-util.js","sourceRoot":"","sources":["../src/io-util.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mCAAyB;AACzB,yBAAwB;AACxB,6BAA4B;AAEf,gBAYE,qTAAA;AAEF,QAAA,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAA;AAEtD,SAAsB,MAAM,CAAC,MAAc;;QACzC,IAAI;YACF,MAAM,YAAI,CAAC,MAAM,CAAC,CAAA;SACnB;QAAC,OAAO,GAAG,EAAE;YACZ,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE;gBACzB,OAAO,KAAK,CAAA;aACb;YAED,MAAM,GAAG,CAAA;SACV;QAED,OAAO,IAAI,CAAA;IACb,CAAC;CAAA;AAZD,wBAYC;AAED,SAAsB,WAAW,CAC/B,MAAc,EACd,UAAmB,KAAK;;QAExB,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,YAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,aAAK,CAAC,MAAM,CAAC,CAAA;QAChE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAA;IAC5B,CAAC;CAAA;AAND,kCAMC;AAED;;;GAGG;AACH,SAAgB,QAAQ,CAAC,CAAS;IAChC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAA;IAC1B,IAAI,CAAC,CAAC,EAAE;QACN,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;KAC5D;IAED,IAAI,kBAAU,EAAE;QACd,OAAO,CACL,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,8BAA8B;SACxE,CAAA,CAAC,sBAAsB;KACzB;IAED,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;AAC1B,CAAC;AAbD,4BAaC;AAED;;;;;;;;;GASG;AACH,SAAsB,MAAM,CAC1B,MAAc,EACd,WAAmB,IAAI,EACvB,QAAgB,CAAC;;QAEjB,WAAE,CAAC,MAAM,EAAE,kCAAkC,CAAC,CAAA;QAE9C,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAE7B,IAAI,KAAK,IAAI,QAAQ;YAAE,OAAO,aAAK,CAAC,MAAM,CAAC,CAAA;QAE3C,IAAI;YACF,MAAM,aAAK,CAAC,MAAM,CAAC,CAAA;YACnB,OAAM;SACP;QAAC,OAAO,GAAG,EAAE;YACZ,QAAQ,GAAG,CAAC,IAAI,EAAE;gBAChB,KAAK,QAAQ,CAAC,CAAC;oBACb,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;oBACvD,MAAM,aAAK,CAAC,MAAM,CAAC,CAAA;oBACnB,OAAM;iBACP;gBACD,OAAO,CAAC,CAAC;oBACP,IAAI,KAAe,CAAA;oBAEnB,IAAI;wBACF,KAAK,GAAG,MAAM,YAAI,CAAC,MAAM,CAAC,CAAA;qBAC3B;oBAAC,OAAO,IAAI,EAAE;wBACb,MAAM,GAAG,CAAA;qBACV;oBAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;wBAAE,MAAM,GAAG,CAAA;iBACpC;aACF;SACF;IACH,CAAC;CAAA;AAlCD,wBAkCC;AAED;;;;;GAKG;AACH,SAAsB,oBAAoB,CACxC,QAAgB,EAChB,UAAoB;;QAEpB,IAAI,KAAK,GAAyB,SAAS,CAAA;QAC3C,IAAI;YACF,mBAAmB;YACnB,KAAK,GAAG,MAAM,YAAI,CAAC,QAAQ,CAAC,CAAA;SAC7B;QAAC,OAAO,GAAG,EAAE;YACZ,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE;gBACzB,sCAAsC;gBACtC,OAAO,CAAC,GAAG,CACT,uEAAuE,QAAQ,MAAM,GAAG,EAAE,CAC3F,CAAA;aACF;SACF;QACD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE;YAC3B,IAAI,kBAAU,EAAE;gBACd,uCAAuC;gBACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;gBACrD,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,EAAE;oBACpE,OAAO,QAAQ,CAAA;iBAChB;aACF;iBAAM;gBACL,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE;oBAC3B,OAAO,QAAQ,CAAA;iBAChB;aACF;SACF;QAED,qBAAqB;QACrB,MAAM,gBAAgB,GAAG,QAAQ,CAAA;QACjC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;YAClC,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAAA;YAEvC,KAAK,GAAG,SAAS,CAAA;YACjB,IAAI;gBACF,KAAK,GAAG,MAAM,YAAI,CAAC,QAAQ,CAAC,CAAA;aAC7B;YAAC,OAAO,GAAG,EAAE;gBACZ,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE;oBACzB,sCAAsC;oBACtC,OAAO,CAAC,GAAG,CACT,uEAAuE,QAAQ,MAAM,GAAG,EAAE,CAC3F,CAAA;iBACF;aACF;YAED,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE;gBAC3B,IAAI,kBAAU,EAAE;oBACd,yEAAyE;oBACzE,IAAI;wBACF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;wBACxC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;wBACvD,KAAK,MAAM,UAAU,IAAI,MAAM,eAAO,CAAC,SAAS,CAAC,EAAE;4BACjD,IAAI,SAAS,KAAK,UAAU,CAAC,WAAW,EAAE,EAAE;gCAC1C,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;gCAC3C,MAAK;6BACN;yBACF;qBACF;oBAAC,OAAO,GAAG,EAAE;wBACZ,sCAAsC;wBACtC,OAAO,CAAC,GAAG,CACT,yEAAyE,QAAQ,MAAM,GAAG,EAAE,CAC7F,CAAA;qBACF;oBAED,OAAO,QAAQ,CAAA;iBAChB;qBAAM;oBACL,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE;wBAC3B,OAAO,QAAQ,CAAA;qBAChB;iBACF;aACF;SACF;QAED,OAAO,EAAE,CAAA;IACX,CAAC;CAAA;AA5ED,oDA4EC;AAED,SAAS,mBAAmB,CAAC,CAAS;IACpC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;IACX,IAAI,kBAAU,EAAE;QACd,6BAA6B;QAC7B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAE1B,2BAA2B;QAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;KACjC;IAED,2BAA2B;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;AACjC,CAAC;AAED,qCAAqC;AACrC,6BAA6B;AAC7B,6BAA6B;AAC7B,SAAS,gBAAgB,CAAC,KAAe;IACvC,OAAO,CACL,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC;QACpB,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACxD,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAC1D,CAAA;AACH,CAAC"}
+56
View File
@@ -0,0 +1,56 @@
/**
* Interface for cp/mv options
*/
export interface CopyOptions {
/** Optional. Whether to recursively copy all subdirectories. Defaults to false */
recursive?: boolean;
/** Optional. Whether to overwrite existing files in the destination. Defaults to true */
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
* @param options optional. See CopyOptions.
*/
export declare function cp(source: string, dest: string, options?: CopyOptions): Promise<void>;
/**
* Moves a path.
*
* @param source source path
* @param dest destination path
* @param options optional. See MoveOptions.
*/
export declare function mv(source: string, dest: string, options?: MoveOptions): Promise<void>;
/**
* Remove a path recursively with force
*
* @param inputPath path to remove
*/
export declare function rmRF(inputPath: string): Promise<void>;
/**
* Make a directory. Creates the full path with folders in between
* Will throw if it fails
*
* @param fsPath path to create
* @returns Promise<void>
*/
export declare function mkdirP(fsPath: string): Promise<void>;
/**
* Returns path of a tool had the tool actually been invoked. Resolves via paths.
* If you check and the tool does not exist, it will throw.
*
* @param tool name of the tool
* @param check whether to check if tool exists
* @returns Promise<string> path to tool
*/
export declare function which(tool: string, check?: boolean): Promise<string>;
+290
View File
@@ -0,0 +1,290 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const childProcess = require("child_process");
const path = require("path");
const util_1 = require("util");
const ioUtil = require("./io-util");
const exec = util_1.promisify(childProcess.exec);
/**
* 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
* @param options optional. See CopyOptions.
*/
function cp(source, dest, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const { force, recursive } = readCopyOptions(options);
const destStat = (yield ioUtil.exists(dest)) ? yield 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 = destStat && destStat.isDirectory()
? path.join(dest, path.basename(source))
: dest;
if (!(yield ioUtil.exists(source))) {
throw new Error(`no such file or directory: ${source}`);
}
const sourceStat = yield 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 {
yield 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`);
}
yield copyFile(source, newDest, force);
}
});
}
exports.cp = cp;
/**
* Moves a path.
*
* @param source source path
* @param dest destination path
* @param options optional. See MoveOptions.
*/
function mv(source, dest, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
if (yield ioUtil.exists(dest)) {
let destExists = true;
if (yield ioUtil.isDirectory(dest)) {
// If dest is directory copy src into dest
dest = path.join(dest, path.basename(source));
destExists = yield ioUtil.exists(dest);
}
if (destExists) {
if (options.force == null || options.force) {
yield rmRF(dest);
}
else {
throw new Error('Destination already exists');
}
}
}
yield mkdirP(path.dirname(dest));
yield ioUtil.rename(source, dest);
});
}
exports.mv = mv;
/**
* Remove a path recursively with force
*
* @param inputPath path to remove
*/
function rmRF(inputPath) {
return __awaiter(this, void 0, void 0, function* () {
if (ioUtil.IS_WINDOWS) {
// Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another
// program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del.
try {
if (yield ioUtil.isDirectory(inputPath, true)) {
yield exec(`rd /s /q "${inputPath}"`);
}
else {
yield exec(`del /f /a "${inputPath}"`);
}
}
catch (err) {
// if you try to delete a file that doesn't exist, desired result is achieved
// other errors are valid
if (err.code !== 'ENOENT')
throw err;
}
// Shelling out fails to remove a symlink folder with missing source, this unlink catches that
try {
yield ioUtil.unlink(inputPath);
}
catch (err) {
// if you try to delete a file that doesn't exist, desired result is achieved
// other errors are valid
if (err.code !== 'ENOENT')
throw err;
}
}
else {
let isDir = false;
try {
isDir = yield ioUtil.isDirectory(inputPath);
}
catch (err) {
// if you try to delete a file that doesn't exist, desired result is achieved
// other errors are valid
if (err.code !== 'ENOENT')
throw err;
return;
}
if (isDir) {
yield exec(`rm -rf "${inputPath}"`);
}
else {
yield ioUtil.unlink(inputPath);
}
}
});
}
exports.rmRF = rmRF;
/**
* Make a directory. Creates the full path with folders in between
* Will throw if it fails
*
* @param fsPath path to create
* @returns Promise<void>
*/
function mkdirP(fsPath) {
return __awaiter(this, void 0, void 0, function* () {
yield ioUtil.mkdirP(fsPath);
});
}
exports.mkdirP = mkdirP;
/**
* Returns path of a tool had the tool actually been invoked. Resolves via paths.
* If you check and the tool does not exist, it will throw.
*
* @param tool name of the tool
* @param check whether to check if tool exists
* @returns Promise<string> path to tool
*/
function which(tool, check) {
return __awaiter(this, void 0, void 0, function* () {
if (!tool) {
throw new Error("parameter 'tool' is required");
}
// recursive when check=true
if (check) {
const result = yield which(tool, false);
if (!result) {
if (ioUtil.IS_WINDOWS) {
throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`);
}
else {
throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`);
}
}
}
try {
// build the list of extensions to try
const extensions = [];
if (ioUtil.IS_WINDOWS && process.env.PATHEXT) {
for (const extension of process.env.PATHEXT.split(path.delimiter)) {
if (extension) {
extensions.push(extension);
}
}
}
// if it's rooted, return it if exists. otherwise return empty.
if (ioUtil.isRooted(tool)) {
const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions);
if (filePath) {
return filePath;
}
return '';
}
// if any path separators, return empty
if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) {
return '';
}
// build the list of directories
//
// 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 toolkit should strive for consistency
// across platforms.
const directories = [];
if (process.env.PATH) {
for (const p of process.env.PATH.split(path.delimiter)) {
if (p) {
directories.push(p);
}
}
}
// return the first match
for (const directory of directories) {
const filePath = yield ioUtil.tryGetExecutablePath(directory + path.sep + tool, extensions);
if (filePath) {
return filePath;
}
}
return '';
}
catch (err) {
throw new Error(`which failed with message ${err.message}`);
}
});
}
exports.which = which;
function readCopyOptions(options) {
const force = options.force == null ? true : options.force;
const recursive = Boolean(options.recursive);
return { force, recursive };
}
function cpDirRecursive(sourceDir, destDir, currentDepth, force) {
return __awaiter(this, void 0, void 0, function* () {
// Ensure there is not a run away recursive copy
if (currentDepth >= 255)
return;
currentDepth++;
yield mkdirP(destDir);
const files = yield ioUtil.readdir(sourceDir);
for (const fileName of files) {
const srcFile = `${sourceDir}/${fileName}`;
const destFile = `${destDir}/${fileName}`;
const srcFileStat = yield ioUtil.lstat(srcFile);
if (srcFileStat.isDirectory()) {
// Recurse
yield cpDirRecursive(srcFile, destFile, currentDepth, force);
}
else {
yield copyFile(srcFile, destFile, force);
}
}
// Change the mode for the newly created directory
yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode);
});
}
// Buffered file copy
function copyFile(srcFile, destFile, force) {
return __awaiter(this, void 0, void 0, function* () {
if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) {
// unlink/re-link it
try {
yield ioUtil.lstat(destFile);
yield ioUtil.unlink(destFile);
}
catch (e) {
// Try to override file permission
if (e.code === 'EPERM') {
yield ioUtil.chmod(destFile, '0666');
yield ioUtil.unlink(destFile);
}
// other errors = it doesn't exist, no work to do
}
// Copy over symlink
const symlinkFull = yield ioUtil.readlink(srcFile);
yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null);
}
else if (!(yield ioUtil.exists(destFile)) || force) {
yield ioUtil.copyFile(srcFile, destFile);
}
});
}
//# sourceMappingURL=io.js.map
+1
View File
File diff suppressed because one or more lines are too long
+64
View File
@@ -0,0 +1,64 @@
{
"_args": [
[
"@actions/io@1.0.1",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "@actions/io@1.0.1",
"_id": "@actions/io@1.0.1",
"_inBundle": false,
"_integrity": "sha512-rhq+tfZukbtaus7xyUtwKfuiCRXd1hWSfmJNEpFgBQJ4woqPEpsBw04awicjwz9tyG2/MVhAEMfVn664Cri5zA==",
"_location": "/@actions/io",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/io@1.0.1",
"name": "@actions/io",
"escapedName": "@actions%2fio",
"scope": "@actions",
"rawSpec": "1.0.1",
"saveSpec": null,
"fetchSpec": "1.0.1"
},
"_requiredBy": [
"/@actions/tool-cache"
],
"_resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.1.tgz",
"_spec": "1.0.1",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"description": "Actions io lib",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"gitHead": "a2ab4bcf78e4f7080f0d45856e6eeba16f0bbc52",
"homepage": "https://github.com/actions/toolkit/tree/master/packages/io",
"keywords": [
"github",
"actions",
"io"
],
"license": "MIT",
"main": "lib/io.js",
"name": "@actions/io",
"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"
},
"version": "1.0.1"
}
+82
View File
@@ -0,0 +1,82 @@
# `@actions/tool-cache`
> Functions necessary for downloading and caching tools.
## Usage
#### 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');
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}`);
```
+79
View File
@@ -0,0 +1,79 @@
export declare class HTTPError extends Error {
readonly httpStatusCode: number | undefined;
constructor(httpStatusCode: number | undefined);
}
/**
* Download a tool from an url and stream it into a file
*
* @param url url of tool to download
* @returns path to downloaded tool
*/
export declare function downloadTool(url: string): Promise<string>;
/**
* Extract a .7z file
*
* @param file path to the .7z file
* @param dest destination directory. Optional.
* @param _7zPath path to 7zr.exe. Optional, for long path support. Most .7z archives do not have this
* problem. If your .7z archive contains very long paths, you can pass the path to 7zr.exe which will
* gracefully handle long paths. By default 7zdec.exe is used because it is a very small program and is
* bundled with the tool lib. However it does not support long paths. 7zr.exe is the reduced command line
* interface, it is smaller than the full command line interface, and it does support long paths. At the
* time of this writing, it is freely available from the LZMA SDK that is available on the 7zip website.
* Be sure to check the current license agreement. If 7zr.exe is bundled with your action, then the path
* to 7zr.exe can be pass to this function.
* @returns path to the destination directory
*/
export declare function extract7z(file: string, dest?: string, _7zPath?: string): Promise<string>;
/**
* Extract a tar
*
* @param file path to the tar
* @param dest destination directory. Optional.
* @param flags flags for the tar. Optional.
* @returns path to the destination directory
*/
export declare function extractTar(file: string, dest?: string, flags?: string): Promise<string>;
/**
* Extract a zip
*
* @param file path to the zip
* @param dest destination directory. Optional.
* @returns path to the destination directory
*/
export declare function extractZip(file: string, dest?: string): Promise<string>;
/**
* Caches a directory and installs it into the tool cacheDir
*
* @param sourceDir the directory to cache into tools
* @param tool tool name
* @param version version of the tool. semver format
* @param arch architecture of the tool. Optional. Defaults to machine architecture
*/
export declare function cacheDir(sourceDir: string, tool: string, version: string, arch?: string): Promise<string>;
/**
* Caches a downloaded file (GUID) and installs it
* into the tool cache with a given targetName
*
* @param sourceFile the file to cache into tools. Typically a result of downloadTool which is a guid.
* @param targetFile the name of the file name in the tools directory
* @param tool tool name
* @param version version of the tool. semver format
* @param arch architecture of the tool. Optional. Defaults to machine architecture
*/
export declare function cacheFile(sourceFile: string, targetFile: string, tool: string, version: string, arch?: string): Promise<string>;
/**
* Finds the path to a tool version in the local installed tool cache
*
* @param toolName name of the tool
* @param versionSpec version of the tool
* @param arch optional arch. defaults to arch of computer
*/
export declare function find(toolName: string, versionSpec: string, arch?: string): string;
/**
* Finds the paths to all versions of a tool that are installed in the local tool cache
*
* @param toolName name of the tool
* @param arch optional arch. defaults to arch of computer
*/
export declare function findAllVersions(toolName: string, arch?: string): string[];
+438
View File
@@ -0,0 +1,438 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = require("@actions/core");
const io = require("@actions/io");
const fs = require("fs");
const os = require("os");
const path = require("path");
const httpm = require("typed-rest-client/HttpClient");
const semver = require("semver");
const uuidV4 = require("uuid/v4");
const exec_1 = require("@actions/exec/lib/exec");
const assert_1 = require("assert");
class HTTPError extends Error {
constructor(httpStatusCode) {
super(`Unexpected HTTP response: ${httpStatusCode}`);
this.httpStatusCode = httpStatusCode;
Object.setPrototypeOf(this, new.target.prototype);
}
}
exports.HTTPError = HTTPError;
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 = process.env['RUNNER_TEMP'] || '';
let cacheRoot = process.env['RUNNER_TOOL_CACHE'] || '';
// If directories not found, place them in common temp locations
if (!tempDirectory || !cacheRoot) {
let baseLocation;
if (IS_WINDOWS) {
// On windows use the USERPROFILE env variable
baseLocation = process.env['USERPROFILE'] || 'C:\\';
}
else {
if (process.platform === 'darwin') {
baseLocation = '/Users';
}
else {
baseLocation = '/home';
}
}
if (!tempDirectory) {
tempDirectory = path.join(baseLocation, 'actions', 'temp');
}
if (!cacheRoot) {
cacheRoot = path.join(baseLocation, 'actions', 'cache');
}
}
/**
* Download a tool from an url and stream it into a file
*
* @param url url of tool to download
* @returns path to downloaded tool
*/
function downloadTool(url) {
return __awaiter(this, void 0, void 0, function* () {
// Wrap in a promise so that we can resolve from within stream callbacks
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
const http = new httpm.HttpClient(userAgent, [], {
allowRetries: true,
maxRetries: 3
});
const destPath = path.join(tempDirectory, uuidV4());
yield io.mkdirP(tempDirectory);
core.debug(`Downloading ${url}`);
core.debug(`Downloading ${destPath}`);
if (fs.existsSync(destPath)) {
throw new Error(`Destination file path ${destPath} already exists`);
}
const response = yield http.get(url);
if (response.message.statusCode !== 200) {
const err = new HTTPError(response.message.statusCode);
core.debug(`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`);
throw err;
}
const file = fs.createWriteStream(destPath);
file.on('open', () => __awaiter(this, void 0, void 0, function* () {
try {
const stream = response.message.pipe(file);
stream.on('close', () => {
core.debug('download complete');
resolve(destPath);
});
}
catch (err) {
core.debug(`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`);
reject(err);
}
}));
file.on('error', err => {
file.end();
reject(err);
});
}
catch (err) {
reject(err);
}
}));
});
}
exports.downloadTool = downloadTool;
/**
* Extract a .7z file
*
* @param file path to the .7z file
* @param dest destination directory. Optional.
* @param _7zPath path to 7zr.exe. Optional, for long path support. Most .7z archives do not have this
* problem. If your .7z archive contains very long paths, you can pass the path to 7zr.exe which will
* gracefully handle long paths. By default 7zdec.exe is used because it is a very small program and is
* bundled with the tool lib. However it does not support long paths. 7zr.exe is the reduced command line
* interface, it is smaller than the full command line interface, and it does support long paths. At the
* time of this writing, it is freely available from the LZMA SDK that is available on the 7zip website.
* Be sure to check the current license agreement. If 7zr.exe is bundled with your action, then the path
* to 7zr.exe can be pass to this function.
* @returns path to the destination directory
*/
function extract7z(file, dest, _7zPath) {
return __awaiter(this, void 0, void 0, function* () {
assert_1.ok(IS_WINDOWS, 'extract7z() not supported on current OS');
assert_1.ok(file, 'parameter "file" is required');
dest = dest || (yield _createExtractFolder(dest));
const originalCwd = process.cwd();
process.chdir(dest);
if (_7zPath) {
try {
const args = [
'x',
'-bb1',
'-bd',
'-sccUTF-8',
file
];
const options = {
silent: true
};
yield exec_1.exec(`"${_7zPath}"`, args, options);
}
finally {
process.chdir(originalCwd);
}
}
else {
const escapedScript = path
.join(__dirname, '..', 'scripts', 'Invoke-7zdec.ps1')
.replace(/'/g, "''")
.replace(/"|\n|\r/g, ''); // double-up single quotes, remove double quotes and newlines
const escapedFile = file.replace(/'/g, "''").replace(/"|\n|\r/g, '');
const escapedTarget = dest.replace(/'/g, "''").replace(/"|\n|\r/g, '');
const command = `& '${escapedScript}' -Source '${escapedFile}' -Target '${escapedTarget}'`;
const args = [
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
command
];
const options = {
silent: true
};
try {
const powershellPath = yield io.which('powershell', true);
yield exec_1.exec(`"${powershellPath}"`, args, options);
}
finally {
process.chdir(originalCwd);
}
}
return dest;
});
}
exports.extract7z = extract7z;
/**
* Extract a tar
*
* @param file path to the tar
* @param dest destination directory. Optional.
* @param flags flags for the tar. Optional.
* @returns path to the destination directory
*/
function extractTar(file, dest, flags = 'xz') {
return __awaiter(this, void 0, void 0, function* () {
if (!file) {
throw new Error("parameter 'file' is required");
}
dest = dest || (yield _createExtractFolder(dest));
const tarPath = yield io.which('tar', true);
yield exec_1.exec(`"${tarPath}"`, [flags, '-C', dest, '-f', file]);
return dest;
});
}
exports.extractTar = extractTar;
/**
* Extract a zip
*
* @param file path to the zip
* @param dest destination directory. Optional.
* @returns path to the destination directory
*/
function extractZip(file, dest) {
return __awaiter(this, void 0, void 0, function* () {
if (!file) {
throw new Error("parameter 'file' is required");
}
dest = dest || (yield _createExtractFolder(dest));
if (IS_WINDOWS) {
yield extractZipWin(file, dest);
}
else {
yield extractZipNix(file, dest);
}
return dest;
});
}
exports.extractZip = extractZip;
function extractZipWin(file, dest) {
return __awaiter(this, void 0, void 0, function* () {
// build the powershell command
const escapedFile = file.replace(/'/g, "''").replace(/"|\n|\r/g, ''); // double-up single quotes, remove double quotes and newlines
const escapedDest = dest.replace(/'/g, "''").replace(/"|\n|\r/g, '');
const command = `$ErrorActionPreference = 'Stop' ; try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}')`;
// run powershell
const powershellPath = yield io.which('powershell');
const args = [
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
command
];
yield exec_1.exec(`"${powershellPath}"`, args);
});
}
function extractZipNix(file, dest) {
return __awaiter(this, void 0, void 0, function* () {
const unzipPath = yield io.which('unzip');
yield exec_1.exec(`"${unzipPath}"`, [file], { cwd: dest });
});
}
/**
* Caches a directory and installs it into the tool cacheDir
*
* @param sourceDir the directory to cache into tools
* @param tool tool name
* @param version version of the tool. semver format
* @param arch architecture of the tool. Optional. Defaults to machine architecture
*/
function cacheDir(sourceDir, tool, version, arch) {
return __awaiter(this, void 0, void 0, function* () {
version = semver.clean(version) || version;
arch = arch || os.arch();
core.debug(`Caching tool ${tool} ${version} ${arch}`);
core.debug(`source dir: ${sourceDir}`);
if (!fs.statSync(sourceDir).isDirectory()) {
throw new Error('sourceDir is not a directory');
}
// Create the tool dir
const destPath = yield _createToolPath(tool, version, arch);
// copy each child item. do not move. move can fail on Windows
// due to anti-virus software having an open handle on a file.
for (const itemName of fs.readdirSync(sourceDir)) {
const s = path.join(sourceDir, itemName);
yield io.cp(s, destPath, { recursive: true });
}
// write .complete
_completeToolPath(tool, version, arch);
return destPath;
});
}
exports.cacheDir = cacheDir;
/**
* Caches a downloaded file (GUID) and installs it
* into the tool cache with a given targetName
*
* @param sourceFile the file to cache into tools. Typically a result of downloadTool which is a guid.
* @param targetFile the name of the file name in the tools directory
* @param tool tool name
* @param version version of the tool. semver format
* @param arch architecture of the tool. Optional. Defaults to machine architecture
*/
function cacheFile(sourceFile, targetFile, tool, version, arch) {
return __awaiter(this, void 0, void 0, function* () {
version = semver.clean(version) || version;
arch = arch || os.arch();
core.debug(`Caching tool ${tool} ${version} ${arch}`);
core.debug(`source file: ${sourceFile}`);
if (!fs.statSync(sourceFile).isFile()) {
throw new Error('sourceFile is not a file');
}
// create the tool dir
const destFolder = yield _createToolPath(tool, version, arch);
// copy instead of move. move can fail on Windows due to
// anti-virus software having an open handle on a file.
const destPath = path.join(destFolder, targetFile);
core.debug(`destination file ${destPath}`);
yield io.cp(sourceFile, destPath);
// write .complete
_completeToolPath(tool, version, arch);
return destFolder;
});
}
exports.cacheFile = cacheFile;
/**
* Finds the path to a tool version in the local installed tool cache
*
* @param toolName name of the tool
* @param versionSpec version of the tool
* @param arch optional arch. defaults to arch of computer
*/
function find(toolName, versionSpec, arch) {
if (!toolName) {
throw new Error('toolName parameter is required');
}
if (!versionSpec) {
throw new Error('versionSpec parameter is required');
}
arch = arch || os.arch();
// attempt to resolve an explicit version
if (!_isExplicitVersion(versionSpec)) {
const localVersions = findAllVersions(toolName, arch);
const match = _evaluateVersions(localVersions, versionSpec);
versionSpec = match;
}
// check for the explicit version in the cache
let toolPath = '';
if (versionSpec) {
versionSpec = semver.clean(versionSpec) || '';
const cachePath = path.join(cacheRoot, toolName, versionSpec, arch);
core.debug(`checking cache: ${cachePath}`);
if (fs.existsSync(cachePath) && fs.existsSync(`${cachePath}.complete`)) {
core.debug(`Found tool in cache ${toolName} ${versionSpec} ${arch}`);
toolPath = cachePath;
}
else {
core.debug('not found');
}
}
return toolPath;
}
exports.find = find;
/**
* Finds the paths to all versions of a tool that are installed in the local tool cache
*
* @param toolName name of the tool
* @param arch optional arch. defaults to arch of computer
*/
function findAllVersions(toolName, arch) {
const versions = [];
arch = arch || os.arch();
const toolPath = path.join(cacheRoot, toolName);
if (fs.existsSync(toolPath)) {
const children = fs.readdirSync(toolPath);
for (const child of children) {
if (_isExplicitVersion(child)) {
const fullPath = path.join(toolPath, child, arch || '');
if (fs.existsSync(fullPath) && fs.existsSync(`${fullPath}.complete`)) {
versions.push(child);
}
}
}
}
return versions;
}
exports.findAllVersions = findAllVersions;
function _createExtractFolder(dest) {
return __awaiter(this, void 0, void 0, function* () {
if (!dest) {
// create a temp dir
dest = path.join(tempDirectory, uuidV4());
}
yield io.mkdirP(dest);
return dest;
});
}
function _createToolPath(tool, version, arch) {
return __awaiter(this, void 0, void 0, function* () {
const folderPath = path.join(cacheRoot, tool, semver.clean(version) || version, arch || '');
core.debug(`destination ${folderPath}`);
const markerPath = `${folderPath}.complete`;
yield io.rmRF(folderPath);
yield io.rmRF(markerPath);
yield io.mkdirP(folderPath);
return folderPath;
});
}
function _completeToolPath(tool, version, arch) {
const folderPath = path.join(cacheRoot, tool, semver.clean(version) || version, arch || '');
const markerPath = `${folderPath}.complete`;
fs.writeFileSync(markerPath, '');
core.debug('finished caching tool');
}
function _isExplicitVersion(versionSpec) {
const c = semver.clean(versionSpec) || '';
core.debug(`isExplicit: ${c}`);
const valid = semver.valid(c) != null;
core.debug(`explicit? ${valid}`);
return valid;
}
function _evaluateVersions(versions, versionSpec) {
let version = '';
core.debug(`evaluating ${versions.length} versions`);
versions = versions.sort((a, b) => {
if (semver.gt(a, b)) {
return 1;
}
return -1;
});
for (let i = versions.length - 1; i >= 0; i--) {
const potential = versions[i];
const satisfied = semver.satisfies(potential, versionSpec);
if (satisfied) {
version = potential;
break;
}
}
if (version) {
core.debug(`matched: ${version}`);
}
else {
core.debug('match not found');
}
return version;
}
//# sourceMappingURL=tool-cache.js.map
File diff suppressed because one or more lines are too long
+140
View File
@@ -0,0 +1,140 @@
# `@actions/core`
> Core functions for setting results, logging, registering secrets and exporting variables across actions
## Usage
### 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,16 @@
interface CommandProperties {
[key: string]: string;
}
/**
* Commands
*
* Command Format:
* ##[name key=value;key=value]message
*
* Examples:
* ##[warning]This is the user warning message
* ##[set-secret name=mypassword]definitelyNotAPassword!
*/
export declare function issueCommand(command: string, properties: CommandProperties, message: string): void;
export declare function issue(name: string, message?: string): void;
export {};
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
/**
* Commands
*
* Command Format:
* ##[name key=value;key=value]message
*
* Examples:
* ##[warning]This is the user warning message
* ##[set-secret name=mypassword]definitelyNotAPassword!
*/
function issueCommand(command, properties, message) {
const cmd = new Command(command, properties, message);
process.stdout.write(cmd.toString() + os.EOL);
}
exports.issueCommand = issueCommand;
function issue(name, message = '') {
issueCommand(name, {}, message);
}
exports.issue = issue;
const CMD_STRING = '::';
class Command {
constructor(command, properties, message) {
if (!command) {
command = 'missing.command';
}
this.command = command;
this.properties = properties;
this.message = message;
}
toString() {
let cmdStr = CMD_STRING + this.command;
if (this.properties && Object.keys(this.properties).length > 0) {
cmdStr += ' ';
for (const key in this.properties) {
if (this.properties.hasOwnProperty(key)) {
const val = this.properties[key];
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 += CMD_STRING;
// safely append the message - avoid blowing up when attempting to
// call .replace() if message is not a string for some reason
const message = `${this.message || ''}`;
cmdStr += escapeData(message);
return cmdStr;
}
}
function escapeData(s) {
return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A');
}
function escape(s) {
return s
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A')
.replace(/]/g, '%5D')
.replace(/;/g, '%3B');
}
//# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":";;AAAA,yBAAwB;AAQxB;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA6B,EAC7B,OAAe;IAEf,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;AAC/C,CAAC;AAPD,oCAOC;AAED,SAAgB,KAAK,CAAC,IAAY,EAAE,UAAkB,EAAE;IACtD,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;AACjC,CAAC;AAFD,sBAEC;AAED,MAAM,UAAU,GAAG,IAAI,CAAA;AAEvB,MAAM,OAAO;IAKX,YAAY,OAAe,EAAE,UAA6B,EAAE,OAAe;QACzE,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,iBAAiB,CAAA;SAC5B;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,QAAQ;QACN,IAAI,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,OAAO,CAAA;QAEtC,IAAI,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9D,MAAM,IAAI,GAAG,CAAA;YACb,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE;gBACjC,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;oBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;oBAChC,IAAI,GAAG,EAAE;wBACP,8DAA8D;wBAC9D,6DAA6D;wBAC7D,MAAM,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,EAAE,CAAC,GAAG,CAAA;qBAC9C;iBACF;aACF;SACF;QAED,MAAM,IAAI,UAAU,CAAA;QAEpB,kEAAkE;QAClE,6DAA6D;QAC7D,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAA;QACvC,MAAM,IAAI,UAAU,CAAC,OAAO,CAAC,CAAA;QAE7B,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC;SACL,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AACzB,CAAC"}
@@ -0,0 +1,112 @@
/**
* Interface for getInput options
*/
export interface InputOptions {
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
required?: boolean;
}
/**
* The code to exit an action
*/
export declare enum ExitCode {
/**
* A code indicating that the action was successful
*/
Success = 0,
/**
* A code indicating that the action was a failure
*/
Failure = 1
}
/**
* 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
*/
export declare function exportVariable(name: string, val: string): void;
/**
* Registers a secret which will get masked from logs
* @param secret value of the secret
*/
export declare function setSecret(secret: string): void;
/**
* Prepends inputPath to the PATH (for this action and future actions)
* @param inputPath
*/
export declare function addPath(inputPath: string): void;
/**
* Gets the value of an input. The value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
export declare function getInput(name: string, options?: InputOptions): string;
/**
* Sets the value of an output.
*
* @param name name of the output to set
* @param value value to store
*/
export declare function setOutput(name: string, value: string): void;
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
export declare function setFailed(message: string): void;
/**
* Writes debug message to user log
* @param message debug message
*/
export declare function debug(message: string): void;
/**
* Adds an error issue
* @param message error issue message
*/
export declare function error(message: string): void;
/**
* Adds an warning issue
* @param message warning issue message
*/
export declare function warning(message: string): void;
/**
* Writes info to log with console.log.
* @param message info message
*/
export declare function info(message: string): void;
/**
* Begin an output group.
*
* Output until the next `groupEnd` will be foldable in this group
*
* @param name The name of the output group
*/
export declare function startGroup(name: string): void;
/**
* End an output group.
*/
export declare function endGroup(): void;
/**
* 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 declare function group<T>(name: string, fn: () => Promise<T>): Promise<T>;
/**
* 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 declare function saveState(name: string, value: string): void;
/**
* Gets the value of an state set by this action's main execution.
*
* @param name name of the state to get
* @returns string
*/
export declare function getState(name: string): string;
+195
View File
@@ -0,0 +1,195 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("./command");
const os = require("os");
const path = require("path");
/**
* The code to exit an action
*/
var ExitCode;
(function (ExitCode) {
/**
* A code indicating that the action was successful
*/
ExitCode[ExitCode["Success"] = 0] = "Success";
/**
* A code indicating that the action was a failure
*/
ExitCode[ExitCode["Failure"] = 1] = "Failure";
})(ExitCode = exports.ExitCode || (exports.ExitCode = {}));
//-----------------------------------------------------------------------
// Variables
//-----------------------------------------------------------------------
/**
* 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
*/
function exportVariable(name, val) {
process.env[name] = val;
command_1.issueCommand('set-env', { name }, val);
}
exports.exportVariable = exportVariable;
/**
* Registers a secret which will get masked from logs
* @param secret value of the secret
*/
function setSecret(secret) {
command_1.issueCommand('add-mask', {}, secret);
}
exports.setSecret = setSecret;
/**
* Prepends inputPath to the PATH (for this action and future actions)
* @param inputPath
*/
function addPath(inputPath) {
command_1.issueCommand('add-path', {}, inputPath);
process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`;
}
exports.addPath = addPath;
/**
* Gets the value of an input. The value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
function getInput(name, options) {
const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
return val.trim();
}
exports.getInput = getInput;
/**
* Sets the value of an output.
*
* @param name name of the output to set
* @param value value to store
*/
function setOutput(name, value) {
command_1.issueCommand('set-output', { name }, value);
}
exports.setOutput = setOutput;
//-----------------------------------------------------------------------
// Results
//-----------------------------------------------------------------------
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
function setFailed(message) {
process.exitCode = ExitCode.Failure;
error(message);
}
exports.setFailed = setFailed;
//-----------------------------------------------------------------------
// Logging Commands
//-----------------------------------------------------------------------
/**
* Writes debug message to user log
* @param message debug message
*/
function debug(message) {
command_1.issueCommand('debug', {}, message);
}
exports.debug = debug;
/**
* Adds an error issue
* @param message error issue message
*/
function error(message) {
command_1.issue('error', message);
}
exports.error = error;
/**
* Adds an warning issue
* @param message warning issue message
*/
function warning(message) {
command_1.issue('warning', message);
}
exports.warning = warning;
/**
* Writes info to log with console.log.
* @param message info message
*/
function info(message) {
process.stdout.write(message + os.EOL);
}
exports.info = info;
/**
* Begin an output group.
*
* Output until the next `groupEnd` will be foldable in this group
*
* @param name The name of the output group
*/
function startGroup(name) {
command_1.issue('group', name);
}
exports.startGroup = startGroup;
/**
* End an output group.
*/
function endGroup() {
command_1.issue('endgroup');
}
exports.endGroup = 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
*/
function group(name, fn) {
return __awaiter(this, void 0, void 0, function* () {
startGroup(name);
let result;
try {
result = yield fn();
}
finally {
endGroup();
}
return result;
});
}
exports.group = group;
//-----------------------------------------------------------------------
// 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
*/
function saveState(name, value) {
command_1.issueCommand('save-state', { name }, value);
}
exports.saveState = saveState;
/**
* Gets the value of an state set by this action's main execution.
*
* @param name name of the state to get
* @returns string
*/
function getState(name) {
return process.env[`STATE_${name}`] || '';
}
exports.getState = getState;
//# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,uCAA6C;AAE7C,yBAAwB;AACxB,6BAA4B;AAU5B;;GAEG;AACH,IAAY,QAUX;AAVD,WAAY,QAAQ;IAClB;;OAEG;IACH,6CAAW,CAAA;IAEX;;OAEG;IACH,6CAAW,CAAA;AACb,CAAC,EAVW,QAAQ,GAAR,gBAAQ,KAAR,gBAAQ,QAUnB;AAED,yEAAyE;AACzE,YAAY;AACZ,yEAAyE;AAEzE;;;;GAIG;AACH,SAAgB,cAAc,CAAC,IAAY,EAAE,GAAW;IACtD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAA;IACvB,sBAAY,CAAC,SAAS,EAAE,EAAC,IAAI,EAAC,EAAE,GAAG,CAAC,CAAA;AACtC,CAAC;AAHD,wCAGC;AAED;;;GAGG;AACH,SAAgB,SAAS,CAAC,MAAc;IACtC,sBAAY,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;AACtC,CAAC;AAFD,8BAEC;AAED;;;GAGG;AACH,SAAgB,OAAO,CAAC,SAAiB;IACvC,sBAAY,CAAC,UAAU,EAAE,EAAE,EAAE,SAAS,CAAC,CAAA;IACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAA;AAC7E,CAAC;AAHD,0BAGC;AAED;;;;;;GAMG;AACH,SAAgB,QAAQ,CAAC,IAAY,EAAE,OAAsB;IAC3D,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;IACrE,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE;QACvC,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAA;KAC5D;IAED,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;AACnB,CAAC;AARD,4BAQC;AAED;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,sBAAY,CAAC,YAAY,EAAE,EAAC,IAAI,EAAC,EAAE,KAAK,CAAC,CAAA;AAC3C,CAAC;AAFD,8BAEC;AAED,yEAAyE;AACzE,UAAU;AACV,yEAAyE;AAEzE;;;;GAIG;AACH,SAAgB,SAAS,CAAC,OAAe;IACvC,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAA;IACnC,KAAK,CAAC,OAAO,CAAC,CAAA;AAChB,CAAC;AAHD,8BAGC;AAED,yEAAyE;AACzE,mBAAmB;AACnB,yEAAyE;AAEzE;;;GAGG;AACH,SAAgB,KAAK,CAAC,OAAe;IACnC,sBAAY,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AAFD,sBAEC;AAED;;;GAGG;AACH,SAAgB,KAAK,CAAC,OAAe;IACnC,eAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AACzB,CAAC;AAFD,sBAEC;AAED;;;GAGG;AACH,SAAgB,OAAO,CAAC,OAAe;IACrC,eAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;AAC3B,CAAC;AAFD,0BAEC;AAED;;;GAGG;AACH,SAAgB,IAAI,CAAC,OAAe;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;AACxC,CAAC;AAFD,oBAEC;AAED;;;;;;GAMG;AACH,SAAgB,UAAU,CAAC,IAAY;IACrC,eAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACtB,CAAC;AAFD,gCAEC;AAED;;GAEG;AACH,SAAgB,QAAQ;IACtB,eAAK,CAAC,UAAU,CAAC,CAAA;AACnB,CAAC;AAFD,4BAEC;AAED;;;;;;;GAOG;AACH,SAAsB,KAAK,CAAI,IAAY,EAAE,EAAoB;;QAC/D,UAAU,CAAC,IAAI,CAAC,CAAA;QAEhB,IAAI,MAAS,CAAA;QAEb,IAAI;YACF,MAAM,GAAG,MAAM,EAAE,EAAE,CAAA;SACpB;gBAAS;YACR,QAAQ,EAAE,CAAA;SACX;QAED,OAAO,MAAM,CAAA;IACf,CAAC;CAAA;AAZD,sBAYC;AAED,yEAAyE;AACzE,uBAAuB;AACvB,yEAAyE;AAEzE;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,sBAAY,CAAC,YAAY,EAAE,EAAC,IAAI,EAAC,EAAE,KAAK,CAAC,CAAA;AAC3C,CAAC;AAFD,8BAEC;AAED;;;;;GAKG;AACH,SAAgB,QAAQ,CAAC,IAAY;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;AAC3C,CAAC;AAFD,4BAEC"}
@@ -0,0 +1,67 @@
{
"_args": [
[
"@actions/core@1.2.0",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "@actions/core@1.2.0",
"_id": "@actions/core@1.2.0",
"_inBundle": false,
"_integrity": "sha512-ZKdyhlSlyz38S6YFfPnyNgCDZuAF2T0Qv5eHflNWytPS8Qjvz39bZFMry9Bb/dpSnqWcNeav5yM2CTYpJeY+Dw==",
"_location": "/@actions/tool-cache/@actions/core",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/core@1.2.0",
"name": "@actions/core",
"escapedName": "@actions%2fcore",
"scope": "@actions",
"rawSpec": "1.2.0",
"saveSpec": null,
"fetchSpec": "1.2.0"
},
"_requiredBy": [
"/@actions/tool-cache"
],
"_resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.0.tgz",
"_spec": "1.2.0",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"description": "Actions core lib",
"devDependencies": {
"@types/node": "^12.0.2"
},
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"homepage": "https://github.com/actions/toolkit/tree/master/packages/core",
"keywords": [
"github",
"actions",
"core"
],
"license": "MIT",
"main": "lib/core.js",
"name": "@actions/core",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/toolkit.git",
"directory": "packages/core"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"tsc": "tsc"
},
"version": "1.2.0"
}
+78
View File
@@ -0,0 +1,78 @@
{
"_args": [
[
"@actions/tool-cache@1.1.2",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "@actions/tool-cache@1.1.2",
"_id": "@actions/tool-cache@1.1.2",
"_inBundle": false,
"_integrity": "sha512-IJczPaZr02ECa3Lgws/TJEVco9tjOujiQSZbO3dHuXXjhd5vrUtfOgGwhmz3/f97L910OraPZ8SknofUk6RvOQ==",
"_location": "/@actions/tool-cache",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/tool-cache@1.1.2",
"name": "@actions/tool-cache",
"escapedName": "@actions%2ftool-cache",
"scope": "@actions",
"rawSpec": "1.1.2",
"saveSpec": null,
"fetchSpec": "1.1.2"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.1.2.tgz",
"_spec": "1.1.2",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"bugs": {
"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"
},
"description": "Actions tool-cache lib",
"devDependencies": {
"@types/nock": "^10.0.3",
"@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"nock": "^10.0.6"
},
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib",
"scripts"
],
"homepage": "https://github.com/actions/toolkit/tree/master/packages/exec",
"keywords": [
"github",
"actions",
"exec"
],
"license": "MIT",
"main": "lib/tool-cache.js",
"name": "@actions/tool-cache",
"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"
},
"version": "1.1.2"
}
+60
View File
@@ -0,0 +1,60 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Source,
[Parameter(Mandatory = $true)]
[string]$Target)
# This script translates the output from 7zdec into UTF8. Node has limited
# built-in support for encodings.
#
# 7zdec uses the system default code page. The system default code page varies
# depending on the locale configuration. On an en-US box, the system default code
# page is Windows-1252.
#
# Note, on a typical en-US box, testing with the 'ç' character is a good way to
# determine whether data is passed correctly between processes. This is because
# the 'ç' character has a different code point across each of the common encodings
# on a typical en-US box, i.e.
# 1) the default console-output code page (IBM437)
# 2) the system default code page (i.e. CP_ACP) (Windows-1252)
# 3) UTF8
$ErrorActionPreference = 'Stop'
# Redefine the wrapper over STDOUT to use UTF8. Node expects UTF8 by default.
$stdout = [System.Console]::OpenStandardOutput()
$utf8 = New-Object System.Text.UTF8Encoding($false) # do not emit BOM
$writer = New-Object System.IO.StreamWriter($stdout, $utf8)
[System.Console]::SetOut($writer)
# All subsequent output must be written using [System.Console]::WriteLine(). In
# PowerShell 4, Write-Host and Out-Default do not consider the updated stream writer.
Set-Location -LiteralPath $Target
# Print the ##command.
$_7zdec = Join-Path -Path "$PSScriptRoot" -ChildPath "externals/7zdec.exe"
[System.Console]::WriteLine("##[command]$_7zdec x `"$Source`"")
# The $OutputEncoding variable instructs PowerShell how to interpret the output
# from the external command.
$OutputEncoding = [System.Text.Encoding]::Default
# Note, the output from 7zdec.exe needs to be iterated over. Otherwise PowerShell.exe
# will launch the external command in such a way that it inherits the streams.
& $_7zdec x $Source 2>&1 |
ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) {
[System.Console]::WriteLine($_.Exception.Message)
}
else {
[System.Console]::WriteLine($_)
}
}
[System.Console]::WriteLine("##[debug]7zdec.exe exit code '$LASTEXITCODE'")
[System.Console]::Out.Flush()
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
# changes log
## 6.2.0
* Coerce numbers to strings when passed to semver.coerce()
* Add `rtl` option to coerce from right to left
## 6.1.3
* Handle X-ranges properly in includePrerelease mode
## 6.1.2
* Do not throw when testing invalid version strings
## 6.1.1
* Add options support for semver.coerce()
* Handle undefined version passed to Range.test
## 6.1.0
* Add semver.compareBuild function
* Support `*` in semver.intersects
## 6.0
* Fix `intersects` logic.
This is technically a bug fix, but since it is also a change to behavior
that may require users updating their code, it is marked as a major
version increment.
## 5.7
* Add `minVersion` method
## 5.6
* Move boolean `loose` param to an options object, with
backwards-compatibility protection.
* Add ability to opt out of special prerelease version handling with
the `includePrerelease` option flag.
## 5.5
* Add version coercion capabilities
## 5.4
* Add intersection checking
## 5.3
* Add `minSatisfying` method
## 5.2
* Add `prerelease(v)` that returns prerelease components
## 5.1
* Add Backus-Naur for ranges
* Remove excessively cute inspection methods
## 5.0
* Remove AMD/Browserified build artifacts
* Fix ltr and gtr when using the `*` range
* Fix for range `*` with a prerelease identifier
+15
View File
@@ -0,0 +1,15 @@
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+443
View File
@@ -0,0 +1,443 @@
semver(1) -- The semantic versioner for npm
===========================================
## Install
```bash
npm install semver
````
## Usage
As a node module:
```js
const semver = require('semver')
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
semver.minVersion('>=1.0.0') // '1.0.0'
semver.valid(semver.coerce('v2')) // '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
```
As a command-line utility:
```
$ semver -h
A JavaScript implementation of the https://semver.org/ specification
Copyright Isaac Z. Schlueter
Usage: semver [options] <version> [<version> [...]]
Prints valid versions sorted by SemVer precedence
Options:
-r --range <range>
Print versions that match the specified range.
-i --increment [<level>]
Increment a version by the specified level. Level can
be one of: major, minor, patch, premajor, preminor,
prepatch, or prerelease. Default level is 'patch'.
Only one version may be specified.
--preid <identifier>
Identifier to be used to prefix premajor, preminor,
prepatch or prerelease version increments.
-l --loose
Interpret versions and ranges loosely
-p --include-prerelease
Always include prerelease versions in range matching
-c --coerce
Coerce a string into SemVer if possible
(does not imply --loose)
--rtl
Coerce version strings right to left
--ltr
Coerce version strings left to right (default)
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
If no satisfying versions are found, then exits failure.
Versions are printed in ascending order, so supplying
multiple versions to the utility will just sort them.
```
## Versions
A "version" is described by the `v2.0.0` specification found at
<https://semver.org/>.
A leading `"="` or `"v"` character is stripped off and ignored.
## Ranges
A `version range` is a set of `comparators` which specify versions
that satisfy the range.
A `comparator` is composed of an `operator` and a `version`. The set
of primitive `operators` is:
* `<` Less than
* `<=` Less than or equal to
* `>` Greater than
* `>=` Greater than or equal to
* `=` Equal. If no operator is specified, then equality is assumed,
so this operator is optional, but MAY be included.
For example, the comparator `>=1.2.7` would match the versions
`1.2.7`, `1.2.8`, `2.5.3`, and `1.3.9`, but not the versions `1.2.6`
or `1.1.0`.
Comparators can be joined by whitespace to form a `comparator set`,
which is satisfied by the **intersection** of all of the comparators
it includes.
A range is composed of one or more comparator sets, joined by `||`. A
version matches a range if and only if every comparator in at least
one of the `||`-separated comparator sets is satisfied by the version.
For example, the range `>=1.2.7 <1.3.0` would match the versions
`1.2.7`, `1.2.8`, and `1.2.99`, but not the versions `1.2.6`, `1.3.0`,
or `1.1.0`.
The range `1.2.7 || >=1.2.9 <2.0.0` would match the versions `1.2.7`,
`1.2.9`, and `1.4.6`, but not the versions `1.2.8` or `2.0.0`.
### Prerelease Tags
If a version has a prerelease tag (for example, `1.2.3-alpha.3`) then
it will only be allowed to satisfy comparator sets if at least one
comparator with the same `[major, minor, patch]` tuple also has a
prerelease tag.
For example, the range `>1.2.3-alpha.3` would be allowed to match the
version `1.2.3-alpha.7`, but it would *not* be satisfied by
`3.4.5-alpha.9`, even though `3.4.5-alpha.9` is technically "greater
than" `1.2.3-alpha.3` according to the SemVer sort rules. The version
range only accepts prerelease tags on the `1.2.3` version. The
version `3.4.5` *would* satisfy the range, because it does not have a
prerelease flag, and `3.4.5` is greater than `1.2.3-alpha.7`.
The purpose for this behavior is twofold. First, prerelease versions
frequently are updated very quickly, and contain many breaking changes
that are (by the author's design) not yet fit for public consumption.
Therefore, by default, they are excluded from range matching
semantics.
Second, a user who has opted into using a prerelease version has
clearly indicated the intent to use *that specific* set of
alpha/beta/rc versions. By including a prerelease tag in the range,
the user is indicating that they are aware of the risk. However, it
is still not appropriate to assume that they have opted into taking a
similar risk on the *next* set of prerelease versions.
Note that this behavior can be suppressed (treating all prerelease
versions as if they were normal versions, for the purpose of range
matching) by setting the `includePrerelease` flag on the options
object to any
[functions](https://github.com/npm/node-semver#functions) that do
range matching.
#### Prerelease Identifiers
The method `.inc` takes an additional `identifier` string argument that
will append the value of the string as a prerelease identifier:
```javascript
semver.inc('1.2.3', 'prerelease', 'beta')
// '1.2.4-beta.0'
```
command-line example:
```bash
$ semver 1.2.3 -i prerelease --preid beta
1.2.4-beta.0
```
Which then can be used to increment further:
```bash
$ semver 1.2.4-beta.0 -i prerelease
1.2.4-beta.1
```
### Advanced Range Syntax
Advanced range syntax desugars to primitive comparators in
deterministic ways.
Advanced ranges may be combined in the same way as primitive
comparators using white space or `||`.
#### Hyphen Ranges `X.Y.Z - A.B.C`
Specifies an inclusive set.
* `1.2.3 - 2.3.4` := `>=1.2.3 <=2.3.4`
If a partial version is provided as the first version in the inclusive
range, then the missing pieces are replaced with zeroes.
* `1.2 - 2.3.4` := `>=1.2.0 <=2.3.4`
If a partial version is provided as the second version in the
inclusive range, then all versions that start with the supplied parts
of the tuple are accepted, but nothing that would be greater than the
provided tuple parts.
* `1.2.3 - 2.3` := `>=1.2.3 <2.4.0`
* `1.2.3 - 2` := `>=1.2.3 <3.0.0`
#### X-Ranges `1.2.x` `1.X` `1.2.*` `*`
Any of `X`, `x`, or `*` may be used to "stand in" for one of the
numeric values in the `[major, minor, patch]` tuple.
* `*` := `>=0.0.0` (Any version satisfies)
* `1.x` := `>=1.0.0 <2.0.0` (Matching major version)
* `1.2.x` := `>=1.2.0 <1.3.0` (Matching major and minor versions)
A partial version range is treated as an X-Range, so the special
character is in fact optional.
* `""` (empty string) := `*` := `>=0.0.0`
* `1` := `1.x.x` := `>=1.0.0 <2.0.0`
* `1.2` := `1.2.x` := `>=1.2.0 <1.3.0`
#### Tilde Ranges `~1.2.3` `~1.2` `~1`
Allows patch-level changes if a minor version is specified on the
comparator. Allows minor-level changes if not.
* `~1.2.3` := `>=1.2.3 <1.(2+1).0` := `>=1.2.3 <1.3.0`
* `~1.2` := `>=1.2.0 <1.(2+1).0` := `>=1.2.0 <1.3.0` (Same as `1.2.x`)
* `~1` := `>=1.0.0 <(1+1).0.0` := `>=1.0.0 <2.0.0` (Same as `1.x`)
* `~0.2.3` := `>=0.2.3 <0.(2+1).0` := `>=0.2.3 <0.3.0`
* `~0.2` := `>=0.2.0 <0.(2+1).0` := `>=0.2.0 <0.3.0` (Same as `0.2.x`)
* `~0` := `>=0.0.0 <(0+1).0.0` := `>=0.0.0 <1.0.0` (Same as `0.x`)
* `~1.2.3-beta.2` := `>=1.2.3-beta.2 <1.3.0` Note that prereleases in
the `1.2.3` version will be allowed, if they are greater than or
equal to `beta.2`. So, `1.2.3-beta.4` would be allowed, but
`1.2.4-beta.2` would not, because it is a prerelease of a
different `[major, minor, patch]` tuple.
#### Caret Ranges `^1.2.3` `^0.2.5` `^0.0.4`
Allows changes that do not modify the left-most non-zero element in the
`[major, minor, patch]` tuple. In other words, this allows patch and
minor updates for versions `1.0.0` and above, patch updates for
versions `0.X >=0.1.0`, and *no* updates for versions `0.0.X`.
Many authors treat a `0.x` version as if the `x` were the major
"breaking-change" indicator.
Caret ranges are ideal when an author may make breaking changes
between `0.2.4` and `0.3.0` releases, which is a common practice.
However, it presumes that there will *not* be breaking changes between
`0.2.4` and `0.2.5`. It allows for changes that are presumed to be
additive (but non-breaking), according to commonly observed practices.
* `^1.2.3` := `>=1.2.3 <2.0.0`
* `^0.2.3` := `>=0.2.3 <0.3.0`
* `^0.0.3` := `>=0.0.3 <0.0.4`
* `^1.2.3-beta.2` := `>=1.2.3-beta.2 <2.0.0` Note that prereleases in
the `1.2.3` version will be allowed, if they are greater than or
equal to `beta.2`. So, `1.2.3-beta.4` would be allowed, but
`1.2.4-beta.2` would not, because it is a prerelease of a
different `[major, minor, patch]` tuple.
* `^0.0.3-beta` := `>=0.0.3-beta <0.0.4` Note that prereleases in the
`0.0.3` version *only* will be allowed, if they are greater than or
equal to `beta`. So, `0.0.3-pr.2` would be allowed.
When parsing caret ranges, a missing `patch` value desugars to the
number `0`, but will allow flexibility within that value, even if the
major and minor versions are both `0`.
* `^1.2.x` := `>=1.2.0 <2.0.0`
* `^0.0.x` := `>=0.0.0 <0.1.0`
* `^0.0` := `>=0.0.0 <0.1.0`
A missing `minor` and `patch` values will desugar to zero, but also
allow flexibility within those values, even if the major version is
zero.
* `^1.x` := `>=1.0.0 <2.0.0`
* `^0.x` := `>=0.0.0 <1.0.0`
### Range Grammar
Putting all this together, here is a Backus-Naur grammar for ranges,
for the benefit of parser authors:
```bnf
range-set ::= range ( logical-or range ) *
logical-or ::= ( ' ' ) * '||' ( ' ' ) *
range ::= hyphen | simple ( ' ' simple ) * | ''
hyphen ::= partial ' - ' partial
simple ::= primitive | partial | tilde | caret
primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
xr ::= 'x' | 'X' | '*' | nr
nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
tilde ::= '~' partial
caret ::= '^' partial
qualifier ::= ( '-' pre )? ( '+' build )?
pre ::= parts
build ::= parts
parts ::= part ( '.' part ) *
part ::= nr | [-0-9A-Za-z]+
```
## Functions
All methods and classes take a final `options` object argument. All
options in this object are `false` by default. The options supported
are:
- `loose` Be more forgiving about not-quite-valid semver strings.
(Any resulting output will always be 100% strict compliant, of
course.) For backwards compatibility reasons, if the `options`
argument is a boolean value instead of an object, it is interpreted
to be the `loose` param.
- `includePrerelease` Set to suppress the [default
behavior](https://github.com/npm/node-semver#prerelease-tags) of
excluding prerelease tagged versions from ranges unless they are
explicitly opted into.
Strict-mode Comparators and Ranges will be strict about the SemVer
strings that they parse.
* `valid(v)`: Return the parsed version, or null if it's not valid.
* `inc(v, release)`: Return the version incremented by the release
type (`major`, `premajor`, `minor`, `preminor`, `patch`,
`prepatch`, or `prerelease`), or null if it's not valid
* `premajor` in one call will bump the version up to the next major
version and down to a prerelease of that major version.
`preminor`, and `prepatch` work the same way.
* If called from a non-prerelease version, the `prerelease` will work the
same as `prepatch`. It increments the patch version, then makes a
prerelease. If the input version is already a prerelease it simply
increments it.
* `prerelease(v)`: Returns an array of prerelease components, or null
if none exist. Example: `prerelease('1.2.3-alpha.1') -> ['alpha', 1]`
* `major(v)`: Return the major version number.
* `minor(v)`: Return the minor version number.
* `patch(v)`: Return the patch version number.
* `intersects(r1, r2, loose)`: Return true if the two supplied ranges
or comparators intersect.
* `parse(v)`: Attempt to parse a string as a semantic version, returning either
a `SemVer` object or `null`.
### Comparison
* `gt(v1, v2)`: `v1 > v2`
* `gte(v1, v2)`: `v1 >= v2`
* `lt(v1, v2)`: `v1 < v2`
* `lte(v1, v2)`: `v1 <= v2`
* `eq(v1, v2)`: `v1 == v2` This is true if they're logically equivalent,
even if they're not the exact same string. You already know how to
compare strings.
* `neq(v1, v2)`: `v1 != v2` The opposite of `eq`.
* `cmp(v1, comparator, v2)`: Pass in a comparison string, and it'll call
the corresponding function above. `"==="` and `"!=="` do simple
string comparison, but are included for completeness. Throws if an
invalid comparison string is provided.
* `compare(v1, v2)`: Return `0` if `v1 == v2`, or `1` if `v1` is greater, or `-1` if
`v2` is greater. Sorts in ascending order if passed to `Array.sort()`.
* `rcompare(v1, v2)`: The reverse of compare. Sorts an array of versions
in descending order when passed to `Array.sort()`.
* `compareBuild(v1, v2)`: The same as `compare` but considers `build` when two versions
are equal. Sorts in ascending order if passed to `Array.sort()`.
`v2` is greater. Sorts in ascending order if passed to `Array.sort()`.
* `diff(v1, v2)`: Returns difference between two versions by the release type
(`major`, `premajor`, `minor`, `preminor`, `patch`, `prepatch`, or `prerelease`),
or null if the versions are the same.
### Comparators
* `intersects(comparator)`: Return true if the comparators intersect
### Ranges
* `validRange(range)`: Return the valid range or null if it's not valid
* `satisfies(version, range)`: Return true if the version satisfies the
range.
* `maxSatisfying(versions, range)`: Return the highest version in the list
that satisfies the range, or `null` if none of them do.
* `minSatisfying(versions, range)`: Return the lowest version in the list
that satisfies the range, or `null` if none of them do.
* `minVersion(range)`: Return the lowest version that can possibly match
the given range.
* `gtr(version, range)`: Return `true` if version is greater than all the
versions possible in the range.
* `ltr(version, range)`: Return `true` if version is less than all the
versions possible in the range.
* `outside(version, range, hilo)`: Return true if the version is outside
the bounds of the range in either the high or low direction. The
`hilo` argument must be either the string `'>'` or `'<'`. (This is
the function called by `gtr` and `ltr`.)
* `intersects(range)`: Return true if any of the ranges comparators intersect
Note that, since ranges may be non-contiguous, a version might not be
greater than a range, less than a range, *or* satisfy a range! For
example, the range `1.2 <1.2.9 || >2.0.0` would have a hole from `1.2.9`
until `2.0.0`, so the version `1.2.10` would not be greater than the
range (because `2.0.1` satisfies, which is higher), nor less than the
range (since `1.2.8` satisfies, which is lower), and it also does not
satisfy the range.
If you want to know if a version satisfies or does not satisfy a
range, use the `satisfies(version, range)` function.
### Coercion
* `coerce(version, options)`: Coerces a string to semver if possible
This aims to provide a very forgiving translation of a non-semver string to
semver. It looks for the first digit in a string, and consumes all
remaining characters which satisfy at least a partial semver (e.g., `1`,
`1.2`, `1.2.3`) up to the max permitted length (256 characters). Longer
versions are simply truncated (`4.6.3.9.2-alpha2` becomes `4.6.3`). All
surrounding text is simply ignored (`v3.4 replaces v3.3.1` becomes
`3.4.0`). Only text which lacks digits will fail coercion (`version one`
is not valid). The maximum length for any semver component considered for
coercion is 16 characters; longer components will be ignored
(`10000000000000000.4.7.4` becomes `4.7.4`). The maximum value for any
semver component is `Integer.MAX_SAFE_INTEGER || (2**53 - 1)`; higher value
components are invalid (`9999999999999999.4.7.4` is likely invalid).
If the `options.rtl` flag is set, then `coerce` will return the right-most
coercible tuple that does not share an ending index with a longer coercible
tuple. For example, `1.2.3.4` will return `2.3.4` in rtl mode, not
`4.0.0`. `1.2.3/4` will return `4.0.0`, because the `4` is not a part of
any other overlapping SemVer tuple.
### Clean
* `clean(version)`: Clean a string to be a valid semver if possible
This will return a cleaned and trimmed semver version. If the provided version is not valid a null will be returned. This does not work for ranges.
ex.
* `s.clean(' = v 2.1.5foo')`: `null`
* `s.clean(' = v 2.1.5foo', { loose: true })`: `'2.1.5-foo'`
* `s.clean(' = v 2.1.5-foo')`: `null`
* `s.clean(' = v 2.1.5-foo', { loose: true })`: `'2.1.5-foo'`
* `s.clean('=v2.1.5')`: `'2.1.5'`
* `s.clean(' =v2.1.5')`: `2.1.5`
* `s.clean(' 2.1.5 ')`: `'2.1.5'`
* `s.clean('~1.0.0')`: `null`
Generated Vendored Executable
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env node
// Standalone semver comparison program.
// Exits successfully and prints matching version(s) if
// any supplied version is valid and passes all tests.
var argv = process.argv.slice(2)
var versions = []
var range = []
var inc = null
var version = require('../package.json').version
var loose = false
var includePrerelease = false
var coerce = false
var rtl = false
var identifier
var semver = require('../semver')
var reverse = false
var options = {}
main()
function main () {
if (!argv.length) return help()
while (argv.length) {
var a = argv.shift()
var indexOfEqualSign = a.indexOf('=')
if (indexOfEqualSign !== -1) {
a = a.slice(0, indexOfEqualSign)
argv.unshift(a.slice(indexOfEqualSign + 1))
}
switch (a) {
case '-rv': case '-rev': case '--rev': case '--reverse':
reverse = true
break
case '-l': case '--loose':
loose = true
break
case '-p': case '--include-prerelease':
includePrerelease = true
break
case '-v': case '--version':
versions.push(argv.shift())
break
case '-i': case '--inc': case '--increment':
switch (argv[0]) {
case 'major': case 'minor': case 'patch': case 'prerelease':
case 'premajor': case 'preminor': case 'prepatch':
inc = argv.shift()
break
default:
inc = 'patch'
break
}
break
case '--preid':
identifier = argv.shift()
break
case '-r': case '--range':
range.push(argv.shift())
break
case '-c': case '--coerce':
coerce = true
break
case '--rtl':
rtl = true
break
case '--ltr':
rtl = false
break
case '-h': case '--help': case '-?':
return help()
default:
versions.push(a)
break
}
}
var options = { loose: loose, includePrerelease: includePrerelease, rtl: rtl }
versions = versions.map(function (v) {
return coerce ? (semver.coerce(v, options) || { version: v }).version : v
}).filter(function (v) {
return semver.valid(v)
})
if (!versions.length) return fail()
if (inc && (versions.length !== 1 || range.length)) { return failInc() }
for (var i = 0, l = range.length; i < l; i++) {
versions = versions.filter(function (v) {
return semver.satisfies(v, range[i], options)
})
if (!versions.length) return fail()
}
return success(versions)
}
function failInc () {
console.error('--inc can only be used on a single version with no range')
fail()
}
function fail () { process.exit(1) }
function success () {
var compare = reverse ? 'rcompare' : 'compare'
versions.sort(function (a, b) {
return semver[compare](a, b, options)
}).map(function (v) {
return semver.clean(v, options)
}).map(function (v) {
return inc ? semver.inc(v, inc, options, identifier) : v
}).forEach(function (v, i, _) { console.log(v) })
}
function help () {
console.log(['SemVer ' + version,
'',
'A JavaScript implementation of the https://semver.org/ specification',
'Copyright Isaac Z. Schlueter',
'',
'Usage: semver [options] <version> [<version> [...]]',
'Prints valid versions sorted by SemVer precedence',
'',
'Options:',
'-r --range <range>',
' Print versions that match the specified range.',
'',
'-i --increment [<level>]',
' Increment a version by the specified level. Level can',
' be one of: major, minor, patch, premajor, preminor,',
" prepatch, or prerelease. Default level is 'patch'.",
' Only one version may be specified.',
'',
'--preid <identifier>',
' Identifier to be used to prefix premajor, preminor,',
' prepatch or prerelease version increments.',
'',
'-l --loose',
' Interpret versions and ranges loosely',
'',
'-p --include-prerelease',
' Always include prerelease versions in range matching',
'',
'-c --coerce',
' Coerce a string into SemVer if possible',
' (does not imply --loose)',
'',
'--rtl',
' Coerce version strings right to left',
'',
'--ltr',
' Coerce version strings left to right (default)',
'',
'Program exits successfully if any valid version satisfies',
'all supplied ranges, and prints all satisfying versions.',
'',
'If no satisfying versions are found, then exits failure.',
'',
'Versions are printed in ascending order, so supplying',
'multiple versions to the utility will just sort them.'
].join('\n'))
}
+66
View File
@@ -0,0 +1,66 @@
{
"_args": [
[
"semver@6.3.0",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "semver@6.3.0",
"_id": "semver@6.3.0",
"_inBundle": false,
"_integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"_location": "/semver",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "semver@6.3.0",
"name": "semver",
"escapedName": "semver",
"rawSpec": "6.3.0",
"saveSpec": null,
"fetchSpec": "6.3.0"
},
"_requiredBy": [
"/@actions/tool-cache",
"/@typescript-eslint/typescript-estree",
"/eslint",
"/istanbul-lib-instrument"
],
"_resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"_spec": "6.3.0",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"bin": {
"semver": "./bin/semver.js"
},
"bugs": {
"url": "https://github.com/npm/node-semver/issues"
},
"description": "The semantic version parser used by npm.",
"devDependencies": {
"tap": "^14.3.1"
},
"files": [
"bin",
"range.bnf",
"semver.js"
],
"homepage": "https://github.com/npm/node-semver#readme",
"license": "ISC",
"main": "semver.js",
"name": "semver",
"repository": {
"type": "git",
"url": "git+https://github.com/npm/node-semver.git"
},
"scripts": {
"postpublish": "git push origin --follow-tags",
"postversion": "npm publish",
"preversion": "npm test",
"test": "tap"
},
"tap": {
"check-coverage": true
},
"version": "6.3.0"
}
+16
View File
@@ -0,0 +1,16 @@
range-set ::= range ( logical-or range ) *
logical-or ::= ( ' ' ) * '||' ( ' ' ) *
range ::= hyphen | simple ( ' ' simple ) * | ''
hyphen ::= partial ' - ' partial
simple ::= primitive | partial | tilde | caret
primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
xr ::= 'x' | 'X' | '*' | nr
nr ::= '0' | [1-9] ( [0-9] ) *
tilde ::= '~' partial
caret ::= '^' partial
qualifier ::= ( '-' pre )? ( '+' build )?
pre ::= parts
build ::= parts
parts ::= part ( '.' part ) *
part ::= nr | [-0-9A-Za-z]+
+1596
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
/.idea
/node_modules
+13
View File
@@ -0,0 +1,13 @@
# Changelog
- 0.0.4 (2016/01/23)
- supported Node v0.12 or later.
- 0.0.3 (2014/01/20)
- fixed package.json
- 0.0.1 (2012/02/18)
- supported Node v0.6.x (0.6.11 or later).
- 0.0.0 (2012/02/11)
- first release.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Koichi Kobayashi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+179
View File
@@ -0,0 +1,179 @@
# node-tunnel - HTTP/HTTPS Agents for tunneling proxies
## Example
```javascript
var tunnel = require('tunnel');
var tunnelingAgent = tunnel.httpsOverHttp({
proxy: {
host: 'localhost',
port: 3128
}
});
var req = https.request({
host: 'example.com',
port: 443,
agent: tunnelingAgent
});
```
## Installation
$ npm install tunnel
## Usages
### HTTP over HTTP tunneling
```javascript
var tunnelingAgent = tunnel.httpOverHttp({
maxSockets: poolSize, // Defaults to 5
proxy: { // Proxy settings
host: proxyHost, // Defaults to 'localhost'
port: proxyPort, // Defaults to 80
localAddress: localAddress, // Local interface if necessary
// Basic authorization for proxy server if necessary
proxyAuth: 'user:password',
// Header fields for proxy server if necessary
headers: {
'User-Agent': 'Node'
}
}
});
var req = http.request({
host: 'example.com',
port: 80,
agent: tunnelingAgent
});
```
### HTTPS over HTTP tunneling
```javascript
var tunnelingAgent = tunnel.httpsOverHttp({
maxSockets: poolSize, // Defaults to 5
// CA for origin server if necessary
ca: [ fs.readFileSync('origin-server-ca.pem')],
// Client certification for origin server if necessary
key: fs.readFileSync('origin-server-key.pem'),
cert: fs.readFileSync('origin-server-cert.pem'),
proxy: { // Proxy settings
host: proxyHost, // Defaults to 'localhost'
port: proxyPort, // Defaults to 80
localAddress: localAddress, // Local interface if necessary
// Basic authorization for proxy server if necessary
proxyAuth: 'user:password',
// Header fields for proxy server if necessary
headers: {
'User-Agent': 'Node'
},
}
});
var req = https.request({
host: 'example.com',
port: 443,
agent: tunnelingAgent
});
```
### HTTP over HTTPS tunneling
```javascript
var tunnelingAgent = tunnel.httpOverHttps({
maxSockets: poolSize, // Defaults to 5
proxy: { // Proxy settings
host: proxyHost, // Defaults to 'localhost'
port: proxyPort, // Defaults to 443
localAddress: localAddress, // Local interface if necessary
// Basic authorization for proxy server if necessary
proxyAuth: 'user:password',
// Header fields for proxy server if necessary
headers: {
'User-Agent': 'Node'
},
// CA for proxy server if necessary
ca: [ fs.readFileSync('origin-server-ca.pem')],
// Server name for verification if necessary
servername: 'example.com',
// Client certification for proxy server if necessary
key: fs.readFileSync('origin-server-key.pem'),
cert: fs.readFileSync('origin-server-cert.pem'),
}
});
var req = http.request({
host: 'example.com',
port: 80,
agent: tunnelingAgent
});
```
### HTTPS over HTTPS tunneling
```javascript
var tunnelingAgent = tunnel.httpsOverHttps({
maxSockets: poolSize, // Defaults to 5
// CA for origin server if necessary
ca: [ fs.readFileSync('origin-server-ca.pem')],
// Client certification for origin server if necessary
key: fs.readFileSync('origin-server-key.pem'),
cert: fs.readFileSync('origin-server-cert.pem'),
proxy: { // Proxy settings
host: proxyHost, // Defaults to 'localhost'
port: proxyPort, // Defaults to 443
localAddress: localAddress, // Local interface if necessary
// Basic authorization for proxy server if necessary
proxyAuth: 'user:password',
// Header fields for proxy server if necessary
headers: {
'User-Agent': 'Node'
}
// CA for proxy server if necessary
ca: [ fs.readFileSync('origin-server-ca.pem')],
// Server name for verification if necessary
servername: 'example.com',
// Client certification for proxy server if necessary
key: fs.readFileSync('origin-server-key.pem'),
cert: fs.readFileSync('origin-server-cert.pem'),
}
});
var req = https.request({
host: 'example.com',
port: 443,
agent: tunnelingAgent
});
```
## CONTRIBUTORS
* [Aleksis Brezas (abresas)](https://github.com/abresas)
## License
Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE) license.
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib/tunnel');
+247
View File
@@ -0,0 +1,247 @@
'use strict';
var net = require('net');
var tls = require('tls');
var http = require('http');
var https = require('https');
var events = require('events');
var assert = require('assert');
var util = require('util');
exports.httpOverHttp = httpOverHttp;
exports.httpsOverHttp = httpsOverHttp;
exports.httpOverHttps = httpOverHttps;
exports.httpsOverHttps = httpsOverHttps;
function httpOverHttp(options) {
var agent = new TunnelingAgent(options);
agent.request = http.request;
return agent;
}
function httpsOverHttp(options) {
var agent = new TunnelingAgent(options);
agent.request = http.request;
agent.createSocket = createSecureSocket;
return agent;
}
function httpOverHttps(options) {
var agent = new TunnelingAgent(options);
agent.request = https.request;
return agent;
}
function httpsOverHttps(options) {
var agent = new TunnelingAgent(options);
agent.request = https.request;
agent.createSocket = createSecureSocket;
return agent;
}
function TunnelingAgent(options) {
var self = this;
self.options = options || {};
self.proxyOptions = self.options.proxy || {};
self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets;
self.requests = [];
self.sockets = [];
self.on('free', function onFree(socket, host, port, localAddress) {
var options = toOptions(host, port, localAddress);
for (var i = 0, len = self.requests.length; i < len; ++i) {
var pending = self.requests[i];
if (pending.host === options.host && pending.port === options.port) {
// Detect the request to connect same origin server,
// reuse the connection.
self.requests.splice(i, 1);
pending.request.onSocket(socket);
return;
}
}
socket.destroy();
self.removeSocket(socket);
});
}
util.inherits(TunnelingAgent, events.EventEmitter);
TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) {
var self = this;
var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress));
if (self.sockets.length >= this.maxSockets) {
// We are over limit so we'll add it to the queue.
self.requests.push(options);
return;
}
// If we are under maxSockets create a new one.
self.createSocket(options, function(socket) {
socket.on('free', onFree);
socket.on('close', onCloseOrRemove);
socket.on('agentRemove', onCloseOrRemove);
req.onSocket(socket);
function onFree() {
self.emit('free', socket, options);
}
function onCloseOrRemove(err) {
self.removeSocket(socket);
socket.removeListener('free', onFree);
socket.removeListener('close', onCloseOrRemove);
socket.removeListener('agentRemove', onCloseOrRemove);
}
});
};
TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
var self = this;
var placeholder = {};
self.sockets.push(placeholder);
var connectOptions = mergeOptions({}, self.proxyOptions, {
method: 'CONNECT',
path: options.host + ':' + options.port,
agent: false
});
if (connectOptions.proxyAuth) {
connectOptions.headers = connectOptions.headers || {};
connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
new Buffer(connectOptions.proxyAuth).toString('base64');
}
debug('making CONNECT request');
var connectReq = self.request(connectOptions);
connectReq.useChunkedEncodingByDefault = false; // for v0.6
connectReq.once('response', onResponse); // for v0.6
connectReq.once('upgrade', onUpgrade); // for v0.6
connectReq.once('connect', onConnect); // for v0.7 or later
connectReq.once('error', onError);
connectReq.end();
function onResponse(res) {
// Very hacky. This is necessary to avoid http-parser leaks.
res.upgrade = true;
}
function onUpgrade(res, socket, head) {
// Hacky.
process.nextTick(function() {
onConnect(res, socket, head);
});
}
function onConnect(res, socket, head) {
connectReq.removeAllListeners();
socket.removeAllListeners();
if (res.statusCode === 200) {
assert.equal(head.length, 0);
debug('tunneling connection has established');
self.sockets[self.sockets.indexOf(placeholder)] = socket;
cb(socket);
} else {
debug('tunneling socket could not be established, statusCode=%d',
res.statusCode);
var error = new Error('tunneling socket could not be established, ' +
'statusCode=' + res.statusCode);
error.code = 'ECONNRESET';
options.request.emit('error', error);
self.removeSocket(placeholder);
}
}
function onError(cause) {
connectReq.removeAllListeners();
debug('tunneling socket could not be established, cause=%s\n',
cause.message, cause.stack);
var error = new Error('tunneling socket could not be established, ' +
'cause=' + cause.message);
error.code = 'ECONNRESET';
options.request.emit('error', error);
self.removeSocket(placeholder);
}
};
TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
var pos = this.sockets.indexOf(socket)
if (pos === -1) {
return;
}
this.sockets.splice(pos, 1);
var pending = this.requests.shift();
if (pending) {
// If we have pending requests and a socket gets closed a new one
// needs to be created to take over in the pool for the one that closed.
this.createSocket(pending, function(socket) {
pending.request.onSocket(socket);
});
}
};
function createSecureSocket(options, cb) {
var self = this;
TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
var hostHeader = options.request.getHeader('host');
var tlsOptions = mergeOptions({}, self.options, {
socket: socket,
servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host
});
// 0 is dummy port for v0.6
var secureSocket = tls.connect(0, tlsOptions);
self.sockets[self.sockets.indexOf(socket)] = secureSocket;
cb(secureSocket);
});
}
function toOptions(host, port, localAddress) {
if (typeof host === 'string') { // since v0.10
return {
host: host,
port: port,
localAddress: localAddress
};
}
return host; // for v0.11 or later
}
function mergeOptions(target) {
for (var i = 1, len = arguments.length; i < len; ++i) {
var overrides = arguments[i];
if (typeof overrides === 'object') {
var keys = Object.keys(overrides);
for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
var k = keys[j];
if (overrides[k] !== undefined) {
target[k] = overrides[k];
}
}
}
}
return target;
}
var debug;
if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
debug = function() {
var args = Array.prototype.slice.call(arguments);
if (typeof args[0] === 'string') {
args[0] = 'TUNNEL: ' + args[0];
} else {
args.unshift('TUNNEL:');
}
console.error.apply(console, args);
}
} else {
debug = function() {};
}
exports.debug = debug; // for test
+67
View File
@@ -0,0 +1,67 @@
{
"_args": [
[
"tunnel@0.0.4",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "tunnel@0.0.4",
"_id": "tunnel@0.0.4",
"_inBundle": false,
"_integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=",
"_location": "/tunnel",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "tunnel@0.0.4",
"name": "tunnel",
"escapedName": "tunnel",
"rawSpec": "0.0.4",
"saveSpec": null,
"fetchSpec": "0.0.4"
},
"_requiredBy": [
"/typed-rest-client"
],
"_resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz",
"_spec": "0.0.4",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"author": {
"name": "Koichi Kobayashi",
"email": "koichik@improvement.jp"
},
"bugs": {
"url": "https://github.com/koichik/node-tunnel/issues"
},
"description": "Node HTTP/HTTPS Agents for tunneling proxies",
"devDependencies": {
"mocha": "*",
"should": "*"
},
"directories": {
"lib": "./lib"
},
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
},
"homepage": "https://github.com/koichik/node-tunnel/",
"keywords": [
"http",
"https",
"agent",
"proxy",
"tunnel"
],
"license": "MIT",
"main": "./index.js",
"name": "tunnel",
"repository": {
"type": "git",
"url": "git+https://github.com/koichik/node-tunnel.git"
},
"scripts": {
"test": "./node_modules/mocha/bin/mocha"
},
"version": "0.0.4"
}
+108
View File
@@ -0,0 +1,108 @@
var http = require('http');
var net = require('net');
var should = require('should');
var tunnel = require('../index');
describe('HTTP over HTTP', function() {
it('should finish without error', function(done) {
var serverPort = 3000;
var proxyPort = 3001;
var poolSize = 3;
var N = 10;
var serverConnect = 0;
var proxyConnect = 0;
var clientConnect = 0;
var server;
var proxy;
var agent;
server = http.createServer(function(req, res) {
tunnel.debug('SERVER: got request');
++serverConnect;
res.writeHead(200);
res.end('Hello' + req.url);
tunnel.debug('SERVER: sending response');
});
server.listen(serverPort, setupProxy);
function setupProxy() {
proxy = http.createServer(function(req, res) {
should.fail();
});
proxy.on('upgrade', onConnect); // for v0.6
proxy.on('connect', onConnect); // for v0.7 or later
function onConnect(req, clientSocket, head) {
tunnel.debug('PROXY: got CONNECT request');
req.method.should.equal('CONNECT');
req.url.should.equal('localhost:' + serverPort);
req.headers.should.not.have.property('transfer-encoding');
req.headers.should.have.property('proxy-authorization',
'Basic ' + new Buffer('user:password').toString('base64'));
++proxyConnect;
tunnel.debug('PROXY: creating a tunnel');
var serverSocket = net.connect(serverPort, function() {
tunnel.debug('PROXY: replying to client CONNECT request');
clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
clientSocket.pipe(serverSocket);
serverSocket.write(head);
serverSocket.pipe(clientSocket);
// workaround, see joyent/node#2524
serverSocket.on('end', function() {
clientSocket.end();
});
});
}
proxy.listen(proxyPort, setupClient);
}
function setupClient() {
agent = tunnel.httpOverHttp({
maxSockets: poolSize,
proxy: {
port: proxyPort,
proxyAuth: 'user:password'
}
});
for (var i = 0; i < N; ++i) {
doClientRequest(i);
}
function doClientRequest(i) {
tunnel.debug('CLIENT: Making HTTP request (%d)', i);
var req = http.get({
port: serverPort,
path: '/' + i,
agent: agent
}, function(res) {
tunnel.debug('CLIENT: got HTTP response (%d)', i);
res.setEncoding('utf8');
res.on('data', function(data) {
data.should.equal('Hello/' + i);
});
res.on('end', function() {
++clientConnect;
if (clientConnect === N) {
proxy.close();
server.close();
}
});
});
}
}
server.on('close', function() {
serverConnect.should.equal(N);
proxyConnect.should.equal(poolSize);
clientConnect.should.equal(N);
agent.sockets.should.be.empty;
agent.requests.should.be.empty;
done();
});
});
});
+130
View File
@@ -0,0 +1,130 @@
var http = require('http');
var https = require('https');
var net = require('net');
var fs = require('fs');
var path = require('path');
var should = require('should');
var tunnel = require('../index');
function readPem(file) {
return fs.readFileSync(path.join('test/keys', file + '.pem'));
}
var proxyKey = readPem('proxy1-key');
var proxyCert = readPem('proxy1-cert');
var proxyCA = readPem('ca2-cert');
var clientKey = readPem('client1-key');
var clientCert = readPem('client1-cert');
var clientCA = readPem('ca3-cert');
describe('HTTP over HTTPS', function() {
it('should finish without error', function(done) {
var serverPort = 3004;
var proxyPort = 3005;
var poolSize = 3;
var N = 10;
var serverConnect = 0;
var proxyConnect = 0;
var clientConnect = 0;
var server;
var proxy;
var agent;
server = http.createServer(function(req, res) {
tunnel.debug('SERVER: got request');
++serverConnect;
res.writeHead(200);
res.end('Hello' + req.url);
tunnel.debug('SERVER: sending response');
});
server.listen(serverPort, setupProxy);
function setupProxy() {
proxy = https.createServer({
key: proxyKey,
cert: proxyCert,
ca: [clientCA],
requestCert: true,
rejectUnauthorized: true
}, function(req, res) {
should.fail();
});
proxy.on('upgrade', onConnect); // for v0.6
proxy.on('connect', onConnect); // for v0.7 or later
function onConnect(req, clientSocket, head) {
tunnel.debug('PROXY: got CONNECT request');
req.method.should.equal('CONNECT');
req.url.should.equal('localhost:' + serverPort);
req.headers.should.not.have.property('transfer-encoding');
++proxyConnect;
tunnel.debug('PROXY: creating a tunnel');
var serverSocket = net.connect(serverPort, function() {
tunnel.debug('PROXY: replying to client CONNECT request');
clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
clientSocket.pipe(serverSocket);
serverSocket.write(head);
serverSocket.pipe(clientSocket);
// workaround, see joyent/node#2524
serverSocket.on('end', function() {
clientSocket.end();
});
});
}
proxy.listen(proxyPort, setupClient);
}
function setupClient() {
agent = tunnel.httpOverHttps({
maxSockets: poolSize,
proxy: {
port: proxyPort,
key: clientKey,
cert: clientCert,
ca: [proxyCA],
rejectUnauthorized: true
}
});
for (var i = 0; i < N; ++i) {
doClientRequest(i);
}
function doClientRequest(i) {
tunnel.debug('CLIENT: Making HTTP request (%d)', i);
var req = http.get({
port: serverPort,
path: '/' + i,
agent: agent
}, function(res) {
tunnel.debug('CLIENT: got HTTP response (%d)', i);
res.setEncoding('utf8');
res.on('data', function(data) {
data.should.equal('Hello/' + i);
});
res.on('end', function() {
++clientConnect;
if (clientConnect === N) {
proxy.close();
server.close();
}
});
});
}
}
server.on('close', function() {
serverConnect.should.equal(N);
proxyConnect.should.equal(poolSize);
clientConnect.should.equal(N);
var name = 'localhost:' + serverPort;
agent.sockets.should.be.empty;
agent.requests.should.be.empty;
done();
});
});
});
+130
View File
@@ -0,0 +1,130 @@
var http = require('http');
var https = require('https');
var net = require('net');
var fs = require('fs');
var path = require('path');
var should = require('should');
var tunnel = require('../index');
function readPem(file) {
return fs.readFileSync(path.join('test/keys', file + '.pem'));
}
var serverKey = readPem('server1-key');
var serverCert = readPem('server1-cert');
var serverCA = readPem('ca1-cert');
var clientKey = readPem('client1-key');
var clientCert = readPem('client1-cert');
var clientCA = readPem('ca3-cert');
describe('HTTPS over HTTP', function() {
it('should finish without error', function(done) {
var serverPort = 3002;
var proxyPort = 3003;
var poolSize = 3;
var N = 10;
var serverConnect = 0;
var proxyConnect = 0;
var clientConnect = 0;
var server;
var proxy;
var agent;
server = https.createServer({
key: serverKey,
cert: serverCert,
ca: [clientCA],
requestCert: true,
rejectUnauthorized: true
}, function(req, res) {
tunnel.debug('SERVER: got request');
++serverConnect;
res.writeHead(200);
res.end('Hello' + req.url);
tunnel.debug('SERVER: sending response');
});
server.listen(serverPort, setupProxy);
function setupProxy() {
proxy = http.createServer(function(req, res) {
should.fail();
});
proxy.on('upgrade', onConnect); // for v0.6
proxy.on('connect', onConnect); // for v0.7 or later
function onConnect(req, clientSocket, head) {
tunnel.debug('PROXY: got CONNECT request');
req.method.should.equal('CONNECT');
req.url.should.equal('localhost:' + serverPort);
req.headers.should.not.have.property('transfer-encoding');
++proxyConnect;
var serverSocket = net.connect(serverPort, function() {
tunnel.debug('PROXY: replying to client CONNECT request');
clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
clientSocket.pipe(serverSocket);
serverSocket.write(head);
serverSocket.pipe(clientSocket);
// workaround, see joyent/node#2524
serverSocket.on('end', function() {
clientSocket.end();
});
});
}
proxy.listen(proxyPort, setupClient);
}
function setupClient() {
agent = tunnel.httpsOverHttp({
maxSockets: poolSize,
key: clientKey,
cert: clientCert,
ca: [serverCA],
rejectUnauthorized: true,
proxy: {
port: proxyPort
}
});
for (var i = 0; i < N; ++i) {
doClientRequest(i);
}
function doClientRequest(i) {
tunnel.debug('CLIENT: Making HTTPS request (%d)', i);
var req = https.get({
port: serverPort,
path: '/' + i,
agent: agent
}, function(res) {
tunnel.debug('CLIENT: got HTTPS response (%d)', i);
res.setEncoding('utf8');
res.on('data', function(data) {
data.should.equal('Hello/' + i);
});
res.on('end', function() {
++clientConnect;
if (clientConnect === N) {
proxy.close();
server.close();
}
});
});
}
}
server.on('close', function() {
serverConnect.should.equal(N);
proxyConnect.should.equal(poolSize);
clientConnect.should.equal(N);
var name = 'localhost:' + serverPort;
agent.sockets.should.be.empty;
agent.requests.should.be.empty;
done();
});
});
});
+261
View File
@@ -0,0 +1,261 @@
var http = require('http');
var https = require('https');
var net = require('net');
var fs = require('fs');
var path = require('path');
var should = require('should');
var tunnel = require('../index');
function readPem(file) {
return fs.readFileSync(path.join('test/keys', file + '.pem'));
}
var serverKey = readPem('server2-key');
var serverCert = readPem('server2-cert');
var serverCA = readPem('ca1-cert');
var proxyKey = readPem('proxy2-key');
var proxyCert = readPem('proxy2-cert');
var proxyCA = readPem('ca2-cert');
var client1Key = readPem('client1-key');
var client1Cert = readPem('client1-cert');
var client1CA = readPem('ca3-cert');
var client2Key = readPem('client2-key');
var client2Cert = readPem('client2-cert');
var client2CA = readPem('ca4-cert');
describe('HTTPS over HTTPS authentication failed', function() {
it('should finish without error', function(done) {
var serverPort = 3008;
var proxyPort = 3009;
var serverConnect = 0;
var proxyConnect = 0;
var clientRequest = 0;
var clientConnect = 0;
var clientError = 0;
var server;
var proxy;
server = https.createServer({
key: serverKey,
cert: serverCert,
ca: [client1CA],
requestCert: true,
rejectUnauthorized: true
}, function(req, res) {
tunnel.debug('SERVER: got request', req.url);
++serverConnect;
req.on('data', function(data) {
});
req.on('end', function() {
res.writeHead(200);
res.end('Hello, ' + serverConnect);
tunnel.debug('SERVER: sending response');
});
req.resume();
});
//server.addContext('server2', {
// key: serverKey,
// cert: serverCert,
// ca: [client1CA],
//});
server.listen(serverPort, setupProxy);
function setupProxy() {
proxy = https.createServer({
key: proxyKey,
cert: proxyCert,
ca: [client2CA],
requestCert: true,
rejectUnauthorized: true
}, function(req, res) {
should.fail();
});
//proxy.addContext('proxy2', {
// key: proxyKey,
// cert: proxyCert,
// ca: [client2CA],
//});
proxy.on('upgrade', onConnect); // for v0.6
proxy.on('connect', onConnect); // for v0.7 or later
function onConnect(req, clientSocket, head) {
req.method.should.equal('CONNECT');
req.url.should.equal('localhost:' + serverPort);
req.headers.should.not.have.property('transfer-encoding');
++proxyConnect;
var serverSocket = net.connect(serverPort, function() {
tunnel.debug('PROXY: replying to client CONNECT request');
clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
clientSocket.pipe(serverSocket);
serverSocket.write(head);
serverSocket.pipe(clientSocket);
// workaround, see #2524
serverSocket.on('end', function() {
clientSocket.end();
});
});
}
proxy.listen(proxyPort, setupClient);
}
function setupClient() {
function doRequest(name, options, host) {
tunnel.debug('CLIENT: Making HTTPS request (%s)', name);
++clientRequest;
var agent = tunnel.httpsOverHttps(options);
var req = https.get({
host: 'localhost',
port: serverPort,
path: '/' + encodeURIComponent(name),
headers: {
host: host ? host : 'localhost',
},
rejectUnauthorized: true,
agent: agent
}, function(res) {
tunnel.debug('CLIENT: got HTTPS response (%s)', name);
++clientConnect;
res.on('data', function(data) {
});
res.on('end', function() {
req.emit('finish');
});
res.resume();
});
req.on('error', function(err) {
tunnel.debug('CLIENT: failed HTTP response (%s)', name, err);
++clientError;
req.emit('finish');
});
req.on('finish', function() {
if (clientConnect + clientError === clientRequest) {
proxy.close();
server.close();
}
});
}
doRequest('no cert origin nor proxy', { // invalid
maxSockets: 1,
ca: [serverCA],
rejectUnauthorized: true,
// no certificate for origin server
proxy: {
port: proxyPort,
ca: [proxyCA],
rejectUnauthorized: true,
headers: {
host: 'proxy2'
}
// no certificate for proxy
}
}, 'server2');
doRequest('no cert proxy', { // invalid
maxSockets: 1,
ca: [serverCA],
rejectUnauthorized: true,
// client certification for origin server
key: client1Key,
cert: client1Cert,
proxy: {
port: proxyPort,
ca: [proxyCA],
rejectUnauthorized: true,
headers: {
host: 'proxy2'
}
// no certificate for proxy
}
}, 'server2');
doRequest('no cert origin', { // invalid
maxSockets: 1,
ca: [serverCA],
rejectUnauthorized: true,
// no certificate for origin server
proxy: {
port: proxyPort,
servername: 'proxy2',
ca: [proxyCA],
rejectUnauthorized: true,
headers: {
host: 'proxy2'
},
// client certification for proxy
key: client2Key,
cert: client2Cert
}
}, 'server2');
doRequest('invalid proxy server name', { // invalid
maxSockets: 1,
ca: [serverCA],
rejectUnauthorized: true,
// client certification for origin server
key: client1Key,
cert: client1Cert,
proxy: {
port: proxyPort,
ca: [proxyCA],
rejectUnauthorized: true,
// client certification for proxy
key: client2Key,
cert: client2Cert,
}
}, 'server2');
doRequest('invalid origin server name', { // invalid
maxSockets: 1,
ca: [serverCA],
rejectUnauthorized: true,
// client certification for origin server
key: client1Key,
cert: client1Cert,
proxy: {
port: proxyPort,
servername: 'proxy2',
ca: [proxyCA],
rejectUnauthorized: true,
headers: {
host: 'proxy2'
},
// client certification for proxy
key: client2Key,
cert: client2Cert
}
});
doRequest('valid', { // valid
maxSockets: 1,
ca: [serverCA],
rejectUnauthorized: true,
// client certification for origin server
key: client1Key,
cert: client1Cert,
proxy: {
port: proxyPort,
servername: 'proxy2',
ca: [proxyCA],
rejectUnauthorized: true,
headers: {
host: 'proxy2'
},
// client certification for proxy
key: client2Key,
cert: client2Cert
}
}, 'server2');
}
server.on('close', function() {
serverConnect.should.equal(1);
proxyConnect.should.equal(3);
clientConnect.should.equal(1);
clientError.should.equal(5);
done();
});
});
});
+146
View File
@@ -0,0 +1,146 @@
var http = require('http');
var https = require('https');
var net = require('net');
var fs = require('fs');
var path = require('path');
var should = require('should');
var tunnel = require('../index.js');
function readPem(file) {
return fs.readFileSync(path.join('test/keys', file + '.pem'));
}
var serverKey = readPem('server1-key');
var serverCert = readPem('server1-cert');
var serverCA = readPem('ca1-cert');
var proxyKey = readPem('proxy1-key');
var proxyCert = readPem('proxy1-cert');
var proxyCA = readPem('ca2-cert');
var client1Key = readPem('client1-key');
var client1Cert = readPem('client1-cert');
var client1CA = readPem('ca3-cert');
var client2Key = readPem('client2-key');
var client2Cert = readPem('client2-cert');
var client2CA = readPem('ca4-cert');
describe('HTTPS over HTTPS', function() {
it('should finish without error', function(done) {
var serverPort = 3006;
var proxyPort = 3007;
var poolSize = 3;
var N = 5;
var serverConnect = 0;
var proxyConnect = 0;
var clientConnect = 0;
var server;
var proxy;
var agent;
server = https.createServer({
key: serverKey,
cert: serverCert,
ca: [client1CA],
requestCert: true,
rejectUnauthorized: true
}, function(req, res) {
tunnel.debug('SERVER: got request');
++serverConnect;
res.writeHead(200);
res.end('Hello' + req.url);
tunnel.debug('SERVER: sending response');
});
server.listen(serverPort, setupProxy);
function setupProxy() {
proxy = https.createServer({
key: proxyKey,
cert: proxyCert,
ca: [client2CA],
requestCert: true,
rejectUnauthorized: true
}, function(req, res) {
should.fail();
});
proxy.on('upgrade', onConnect); // for v0.6
proxy.on('connect', onConnect); // for v0.7 or later
function onConnect(req, clientSocket, head) {
tunnel.debug('PROXY: got CONNECT request');
req.method.should.equal('CONNECT');
req.url.should.equal('localhost:' + serverPort);
req.headers.should.not.have.property('transfer-encoding');
++proxyConnect;
var serverSocket = net.connect(serverPort, function() {
tunnel.debug('PROXY: replying to client CONNECT request');
clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
clientSocket.pipe(serverSocket);
serverSocket.write(head);
serverSocket.pipe(clientSocket);
// workaround, see joyent/node#2524
serverSocket.on('end', function() {
clientSocket.end();
});
});
}
proxy.listen(proxyPort, setupClient);
}
function setupClient() {
agent = tunnel.httpsOverHttps({
maxSockets: poolSize,
// client certification for origin server
key: client1Key,
cert: client1Cert,
ca: [serverCA],
rejectUnauthroized: true,
proxy: {
port: proxyPort,
// client certification for proxy
key: client2Key,
cert: client2Cert,
ca: [proxyCA],
rejectUnauthroized: true
}
});
for (var i = 0; i < N; ++i) {
doClientRequest(i);
}
function doClientRequest(i) {
tunnel.debug('CLIENT: Making HTTPS request (%d)', i);
var req = https.get({
port: serverPort,
path: '/' + i,
agent: agent
}, function(res) {
tunnel.debug('CLIENT: got HTTPS response (%d)', i);
res.setEncoding('utf8');
res.on('data', function(data) {
data.should.equal('Hello/' + i);
});
res.on('end', function() {
++clientConnect;
if (clientConnect === N) {
proxy.close();
server.close();
}
});
});
}
}
server.on('close', function() {
serverConnect.should.equal(N);
proxyConnect.should.equal(poolSize);
clientConnect.should.equal(N);
var name = 'localhost:' + serverPort;
agent.sockets.should.be.empty;
agent.requests.should.be.empty;
done();
});
});
});
+157
View File
@@ -0,0 +1,157 @@
all: server1-cert.pem server2-cert.pem proxy1-cert.pem proxy2-cert.pem client1-cert.pem client2-cert.pem
#
# Create Certificate Authority: ca1
# ('password' is used for the CA password.)
#
ca1-cert.pem: ca1.cnf
openssl req -new -x509 -days 9999 -config ca1.cnf -keyout ca1-key.pem -out ca1-cert.pem
#
# Create Certificate Authority: ca2
# ('password' is used for the CA password.)
#
ca2-cert.pem: ca2.cnf
openssl req -new -x509 -days 9999 -config ca2.cnf -keyout ca2-key.pem -out ca2-cert.pem
#
# Create Certificate Authority: ca3
# ('password' is used for the CA password.)
#
ca3-cert.pem: ca3.cnf
openssl req -new -x509 -days 9999 -config ca3.cnf -keyout ca3-key.pem -out ca3-cert.pem
#
# Create Certificate Authority: ca4
# ('password' is used for the CA password.)
#
ca4-cert.pem: ca4.cnf
openssl req -new -x509 -days 9999 -config ca4.cnf -keyout ca4-key.pem -out ca4-cert.pem
#
# server1 is signed by ca1.
#
server1-key.pem:
openssl genrsa -out server1-key.pem 1024
server1-csr.pem: server1.cnf server1-key.pem
openssl req -new -config server1.cnf -key server1-key.pem -out server1-csr.pem
server1-cert.pem: server1-csr.pem ca1-cert.pem ca1-key.pem
openssl x509 -req \
-days 9999 \
-passin "pass:password" \
-in server1-csr.pem \
-CA ca1-cert.pem \
-CAkey ca1-key.pem \
-CAcreateserial \
-out server1-cert.pem
#
# server2 is signed by ca1.
#
server2-key.pem:
openssl genrsa -out server2-key.pem 1024
server2-csr.pem: server2.cnf server2-key.pem
openssl req -new -config server2.cnf -key server2-key.pem -out server2-csr.pem
server2-cert.pem: server2-csr.pem ca1-cert.pem ca1-key.pem
openssl x509 -req \
-days 9999 \
-passin "pass:password" \
-in server2-csr.pem \
-CA ca1-cert.pem \
-CAkey ca1-key.pem \
-CAcreateserial \
-out server2-cert.pem
server2-verify: server2-cert.pem ca1-cert.pem
openssl verify -CAfile ca1-cert.pem server2-cert.pem
#
# proxy1 is signed by ca2.
#
proxy1-key.pem:
openssl genrsa -out proxy1-key.pem 1024
proxy1-csr.pem: proxy1.cnf proxy1-key.pem
openssl req -new -config proxy1.cnf -key proxy1-key.pem -out proxy1-csr.pem
proxy1-cert.pem: proxy1-csr.pem ca2-cert.pem ca2-key.pem
openssl x509 -req \
-days 9999 \
-passin "pass:password" \
-in proxy1-csr.pem \
-CA ca2-cert.pem \
-CAkey ca2-key.pem \
-CAcreateserial \
-out proxy1-cert.pem
#
# proxy2 is signed by ca2.
#
proxy2-key.pem:
openssl genrsa -out proxy2-key.pem 1024
proxy2-csr.pem: proxy2.cnf proxy2-key.pem
openssl req -new -config proxy2.cnf -key proxy2-key.pem -out proxy2-csr.pem
proxy2-cert.pem: proxy2-csr.pem ca2-cert.pem ca2-key.pem
openssl x509 -req \
-days 9999 \
-passin "pass:password" \
-in proxy2-csr.pem \
-CA ca2-cert.pem \
-CAkey ca2-key.pem \
-CAcreateserial \
-out proxy2-cert.pem
proxy2-verify: proxy2-cert.pem ca2-cert.pem
openssl verify -CAfile ca2-cert.pem proxy2-cert.pem
#
# client1 is signed by ca3.
#
client1-key.pem:
openssl genrsa -out client1-key.pem 1024
client1-csr.pem: client1.cnf client1-key.pem
openssl req -new -config client1.cnf -key client1-key.pem -out client1-csr.pem
client1-cert.pem: client1-csr.pem ca3-cert.pem ca3-key.pem
openssl x509 -req \
-days 9999 \
-passin "pass:password" \
-in client1-csr.pem \
-CA ca3-cert.pem \
-CAkey ca3-key.pem \
-CAcreateserial \
-out client1-cert.pem
#
# client2 is signed by ca4.
#
client2-key.pem:
openssl genrsa -out client2-key.pem 1024
client2-csr.pem: client2.cnf client2-key.pem
openssl req -new -config client2.cnf -key client2-key.pem -out client2-csr.pem
client2-cert.pem: client2-csr.pem ca4-cert.pem ca4-key.pem
openssl x509 -req \
-days 9999 \
-passin "pass:password" \
-in client2-csr.pem \
-CA ca4-cert.pem \
-CAkey ca4-key.pem \
-CAcreateserial \
-out client2-cert.pem
clean:
rm -f *.pem *.srl
test: client-verify server2-verify proxy1-verify proxy2-verify client-verify
+14
View File
@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICKjCCAZMCCQDQ8o4kHKdCPDANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA
dGlueWNsb3Vkcy5vcmcwHhcNMTEwMzE0MTgyOTEyWhcNMzgwNzI5MTgyOTEyWjB9
MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK
EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MTEgMB4G
CSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwXDANBgkqhkiG9w0BAQEFAANL
ADBIAkEAnzpAqcoXZxWJz/WFK7BXwD23jlREyG11x7gkydteHvn6PrVBbB5yfu6c
bk8w3/Ar608AcyMQ9vHjkLQKH7cjEQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAKha
HqjCfTIut+m/idKy3AoFh48tBHo3p9Nl5uBjQJmahKdZAaiksL24Pl+NzPQ8LIU+
FyDHFp6OeJKN6HzZ72Bh9wpBVu6Uj1hwhZhincyTXT80wtSI/BoUAW8Ls2kwPdus
64LsJhhxqj2m4vPKNRbHB2QxnNrGi30CUf3kt3Ia
-----END CERTIFICATE-----
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBXTCCAQcCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH
EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD
EwZhZ2VudDExIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMFwwDQYJ
KoZIhvcNAQEBBQADSwAwSAJBAJ86QKnKF2cVic/1hSuwV8A9t45URMhtdce4JMnb
Xh75+j61QWwecn7unG5PMN/wK+tPAHMjEPbx45C0Ch+3IxECAwEAAaAlMCMGCSqG
SIb3DQEJBzEWExRBIGNoYWxsZW5nZSBwYXNzd29yZDANBgkqhkiG9w0BAQUFAANB
AF+AfG64hNyYHum46m6i7RgnUBrJSOynGjs23TekV4he3QdMSAAPPqbll8W14+y3
vOo7/yQ2v2uTqxCjakUNPPs=
-----END CERTIFICATE REQUEST-----
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAJ86QKnKF2cVic/1hSuwV8A9t45URMhtdce4JMnbXh75+j61QWwe
cn7unG5PMN/wK+tPAHMjEPbx45C0Ch+3IxECAwEAAQJBAI2cU1IuR+4IO87WPyAB
76kruoo87AeNQkjjvuQ/00+b/6IS45mcEP5Kw0NukbqBhIw2di9uQ9J51DJ/ZfQr
+YECIQDUHaN3ZjIdJ7/w8Yq9Zzz+3kY2F/xEz6e4ftOFW8bY2QIhAMAref+WYckC
oECgOLAvAxB1lI4j7oCbAaawfxKdnPj5AiEAi95rXx09aGpAsBGmSdScrPdG1v6j
83/2ebrvoZ1uFqkCIB0AssnrRVjUB6GZTNTyU3ERfdkx/RX1zvr8WkFR/lXpAiB7
cUZ1i8ZkZrPrdVgw2cb28UJM7qZHQnXcMHTXFFvxeQ==
-----END RSA PRIVATE KEY-----
+19
View File
@@ -0,0 +1,19 @@
[ req ]
default_bits = 1024
days = 999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
[ req_distinguished_name ]
C = US
ST = CA
L = SF
O = Joyent
OU = Node.js
CN = agent1
emailAddress = ry@tinyclouds.org
[ req_attributes ]
challengePassword = A challenge password
+13
View File
@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB7DCCAZYCCQC7gs0MDNn6MTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJV
UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
BgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MjEgMB4GCSqGSIb3DQEJARYR
cnlAdGlueWNsb3Vkcy5vcmcwHhcNMTEwMzE0MTgyOTEyWhcNMzgwNzI5MTgyOTEy
WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYD
VQQKEwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MjEg
MB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwXDANBgkqhkiG9w0BAQEF
AANLADBIAkEAyXb8FrRdKbhrKLgLSsn61i1C7w7fVVVd7OQsmV/7p9WB2lWFiDlC
WKGU9SiIz/A6wNZDUAuc2E+VwtpCT561AQIDAQABMA0GCSqGSIb3DQEBBQUAA0EA
C8HzpuNhFLCI3A5KkBS5zHAQax6TFUOhbpBCR0aTDbJ6F1liDTK1lmU/BjvPoj+9
1LHwrmh29rK8kBPEjmymCQ==
-----END CERTIFICATE-----
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBXTCCAQcCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH
EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD
EwZhZ2VudDIxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMFwwDQYJ
KoZIhvcNAQEBBQADSwAwSAJBAMl2/Ba0XSm4ayi4C0rJ+tYtQu8O31VVXezkLJlf
+6fVgdpVhYg5QlihlPUoiM/wOsDWQ1ALnNhPlcLaQk+etQECAwEAAaAlMCMGCSqG
SIb3DQEJBzEWExRBIGNoYWxsZW5nZSBwYXNzd29yZDANBgkqhkiG9w0BAQUFAANB
AJnll2pt5l0pzskQSpjjLVTlFDFmJr/AZ3UK8v0WxBjYjCe5Jx4YehkChpxIyDUm
U3J9q9MDUf0+Y2+EGkssFfk=
-----END CERTIFICATE REQUEST-----
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAMl2/Ba0XSm4ayi4C0rJ+tYtQu8O31VVXezkLJlf+6fVgdpVhYg5
QlihlPUoiM/wOsDWQ1ALnNhPlcLaQk+etQECAwEAAQJBAMT6Bf34+UHKY1ObpsbH
9u2jsVblFq1rWvs8GPMY6oertzvwm3DpuSUp7PTgOB1nLTLYtCERbQ4ovtN8tn3p
OHUCIQDzIEGsoCr5vlxXvy2zJwu+fxYuhTZWMVuo1397L0VyhwIhANQh+yzqUgaf
WRtSB4T2W7ADtJI35ET61jKBty3CqJY3AiAIwju7dVW3A5WeD6Qc1SZGKZvp9yCb
AFI2BfVwwaY11wIgXF3PeGcvACMyMWsuSv7aPXHfliswAbkWuzcwA4TW01ECIGWa
cgsDvVFxmfM5NPSuT/UDTa6R5BFISB5ea0N0AR3I
-----END RSA PRIVATE KEY-----
+19
View File
@@ -0,0 +1,19 @@
[ req ]
default_bits = 1024
days = 999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
[ req_distinguished_name ]
C = US
ST = CA
L = SF
O = Joyent
OU = Node.js
CN = agent2
emailAddress = ry@tinyclouds.org
[ req_attributes ]
challengePassword = A challenge password
+14
View File
@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICKjCCAZMCCQCDBr594bsJmTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMjEgMB4GCSqGSIb3DQEJARYRcnlA
dGlueWNsb3Vkcy5vcmcwHhcNMTEwMzE0MTgyOTEyWhcNMzgwNzI5MTgyOTEyWjB9
MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK
EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MzEgMB4G
CSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwXDANBgkqhkiG9w0BAQEFAANL
ADBIAkEAtlNDZ+bHeBI0B2gD/IWqA7Aq1hwsnS4+XpnLesjTQcL2JwFFpkR0oWrw
yjrYhCogi7c5gjKrLZF1d2JD5JgHgQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAJoK
bXwsImk7vJz9649yrmsXwnuGbEKVYMvqcGyjaZNP9lYEG41y5CeRzxhWy2rlYdhE
f2nqE2lg75oJP7LQqfQY7aCqwahM3q/GQbsfKVCGjF7TVyq9TQzd8iW+FEJIQzSE
3aN85hR67+3VAXeSzmkGSVBO2m1SJIug4qftIkc2
-----END CERTIFICATE-----
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBXTCCAQcCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH
EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD
EwZhZ2VudDMxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMFwwDQYJ
KoZIhvcNAQEBBQADSwAwSAJBALZTQ2fmx3gSNAdoA/yFqgOwKtYcLJ0uPl6Zy3rI
00HC9icBRaZEdKFq8Mo62IQqIIu3OYIyqy2RdXdiQ+SYB4ECAwEAAaAlMCMGCSqG
SIb3DQEJBzEWExRBIGNoYWxsZW5nZSBwYXNzd29yZDANBgkqhkiG9w0BAQUFAANB
AEGo76iH+a8pnE+RWQT+wg9/BL+iIuqrcFXLs0rbGonqderrwXAe15ODwql/Bfu3
zgMt8ooTsgMPcMX9EgmubEM=
-----END CERTIFICATE REQUEST-----
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBALZTQ2fmx3gSNAdoA/yFqgOwKtYcLJ0uPl6Zy3rI00HC9icBRaZE
dKFq8Mo62IQqIIu3OYIyqy2RdXdiQ+SYB4ECAwEAAQJAIk+G9s2SKgFa8y3a2jGZ
LfqABSzmJGooaIsOpLuYLd6eCC31XUDlT4rPVGRhysKQCQ4+NMjgdnj9ZqNnvXY/
RQIhAOgbdltr3Ey2hy7RuDW5rmOeJTuVqCrZ7QI8ifyCEbYTAiEAyRfvWSvvASeP
kZTMUhATRUpuyDQW+058NE0oJSinTpsCIQCR/FPhBGI3TcaQyA9Ym0T4GwvIAkUX
TqInefRAAX8qSQIgZVJPAdIWGbHSL9sWW97HpukLCorcbYEtKbkamiZyrjMCIQCX
lX76ttkeId5OsJGQcF67eFMMr2UGZ1WMf6M39lCYHQ==
-----END RSA PRIVATE KEY-----

Some files were not shown because too many files have changed in this diff Show More