Compare commits

...

93 Commits

Author SHA1 Message Date
Yang c56210bc6a Merge branch 'main' into release/v2
* main:
  Prepare for release 2.11.1.
  Update npm packages.
  Bump @actions/core from 1.2.4 to 1.2.6
  Update to gradle 6.7-rc-1 for test fixture. Build with Java 15 on CI.
  Improve GitHub Actions Gradle cache.
  Add square/radiography to adopter list
  Add cashapp/copper to adapter list
  SDK Build tools 30.0.2.
  Update fixture dependencies.
  Gradle 6.6.
  Update README.md
  Update packages.
  Build tools 30.0.1.
  Use fs for writing licence files.
  Update cmdline-tools to 2.1.
2020-10-08 12:44:58 +11:00
Yang 04a32d7ac6 Prepare for release 2.11.1. 2020-10-08 12:20:45 +11:00
Yang 1eebfdbd84 Update npm packages. 2020-10-08 12:14:51 +11:00
dependabot[bot] 47cc04b42b Bump @actions/core from 1.2.4 to 1.2.6
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.2.4 to 1.2.6.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-02 03:38:16 +10:00
Yang dd0984ca1b Update to gradle 6.7-rc-1 for test fixture. Build with Java 15 on CI. 2020-09-20 18:50:55 +10:00
Yang fef056dbdf Improve GitHub Actions Gradle cache. 2020-09-03 14:55:52 +10:00
Yang 31e4885c24 Add square/radiography to adopter list 2020-09-02 13:05:03 +10:00
Yang a59d5e6807 Add cashapp/copper to adapter list 2020-08-16 19:13:51 +10:00
Yang 8feb8a564a SDK Build tools 30.0.2. 2020-08-15 15:00:23 +10:00
Yang d677dca18c Update fixture dependencies. 2020-08-15 00:01:17 +10:00
Yang d23e781d9a Gradle 6.6. 2020-08-11 10:34:30 +10:00
Vinay Shenoy 29a8a60dbb Update README.md
We use this action for running instrumented tests in CI. Thanks for the great work!
2020-07-28 22:24:22 +10:00
Yang 327f6f1c1f Update packages. 2020-07-18 19:43:39 +10:00
Yang 1dc0441870 Build tools 30.0.1. 2020-07-09 19:49:16 +10:00
Yang fb3c06d709 Use fs for writing licence files. 2020-07-03 16:14:01 +10:00
Yang e22ecdedee Update cmdline-tools to 2.1. 2020-06-24 23:39:11 +10:00
Yang 4a8a9af74c Merge branch 'main' into release/v2
* main:
  Prepare for release 2.11.0.
  Support running multiple actions sequentially in a single job.
  Add RxBinding to adopter list.
  Rename master branch to main.
2020-06-20 19:53:32 +10:00
Yang a72b5eb6c0 Prepare for release 2.11.0. 2020-06-20 19:52:48 +10:00
Yang b39f1a9f8b Support running multiple actions sequentially in a single job. 2020-06-20 19:48:18 +10:00
Yang a4d07e10d7 Add RxBinding to adopter list. 2020-06-19 14:46:44 +10:00
Yang 6f6ca5ebc6 Rename master branch to main. 2020-06-18 19:26:07 +10:00
Yang b62c970881 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.10.0.
  Support API 30 system images and add API 30 to workflow. Update build tools to 30.0.0. Update test projec’s compileSdk and targetSdk to API 30.
2020-06-12 19:51:10 +10:00
Yang 9ef7536c4b Prepare for release 2.10.0. 2020-06-12 19:50:37 +10:00
Yang 8d0ce4c94b Support API 30 system images and add API 30 to workflow. Update build tools to 30.0.0. Update test projec’s compileSdk and targetSdk to API 30. 2020-06-12 19:44:21 +10:00
Yang e93c997dca Merge branch 'master' into release/v2
* master:
  Prepare for release 2.9.0.
  Update test fixture dependencies.
  SDK Command-line Tools 2.0.
  Add information on running on Linux to README.md.
  Fix doc.
  Mention ci-matters repo in adopters list
2020-05-29 23:10:58 +10:00
Yang fc17953a7f Prepare for release 2.9.0. 2020-05-29 23:10:13 +10:00
Yang d122d1159d Update test fixture dependencies. 2020-05-29 21:48:25 +10:00
Yang 736093576f SDK Command-line Tools 2.0. 2020-05-29 21:48:25 +10:00
Yang a06469d17c Add information on running on Linux to README.md. 2020-05-21 18:11:25 +10:00
Yang 3609b3300b Fix doc. 2020-05-10 01:07:23 +10:00
vgaidarji 9b15ac23ac Mention ci-matters repo in adopters list 2020-05-09 21:41:51 +10:00
Yang 4f4106d3fc Merge branch 'master' into release/v2
* master:
  Prepare for release 2.8.0.
  Add support for specifying a custom name used for creating AVD.
  Fix typo
  Add list of projects using android-emulator-runner.
  Bump kotlin and AGP for test fixture project.
2020-05-01 20:18:58 +10:00
Yang 4df9257dba Prepare for release 2.8.0. 2020-05-01 20:18:07 +10:00
Yang f5435af41b Add support for specifying a custom name used for creating AVD. 2020-05-01 20:11:25 +10:00
Yang fdd35a3667 Fix typo 2020-04-25 18:52:46 +10:00
Yang 2513ea5ecf Add list of projects using android-emulator-runner. 2020-04-25 18:17:11 +10:00
Yang c0431f2812 Bump kotlin and AGP for test fixture project. 2020-04-14 23:57:49 +10:00
Yang 2f678a7026 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.7.0.
  Security audit.
  Support specifying versions of NDK and CMake to install.
2020-04-11 21:52:57 +10:00
Yang 33176ea112 Prepare for release 2.7.0. 2020-04-11 21:50:47 +10:00
Yang 8972f964c9 Security audit. 2020-04-11 21:48:08 +10:00
Yang 438dce7110 Support specifying versions of NDK and CMake to install. 2020-04-11 21:02:42 +10:00
Yang Chen e4eb880655 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.6.2.
  Add gradle caching.
  Use correct cli-tools binary for mac.
  Remove API 28 from workflow.
2020-04-03 00:13:02 +11:00
Yang Chen 61dd774a5b Prepare for release 2.6.2. 2020-04-03 00:12:04 +11:00
Yang Chen 8c62a78107 Add gradle caching. 2020-04-02 23:43:06 +11:00
Yang Chen 0d53ec8e43 Use correct cli-tools binary for mac. 2020-04-02 22:56:56 +11:00
Yang Chen 730757f124 Remove API 28 from workflow. 2020-04-01 09:37:55 +11:00
Yang Chen a3b0e6d6d8 Update node_modules. 2020-04-01 00:56:37 +11:00
Yang Chen 0159db621d Merge branch 'master' into release/v2
* master:
  Prepare for release 2.6.1.
  Fix sdk license issue with API 28+ system images on Linux.
2020-04-01 00:54:08 +11:00
Yang Chen 22965df722 Prepare for release 2.6.1. 2020-04-01 00:51:14 +11:00
Yang Chen 49a3d042e9 Fix sdk license issue with API 28+ system images on Linux. 2020-04-01 00:38:58 +11:00
Yang Chen 769ff281b3 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.6.0.
  Update packages.
  Add information re. hardware acceleration for linux support.
  Support running on linux without hardware accleration.
  Gradle 6.3.
  Gradle 6.3-rc-4.
  Update workflow to use Java 14.
  Update npm packages.
  Bump acorn from 5.7.3 to 5.7.4
2020-03-28 15:00:56 +11:00
Yang Chen 0d966550cf Prepare for release 2.6.0. 2020-03-28 14:59:20 +11:00
Yang Chen eb9e069f3f Update packages. 2020-03-28 14:10:50 +11:00
Yang Chen 0ac3f34493 Add information re. hardware acceleration for linux support. 2020-03-28 14:10:50 +11:00
Yang Chen 345ace6178 Support running on linux without hardware accleration. 2020-03-28 14:10:50 +11:00
Yang Chen 490f28e17c Gradle 6.3. 2020-03-25 23:23:07 +11:00
Yang Chen fc08a40bf0 Gradle 6.3-rc-4. 2020-03-25 23:23:07 +11:00
Yang Chen 75464a704b Update workflow to use Java 14. 2020-03-25 23:23:07 +11:00
Yang Chen de92fdaa03 Update npm packages. 2020-03-17 21:16:57 +11:00
dependabot[bot] 47063ae93b Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-17 20:55:34 +11:00
Yang Chen 6bb3965c96 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.5.0.
  Add support for API 15-19 system images.
  Use new cmdline-tools with support for running sdkmanager and avdmanager with Java 8+.
  Update test-fixture dependencies. Drop minSdkVersion to 15.
  Update to AGP 3.6.0 and Gradle 6.2.1 for test-fixture project.
2020-03-05 14:55:36 +11:00
Yang Chen da846ad45b Prepare for release 2.5.0. 2020-03-05 14:54:33 +11:00
Yang Chen ede514c8c2 Add support for API 15-19 system images. 2020-03-05 14:50:59 +11:00
Yang Chen 4a949058e2 Use new cmdline-tools with support for running sdkmanager and avdmanager with Java 8+. 2020-03-05 14:34:19 +11:00
Yang Chen e1015a487b Update test-fixture dependencies. Drop minSdkVersion to 15. 2020-03-05 14:34:19 +11:00
Yang Chen 883b8114b1 Update to AGP 3.6.0 and Gradle 6.2.1 for test-fixture project. 2020-02-26 18:33:53 +11:00
Yang Chen 2754675516 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.4.0.
  Add support for setting a custom working-directory.
  Fix typo
2020-02-15 18:52:13 +11:00
Yang Chen aaa1121ed1 Prepare for release 2.4.0. 2020-02-15 18:51:35 +11:00
Yang Chen ff311e8a7d Add support for setting a custom working-directory. 2020-02-15 18:29:17 +11:00
Ricky Ng-Adam d0fa6c63f8 Fix typo 2020-02-15 18:08:40 +11:00
Yang Chen bdf1f83901 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.3.2.
  Fix escaped environment variables in script.
2020-02-04 12:28:52 +11:00
Yang Chen d3a226f97f Prepare for release 2.3.2. 2020-02-04 12:27:56 +11:00
Yang Chen 4082c705ad Fix escaped environment variables in script. 2020-02-04 12:25:35 +11:00
Yang Chen 5dd12aaed5 Merge branch 'master' into release/v2
* master:
  Prepare for release 2.3.1.
  Update Gradle to 6.1.1 for test-fixture.
  Build tools 29.0.3.
  Validate Gradle Wrapper.
2020-01-31 11:47:56 +11:00
Yang Chen e53c6d9838 Prepare for release 2.3.1. 2020-01-31 11:47:03 +11:00
Yang Chen cb6efbb995 Update Gradle to 6.1.1 for test-fixture. 2020-01-31 11:43:15 +11:00
Yang Chen 97f77e3305 Build tools 29.0.3. 2020-01-31 11:43:15 +11:00
Yang Chen 128c6a83e1 Validate Gradle Wrapper. 2020-01-27 19:48:34 +11:00
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 1162b6cd2f Prepare for release 2.3.0. 2020-01-20 03:56:38 +11:00
Yang Chen e756009f0d Force use Java 8 for sdkmanager and avdmanager. 2020-01-20 03:52:46 +11:00
Yang Chen 2c5dfd9240 Update checkout action to v2. 2019-12-15 17:05:45 +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 86385e08b6 Prepare for release 2.2.0. 2019-12-15 04:29:15 +11:00
Yang Chen e90c3c1695 Stop pinning emulator build in workflow. 2019-12-15 04:27:24 +11:00
Yang Chen 12eba85b00 Add support for pinning specific emulator build. 2019-12-15 04:27:24 +11:00
Yang Chen 37737c6a8e Explicitly set GPU mode to swiftshader_indirect. 2019-12-15 04:27:24 +11:00
Yang Chen c03965e565 Fix script execution order. Run test fixture in workflow. 2019-12-15 02:15:41 +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 269b6dcc82 Prepare for release 2.1.0. 2019-12-14 00:34:56 +11:00
Yang Chen 9739504a54 Support multi-line script. 2019-12-14 00:30:11 +11:00
ychescale9 ae2123e35c Skip workflow for markdown file changes. 2019-12-08 11:44:42 +11:00
ychescale9 a832f214fc Reference Article 2019-12-08 11:39:41 +11:00
90 changed files with 8925 additions and 2720 deletions
+49 -11
View File
@@ -3,20 +3,42 @@ on:
pull_request:
push:
branches:
- master
- main
paths-ignore:
- '**.md'
jobs:
test:
runs-on: macOS-latest
timeout-minutes: 10
runs-on: ${{ matrix.os }}
env:
JAVA_TOOL_OPTIONS: -Xmx4g
timeout-minutes: 15
strategy:
matrix:
api-level: [21, 23, 29]
os: [macos-latest, ubuntu-latest]
api-level: [16, 23, 29]
target: [default, google_apis]
exclude:
- os: ubuntu-latest
api-level: 23
- os: ubuntu-latest
api-level: 29
- target: google_apis
api-level: 16
- target: google_apis
api-level: 23
- target: google_apis
api-level: 29
include:
- os: macos-latest
api-level: 30
target: google_apis
steps:
- name: checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
uses: actions/checkout@v2
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: build, test and lint
run: |
@@ -25,13 +47,29 @@ jobs:
npm run lint
npm test
- name: Java 15
uses: actions/setup-java@v1
with:
java-version: 15
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
- name: run action
uses: ./
with:
api-level: ${{ matrix.api-level }}
target: google_apis
arch: x86_64
target: ${{ matrix.target }}
arch: x86
profile: Nexus 6
emulator-options: -no-window -no-snapshot -noaudio -no-boot-anim -camera-back emulated
avd-name: test
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: adb devices -l
working-directory: ./test-fixture/
script: |
echo $GITHUB_REPOSITORY
adb devices
./gradlew help
./gradlew connectedDebugAndroidTest
+69
View File
@@ -1,5 +1,74 @@
# Change Log
## 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.
## v2.2.0
* Fixed an issue where emulator is killed prematurely.
* Added `-gpu swiftshader_indirect` to default `launch-options`.
* Added support for pinning a specific `emulator-build` - e.g. `6061023` for emulator **v29.3.0.0**.
## v2.1.0
* Added support for multi-line script.
## v2.0.0
* Added action input `emulator-options` for providing command-line options used when launching the emulator. Default value is `-no-window -no-snapshot -noaudio -no-boot-anim`.
+68 -16
View File
@@ -4,9 +4,9 @@
<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 Android Emulators on macOS virtual machines.
A GitHub Action for installing, configuring and running hardware-accelerated 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 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.
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).
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.
@@ -22,19 +22,17 @@ This action automates the process by doing the following:
## Usage
Note that this action must be run on a **macOS** VM, e.g. `macOS-latest` or `macOS-10.14`.
It is recommended to run this action on a **macOS** VM, e.g. `macos-latest` or `macos-10.15` to take advantage of hardware accleration support provided by **HAXM**.
A workflow that uses **android-emulator-runner** to run your instrumented tests on **API 29**:
```
jobs:
test:
runs-on: macOS-latest
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
uses: actions/checkout@v2
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
@@ -48,16 +46,14 @@ We can also leverage GitHub Actions's build matrix to test across multiple confi
```
jobs:
test:
runs-on: macOS-latest
runs-on: macos-latest
strategy:
matrix:
api-level: [21, 23, 29]
target: [default, google_apis]
steps:
- name: checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
uses: actions/checkout@v2
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
@@ -69,16 +65,72 @@ jobs:
script: ./gradlew connectedCheck
```
If you need specific versions of **NDK** and **CMake** installed:
```
jobs:
test:
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v2
- 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
```
## Configurations
| | **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**. |
| `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 15**. |
| `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-snapshot -camera-back emulated`. |
| `arch` | Optional | `x86` | CPU architecture of the system image - `x86` or `x86_64`. Note that `x86_64` image is only available for API 21+. |
| `profile` | Optional | N/A | Hardware profile used for creating the AVD - e.g. `Nexus 6`. For a list of all profiles available, run `avdmanager list` and refer to the results under "Available Android Virtual Devices". |
| `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. |
| `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`. |
| `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. |
| `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` |
| `script` | Required | N/A | Custom script to run - e.g. to run Android instrumented tests on the emulator: `./gradlew connectedCheck` |
Default `emulator-options`: `-no-window -no-snapshot -noaudio -no-boot-anim`.
Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`.
## Can I use this action on Linux VMs?
The short answer is yes but it's expected to be a much worse experience (on some newer API levels it might not work at all) than running it on macOS.
For a longer answer please refer to [this issue](https://github.com/ReactiveCircus/android-emulator-runner/issues/46).
## 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](https://github.com/square/workflow/blob/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/blob/master/.github/workflows/ci.yaml)
- [ReactiveCircus/streamlined](https://github.com/ReactiveCircus/streamlined/tree/main/.github/workflows)
- [ReactiveCircus/FlowBinding](https://github.com/ReactiveCircus/FlowBinding)
- [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/)
- [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)
- [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)
If you are using **Android Emulator Runner** and want your project included in the list, please feel free to create an issue or open a pull request.
+3 -3
View File
@@ -4,7 +4,7 @@ Refer to the [recommendeations for versioning and releasing actions](https://git
## New major release
- From `master` branch, run `npm run build && npm test --clean && npm run lint` to make sure `lib/*.js` are up-to-date.
- From `main` 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 [recommendeations for versioning and releasing actions](https://git
## New minor / patch release
- From `master` branch, run `npm run build && npm test --clean && npm run lint` to make sure `lib/*.js` are up-to-date.
- From `main` 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 `master` into the release branch e.g. `release/v1`.
- Merge from `main` into the release branch e.g. `release/v1`.
- Run `npm prune --production`.
- Commit merged changes (and the pruned `node_modules`).
- Push to remote.
+25 -2
View File
@@ -17,14 +17,14 @@ describe('api-level validator tests', () => {
it('Throws if api-level is lower than min API supported', () => {
const func = () => {
validator.checkApiLevel('20');
validator.checkApiLevel('14');
};
expect(func).toThrowError(`Minimum API level supported is ${validator.MIN_API_LEVEL}.`);
});
it('Validates successfully with valid api-level', () => {
const func1 = () => {
validator.checkApiLevel('21');
validator.checkApiLevel('15');
};
expect(func1).not.toThrow();
@@ -97,3 +97,26 @@ describe('disable-animations validator tests', () => {
expect(func2).not.toThrow();
});
});
describe('emulator-build validator tests', () => {
it('Throws if emulator-build is not a number', () => {
const func = () => {
validator.checkEmulatorBuild('abc123');
};
expect(func).toThrowError(`Unexpected emulator build: 'abc123'.`);
});
it('Throws if emulator-build is not an integer', () => {
const func = () => {
validator.checkEmulatorBuild('123.123');
};
expect(func).toThrowError(`Unexpected emulator build: '123.123'.`);
});
it('Validates successfully with valid emulator-build', () => {
const func = () => {
validator.checkEmulatorBuild('6061023');
};
expect(func).not.toThrow();
});
});
+33
View File
@@ -0,0 +1,33 @@
import * as parser from '../src/script-parser';
describe('script parser tests', () => {
it('Scripts are trimmed', () => {
const script = ` command \n`;
expect(parser.parseScript(script)).toEqual(['command']);
});
it('Commented lines are filtered out', () => {
const script = `
# command1
command2
# command3
command4
`;
expect(parser.parseScript(script)).toEqual(['command2', 'command4']);
});
it('Throws if parsed scripts array is empty', () => {
const func = () => {
const script = `
# command1
# command2
`;
const result = parser.parseScript(script);
console.log(`Result: ${result}`);
};
expect(func).toThrowError(`No valid script found.`);
});
});
+14 -3
View File
@@ -15,13 +15,24 @@ inputs:
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`.'
description: 'hardware profile used for creating the AVD - e.g. `Nexus 6`'
avd-name:
description: 'custom AVD name used for creating the Android Virtual Device'
default: 'test'
emulator-options:
description: 'command-line options used when launching the emulator - e.g. `-no-snapshot -camera-back emulated`.'
default: '-no-window -no-snapshot -noaudio -no-boot-anim'
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'
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'
script:
description: 'custom script to run - e.g. `./gradlew connectedCheck`'
required: true
+41 -26
View File
@@ -1,41 +1,57 @@
"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 (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;
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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 });
exports.killEmulator = exports.launchEmulator = void 0;
const exec = __importStar(require("@actions/exec"));
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`;
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
/**
* Creates and launches a new AVD instance with the specified configurations.
*/
function launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations) {
function launchEmulator(apiLevel, target, arch, profile, avdName, 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}"`);
yield exec.exec(`avdmanager create avd --force -n "${avdName}" --abi "${target}/${arch}" --package "system-images;android-${apiLevel};${target};${arch}" --device "${profile}"`);
}
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}'"`);
yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}'"`);
}
// start emulator
console.log('Starting emulator.');
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd test ${emulatorOptions} &"`, [], {
// turn off hardware acceleration on Linux
if (process.platform === 'linux') {
emulatorOptions += ' -accel off';
}
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
listeners: {
stderr: (data) => {
if (data.toString().includes('invalid command-line parameter')) {
@@ -46,13 +62,13 @@ function launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disabl
});
// wait for emulator to complete booting
yield waitForDevice();
yield exec.exec(`${ADB_PATH} shell input keyevent 82`);
yield exec.exec(`adb shell input keyevent 82`);
// disable animations
if (disableAnimations) {
console.log('Disabling animations.');
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`);
yield exec.exec(`adb shell settings put global window_animation_scale 0.0`);
yield exec.exec(`adb shell settings put global transition_animation_scale 0.0`);
yield exec.exec(`adb shell settings put global animator_duration_scale 0.0`);
}
});
}
@@ -63,10 +79,10 @@ exports.launchEmulator = launchEmulator;
function killEmulator() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield exec.exec(`${ADB_PATH} -s emulator-5554 emu kill`);
yield exec.exec(`adb -s emulator-5554 emu kill`);
}
catch (error) {
console.log('No emulator running on port 5554');
console.log(error.message);
}
});
}
@@ -76,7 +92,6 @@ exports.killEmulator = killEmulator;
*/
function waitForDevice() {
return __awaiter(this, void 0, void 0, function* () {
const adbPath = `${process.env.ANDROID_HOME}/platform-tools/adb`;
let booted = false;
let attempts = 0;
const retryInterval = 2; // retry every 2 seconds
@@ -84,7 +99,7 @@ function waitForDevice() {
while (!booted) {
try {
let result = '';
yield exec.exec(`${adbPath} shell getprop sys.boot_completed`, [], {
yield exec.exec(`adb shell getprop sys.boot_completed`, [], {
listeners: {
stdout: (data) => {
result += data.toString();
@@ -98,7 +113,7 @@ function waitForDevice() {
}
}
catch (error) {
console.log(error.message);
console.warn(error.message);
}
if (attempts < maxAttemps) {
yield delay(retryInterval * 1000);
+8 -1
View File
@@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MIN_API_LEVEL = 21;
exports.checkEmulatorBuild = exports.checkDisableAnimations = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
exports.MIN_API_LEVEL = 15;
exports.VALID_TARGETS = ['default', 'google_apis'];
exports.VALID_ARCHS = ['x86', 'x86_64'];
function checkApiLevel(apiLevel) {
@@ -30,3 +31,9 @@ function checkDisableAnimations(disableAnimations) {
}
}
exports.checkDisableAnimations = checkDisableAnimations;
function checkEmulatorBuild(emulatorBuild) {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
}
}
exports.checkEmulatorBuild = checkEmulatorBuild;
+79 -21
View File
@@ -1,31 +1,50 @@
"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 (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;
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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 = __importStar(require("@actions/core"));
const sdk_installer_1 = require("./sdk-installer");
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");
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
// only support running on macOS
// only support running on macOS or Linux
if (process.platform !== 'darwin') {
throw new Error('This action is expected to be run within a macOS virtual machine to enable hardware acceleration.');
if (process.platform === 'linux') {
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.`);
}
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 });
@@ -39,10 +58,13 @@ function run() {
// CPU architecture of the system image
const arch = core.getInput('arch');
input_validator_1.checkArch(arch);
console.log(`CPI architecture: ${arch}`);
console.log(`CPU architecture: ${arch}`);
// Hardware profile used for creating the AVD
const profile = core.getInput('profile');
console.log(`Hardware profile: ${profile}`);
// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);
// emulator options
const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`);
@@ -51,15 +73,51 @@ function run() {
input_validator_1.checkDisableAnimations(disableAnimationsInput);
const disableAnimations = disableAnimationsInput === 'true';
console.log(`disable animations: ${disableAnimations}`);
// custom scrpt to run
const script = core.getInput('script', { required: true });
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
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;
// 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}`);
}));
// install SDK
yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion);
// launch an emulator
yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, avdName, emulatorOptions, disableAnimations);
// execute the custom script
try {
// install SDK
yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch);
// launch an emulator
yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations);
// execute the custom script
yield exec.exec(`${script}`);
// move to custom working directory if set
if (workingDirectory) {
process.chdir(workingDirectory);
}
for (const script of scripts) {
yield exec.exec(`sh -c \\"${script}"`);
}
}
catch (error) {
core.setFailed(error.message);
+20
View File
@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseScript = void 0;
/**
* Convert a (potentially multi-line) script to an array of single-line script(s).
*/
function parseScript(rawScript) {
const scripts = rawScript
.trim()
.split(/\r\n|\n|\r/)
.map((value) => value.trim())
.filter((value) => {
return !value.startsWith('#') && value.length > 0;
});
if (scripts.length == 0) {
throw new Error(`No valid script found.`);
}
return scripts;
}
exports.parseScript = parseScript;
+78 -16
View File
@@ -1,33 +1,95 @@
"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 (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;
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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 });
exports.installAndroidSdk = void 0;
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
const BUILD_TOOLS_VERSION = '29.0.2';
const io = __importStar(require("@actions/io"));
const fs = __importStar(require("fs"));
const BUILD_TOOLS_VERSION = '30.0.2';
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-6609375_latest.zip';
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip';
/**
* 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.
*/
function installAndroidSdk(apiLevel, target, arch) {
function installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion) {
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, platform, and emulator.');
yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' emulator > /dev/null"`);
const isOnMac = process.platform === 'darwin';
if (!isOnMac) {
yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
}
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;
yield io.mkdirP(`${process.env.ANDROID_HOME}/cmdline-tools`);
yield exec.exec(`curl -fo commandlinetools.zip ${sdkUrl}`);
yield exec.exec(`unzip -q commandlinetools.zip -d ${cmdlineToolsPath}`);
yield io.rmRF('commandlinetools.zip');
// add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/tools:${cmdlineToolsPath}/tools/bin:${process.env.ANDROID_HOME}/platform-tools`);
}
// additional permission and license requirements for Linux
const sdkPreviewLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-preview-license`;
if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {
fs.writeFileSync(sdkPreviewLicensePath, '\n84831b9409646a918e30573bab4c9c91346d8abd');
}
// license required for API 30 system images
const sdkArmDbtLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-arm-dbt-license`;
if (apiLevel == 30 && !fs.existsSync(sdkArmDbtLicensePath)) {
fs.writeFileSync(sdkArmDbtLicensePath, '\n859f317696f67ef3d7f30a50a5560e7834b43903');
}
console.log('Installing latest build tools, platform tools, and platform.');
yield exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`);
if (emulatorBuild) {
console.log(`Installing emulator build ${emulatorBuild}.`);
yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}-${emulatorBuild}.zip`);
yield io.rmRF(`${process.env.ANDROID_HOME}/emulator`);
yield exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`);
yield io.rmRF('emulator.zip');
}
else {
console.log('Installing latest emulator.');
yield exec.exec(`sh -c \\"sdkmanager --install emulator > /dev/null"`);
}
console.log('Installing system images.');
yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`);
yield exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`);
if (ndkVersion) {
console.log(`Installing NDK ${ndkVersion}.`);
yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' > /dev/null"`);
}
if (cmakeVersion) {
console.log(`Installing CMake ${cmakeVersion}.`);
yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' > /dev/null"`);
}
});
}
exports.installAndroidSdk = installAndroidSdk;
+1
View File
@@ -0,0 +1 @@
export declare function issueCommand(command: string, message: any): void;
+29
View File
@@ -0,0 +1,29 @@
"use strict";
// For internal use, subject to change.
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 });
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const utils_1 = require("./utils");
function issueCommand(command, message) {
const filePath = process.env[`GITHUB_${command}`];
if (!filePath) {
throw new Error(`Unable to find environment variable for file command ${command}`);
}
if (!fs.existsSync(filePath)) {
throw new Error(`Missing file at path: ${filePath}`);
}
fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, {
encoding: 'utf8'
});
}
exports.issueCommand = issueCommand;
//# sourceMappingURL=file-command.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"file-command.js","sourceRoot":"","sources":["../src/file-command.ts"],"names":[],"mappings":";AAAA,uCAAuC;;;;;;;;;AAEvC,mCAAmC;AACnC,uDAAuD;AAEvD,uCAAwB;AACxB,uCAAwB;AACxB,mCAAsC;AAEtC,SAAgB,YAAY,CAAC,OAAe,EAAE,OAAY;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,IAAI,KAAK,CACb,wDAAwD,OAAO,EAAE,CAClE,CAAA;KACF;IACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAA;KACrD;IAED,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,sBAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;QACjE,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAA;AACJ,CAAC;AAdD,oCAcC"}
+5
View File
@@ -0,0 +1,5 @@
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
*/
export declare function toCommandValue(input: any): string;
+19
View File
@@ -0,0 +1,19 @@
"use strict";
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
*/
function toCommandValue(input) {
if (input === null || input === undefined) {
return '';
}
else if (typeof input === 'string' || input instanceof String) {
return input;
}
return JSON.stringify(input);
}
exports.toCommandValue = toCommandValue;
//# sourceMappingURL=utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";AAAA,mCAAmC;AACnC,uDAAuD;;AAEvD;;;GAGG;AACH,SAAgB,cAAc,CAAC,KAAU;IACvC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE;QACzC,OAAO,EAAE,CAAA;KACV;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,YAAY,MAAM,EAAE;QAC/D,OAAO,KAAe,CAAA;KACvB;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC;AAPD,wCAOC"}
+16
View File
@@ -0,0 +1,16 @@
export interface IToolReleaseFile {
filename: string;
platform: string;
platform_version?: string;
arch: string;
download_url: string;
}
export interface IToolRelease {
version: string;
stable: boolean;
release_url: string;
files: IToolReleaseFile[];
}
export declare function _findMatch(versionSpec: string, stable: boolean, candidates: IToolRelease[], archFilter: string): Promise<IToolRelease | undefined>;
export declare function _getOsVersion(): string;
export declare function _readLinuxVersionFile(): string;
+106
View File
@@ -0,0 +1,106 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const semver = __importStar(require("semver"));
const core_1 = require("@actions/core");
// needs to be require for core node modules to be mocked
/* eslint @typescript-eslint/no-require-imports: 0 */
const os = require("os");
const cp = require("child_process");
const fs = require("fs");
function _findMatch(versionSpec, stable, candidates, archFilter) {
return __awaiter(this, void 0, void 0, function* () {
const platFilter = os.platform();
let result;
let match;
let file;
for (const candidate of candidates) {
const version = candidate.version;
core_1.debug(`check ${version} satisfies ${versionSpec}`);
if (semver.satisfies(version, versionSpec) &&
(!stable || candidate.stable === stable)) {
file = candidate.files.find(item => {
core_1.debug(`${item.arch}===${archFilter} && ${item.platform}===${platFilter}`);
let chk = item.arch === archFilter && item.platform === platFilter;
if (chk && item.platform_version) {
const osVersion = module.exports._getOsVersion();
if (osVersion === item.platform_version) {
chk = true;
}
else {
chk = semver.satisfies(osVersion, item.platform_version);
}
}
return chk;
});
if (file) {
core_1.debug(`matched ${candidate.version}`);
match = candidate;
break;
}
}
}
if (match && file) {
// clone since we're mutating the file list to be only the file that matches
result = Object.assign({}, match);
result.files = [file];
}
return result;
});
}
exports._findMatch = _findMatch;
function _getOsVersion() {
// TODO: add windows and other linux, arm variants
// right now filtering on version is only an ubuntu and macos scenario for tools we build for hosted (python)
const plat = os.platform();
let version = '';
if (plat === 'darwin') {
version = cp.execSync('sw_vers -productVersion').toString();
}
else if (plat === 'linux') {
// lsb_release process not in some containers, readfile
// Run cat /etc/lsb-release
// DISTRIB_ID=Ubuntu
// DISTRIB_RELEASE=18.04
// DISTRIB_CODENAME=bionic
// DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
const lsbContents = module.exports._readLinuxVersionFile();
if (lsbContents) {
const lines = lsbContents.split('\n');
for (const line of lines) {
const parts = line.split('=');
if (parts.length === 2 && parts[0].trim() === 'DISTRIB_RELEASE') {
version = parts[1].trim();
break;
}
}
}
}
return version;
}
exports._getOsVersion = _getOsVersion;
function _readLinuxVersionFile() {
const lsbFile = '/etc/lsb-release';
let contents = '';
if (fs.existsSync(lsbFile)) {
contents = fs.readFileSync(lsbFile).toString();
}
return contents;
}
exports._readLinuxVersionFile = _readLinuxVersionFile;
//# sourceMappingURL=manifest.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,+CAAgC;AAChC,wCAAmC;AAEnC,yDAAyD;AACzD,qDAAqD;AAErD,yBAAyB;AACzB,oCAAoC;AACpC,yBAAyB;AAqDzB,SAAsB,UAAU,CAC9B,WAAmB,EACnB,MAAe,EACf,UAA0B,EAC1B,UAAkB;;QAElB,MAAM,UAAU,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAA;QAEhC,IAAI,MAAgC,CAAA;QACpC,IAAI,KAA+B,CAAA;QAEnC,IAAI,IAAkC,CAAA;QACtC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;YAClC,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;YAEjC,YAAK,CAAC,SAAS,OAAO,cAAc,WAAW,EAAE,CAAC,CAAA;YAClD,IACE,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,WAAW,CAAC;gBACtC,CAAC,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM,CAAC,EACxC;gBACA,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACjC,YAAK,CACH,GAAG,IAAI,CAAC,IAAI,MAAM,UAAU,OAAO,IAAI,CAAC,QAAQ,MAAM,UAAU,EAAE,CACnE,CAAA;oBAED,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,QAAQ,KAAK,UAAU,CAAA;oBAClE,IAAI,GAAG,IAAI,IAAI,CAAC,gBAAgB,EAAE;wBAChC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,CAAA;wBAEhD,IAAI,SAAS,KAAK,IAAI,CAAC,gBAAgB,EAAE;4BACvC,GAAG,GAAG,IAAI,CAAA;yBACX;6BAAM;4BACL,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAA;yBACzD;qBACF;oBAED,OAAO,GAAG,CAAA;gBACZ,CAAC,CAAC,CAAA;gBAEF,IAAI,IAAI,EAAE;oBACR,YAAK,CAAC,WAAW,SAAS,CAAC,OAAO,EAAE,CAAC,CAAA;oBACrC,KAAK,GAAG,SAAS,CAAA;oBACjB,MAAK;iBACN;aACF;SACF;QAED,IAAI,KAAK,IAAI,IAAI,EAAE;YACjB,4EAA4E;YAC5E,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;YACjC,MAAM,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,CAAA;SACtB;QAED,OAAO,MAAM,CAAA;IACf,CAAC;CAAA;AAtDD,gCAsDC;AAED,SAAgB,aAAa;IAC3B,kDAAkD;IAClD,6GAA6G;IAC7G,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAA;IAC1B,IAAI,OAAO,GAAG,EAAE,CAAA;IAEhB,IAAI,IAAI,KAAK,QAAQ,EAAE;QACrB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,QAAQ,EAAE,CAAA;KAC5D;SAAM,IAAI,IAAI,KAAK,OAAO,EAAE;QAC3B,uDAAuD;QACvD,2BAA2B;QAC3B,oBAAoB;QACpB,wBAAwB;QACxB,0BAA0B;QAC1B,2CAA2C;QAC3C,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAA;QAC1D,IAAI,WAAW,EAAE;YACf,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;gBACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,iBAAiB,EAAE;oBAC/D,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;oBACzB,MAAK;iBACN;aACF;SACF;KACF;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AA7BD,sCA6BC;AAED,SAAgB,qBAAqB;IACnC,MAAM,OAAO,GAAG,kBAAkB,CAAA;IAClC,IAAI,QAAQ,GAAG,EAAE,CAAA;IAEjB,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE;QAC1B,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAA;KAC/C;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AATD,sDASC"}
+12
View File
@@ -0,0 +1,12 @@
/**
* Internal class for retries
*/
export declare class RetryHelper {
private maxAttempts;
private minSeconds;
private maxSeconds;
constructor(maxAttempts: number, minSeconds: number, maxSeconds: number);
execute<T>(action: () => Promise<T>, isRetryable?: (e: Error) => boolean): Promise<T>;
private getSleepAmount;
private sleep;
}
+70
View File
@@ -0,0 +1,70 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
/**
* Internal class for retries
*/
class RetryHelper {
constructor(maxAttempts, minSeconds, maxSeconds) {
if (maxAttempts < 1) {
throw new Error('max attempts should be greater than or equal to 1');
}
this.maxAttempts = maxAttempts;
this.minSeconds = Math.floor(minSeconds);
this.maxSeconds = Math.floor(maxSeconds);
if (this.minSeconds > this.maxSeconds) {
throw new Error('min seconds should be less than or equal to max seconds');
}
}
execute(action, isRetryable) {
return __awaiter(this, void 0, void 0, function* () {
let attempt = 1;
while (attempt < this.maxAttempts) {
// Try
try {
return yield action();
}
catch (err) {
if (isRetryable && !isRetryable(err)) {
throw err;
}
core.info(err.message);
}
// Sleep
const seconds = this.getSleepAmount();
core.info(`Waiting ${seconds} seconds before trying again`);
yield this.sleep(seconds);
attempt++;
}
// Last attempt
return yield action();
});
}
getSleepAmount() {
return (Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
this.minSeconds);
}
sleep(seconds) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
});
}
}
exports.RetryHelper = RetryHelper;
//# sourceMappingURL=retry-helper.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"retry-helper.js","sourceRoot":"","sources":["../src/retry-helper.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oDAAqC;AAErC;;GAEG;AACH,MAAa,WAAW;IAKtB,YAAY,WAAmB,EAAE,UAAkB,EAAE,UAAkB;QACrE,IAAI,WAAW,GAAG,CAAC,EAAE;YACnB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;SACrE;QAED,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACxC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACxC,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE;YACrC,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;SAC3E;IACH,CAAC;IAEK,OAAO,CACX,MAAwB,EACxB,WAAmC;;YAEnC,IAAI,OAAO,GAAG,CAAC,CAAA;YACf,OAAO,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE;gBACjC,MAAM;gBACN,IAAI;oBACF,OAAO,MAAM,MAAM,EAAE,CAAA;iBACtB;gBAAC,OAAO,GAAG,EAAE;oBACZ,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE;wBACpC,MAAM,GAAG,CAAA;qBACV;oBAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;iBACvB;gBAED,QAAQ;gBACR,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,EAAE,CAAA;gBACrC,IAAI,CAAC,IAAI,CAAC,WAAW,OAAO,8BAA8B,CAAC,CAAA;gBAC3D,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;gBACzB,OAAO,EAAE,CAAA;aACV;YAED,eAAe;YACf,OAAO,MAAM,MAAM,EAAE,CAAA;QACvB,CAAC;KAAA;IAEO,cAAc;QACpB,OAAO,CACL,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;YACnE,IAAI,CAAC,UAAU,CAChB,CAAA;IACH,CAAC;IAEa,KAAK,CAAC,OAAe;;YACjC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC,CAAA;QACpE,CAAC;KAAA;CACF;AAxDD,kCAwDC"}
+8
View File
@@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.12"
- "iojs"
before_install:
- npm install -g npm@~1.4.6
+18
View File
@@ -0,0 +1,18 @@
This software is released under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
var argv = require('../')(process.argv.slice(2));
console.log(argv);
+245
View File
@@ -0,0 +1,245 @@
module.exports = function (args, opts) {
if (!opts) opts = {};
var flags = { bools : {}, strings : {}, unknownFn: null };
if (typeof opts['unknown'] === 'function') {
flags.unknownFn = opts['unknown'];
}
if (typeof opts['boolean'] === 'boolean' && opts['boolean']) {
flags.allBools = true;
} else {
[].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
}
var aliases = {};
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
flags.strings[aliases[key]] = true;
}
});
var defaults = opts['default'] || {};
var argv = { _ : [] };
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--')+1);
args = args.slice(0, args.indexOf('--'));
}
function argDefined(key, arg) {
return (flags.allBools && /^--[^=]+$/.test(arg)) ||
flags.strings[key] || flags.bools[key] || aliases[key];
}
function setArg (key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) return;
}
var value = !flags.strings[key] && isNumber(val)
? Number(val) : val
;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
function setKey (obj, keys, value) {
var o = obj;
for (var i = 0; i < keys.length-1; i++) {
var key = keys[i];
if (key === '__proto__') return;
if (o[key] === undefined) o[key] = {};
if (o[key] === Object.prototype || o[key] === Number.prototype
|| o[key] === String.prototype) o[key] = {};
if (o[key] === Array.prototype) o[key] = [];
o = o[key];
}
var key = keys[keys.length - 1];
if (key === '__proto__') return;
if (o === Object.prototype || o === Number.prototype
|| o === String.prototype) o = {};
if (o === Array.prototype) o = [];
if (o[key] === undefined || flags.bools[key] || typeof o[key] === 'boolean') {
o[key] = value;
}
else if (Array.isArray(o[key])) {
o[key].push(value);
}
else {
o[key] = [ o[key], value ];
}
}
function aliasIsBoolean(key) {
return aliases[key].some(function (x) {
return flags.bools[x];
});
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (/^--.+=/.test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
var key = m[1];
var value = m[2];
if (flags.bools[key]) {
value = value !== 'false';
}
setArg(key, value, arg);
}
else if (/^--no-.+/.test(arg)) {
var key = arg.match(/^--no-(.+)/)[1];
setArg(key, false, arg);
}
else if (/^--.+/.test(arg)) {
var key = arg.match(/^--(.+)/)[1];
var next = args[i + 1];
if (next !== undefined && !/^-/.test(next)
&& !flags.bools[key]
&& !flags.allBools
&& (aliases[key] ? !aliasIsBoolean(key) : true)) {
setArg(key, next, arg);
i++;
}
else if (/^(true|false)$/.test(next)) {
setArg(key, next === 'true', arg);
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
else if (/^-[^-]+/.test(arg)) {
var letters = arg.slice(1,-1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
var next = arg.slice(j+2);
if (next === '-') {
setArg(letters[j], next, arg)
continue;
}
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) {
setArg(letters[j], next.split('=')[1], arg);
broken = true;
break;
}
if (/[A-Za-z]/.test(letters[j])
&& /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j+1] && letters[j+1].match(/\W/)) {
setArg(letters[j], arg.slice(j+2), arg);
broken = true;
break;
}
else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
var key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
&& !flags.bools[key]
&& (aliases[key] ? !aliasIsBoolean(key) : true)) {
setArg(key, args[i+1], arg);
i++;
}
else if (args[i+1] && /^(true|false)$/.test(args[i+1])) {
setArg(key, args[i+1] === 'true', arg);
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
}
else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(
flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
);
}
if (opts.stopEarly) {
argv._.push.apply(argv._, args.slice(i + 1));
break;
}
}
}
Object.keys(defaults).forEach(function (key) {
if (!hasKey(argv, key.split('.'))) {
setKey(argv, key.split('.'), defaults[key]);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[key]);
});
}
});
if (opts['--']) {
argv['--'] = new Array();
notFlags.forEach(function(key) {
argv['--'].push(key);
});
}
else {
notFlags.forEach(function(key) {
argv._.push(key);
});
}
return argv;
};
function hasKey (obj, keys) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
o = (o[key] || {});
});
var key = keys[keys.length - 1];
return key in o;
}
function isNumber (x) {
if (typeof x === 'number') return true;
if (/^0x[0-9a-f]+$/i.test(x)) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
}
+80
View File
@@ -0,0 +1,80 @@
{
"_args": [
[
"minimist@1.2.5",
"/Users/Yang/Desktop/Projects/android-emulator-runner"
]
],
"_from": "minimist@1.2.5",
"_id": "minimist@1.2.5",
"_inBundle": false,
"_integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"_location": "/minimist",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "minimist@1.2.5",
"name": "minimist",
"escapedName": "minimist",
"rawSpec": "1.2.5",
"saveSpec": null,
"fetchSpec": "1.2.5"
},
"_requiredBy": [
"/",
"/@babel/core/json5",
"/@cnakazawa/watch",
"/json5",
"/sane"
],
"_resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"_spec": "1.2.5",
"_where": "/Users/Yang/Desktop/Projects/android-emulator-runner",
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"bugs": {
"url": "https://github.com/substack/minimist/issues"
},
"description": "parse argument options",
"devDependencies": {
"covert": "^1.0.0",
"tap": "~0.4.0",
"tape": "^3.5.0"
},
"homepage": "https://github.com/substack/minimist",
"keywords": [
"argv",
"getopt",
"parser",
"optimist"
],
"license": "MIT",
"main": "index.js",
"name": "minimist",
"repository": {
"type": "git",
"url": "git://github.com/substack/minimist.git"
},
"scripts": {
"coverage": "covert test/*.js",
"test": "tap test/*.js"
},
"testling": {
"files": "test/*.js",
"browsers": [
"ie/6..latest",
"ff/5",
"firefox/latest",
"chrome/10",
"chrome/latest",
"safari/5.1",
"safari/latest",
"opera/12"
]
},
"version": "1.2.5"
}
+95
View File
@@ -0,0 +1,95 @@
# minimist
parse argument options
This module is the guts of optimist's argument parser without all the
fanciful decoration.
# example
``` js
var argv = require('minimist')(process.argv.slice(2));
console.log(argv);
```
```
$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }
```
```
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
x: 3,
y: 4,
n: 5,
a: true,
b: true,
c: true,
beep: 'boop' }
```
# security
Previous versions had a prototype pollution bug that could cause privilege
escalation in some circumstances when handling untrusted user input.
Please use version 1.2.3 or later: https://snyk.io/vuln/SNYK-JS-MINIMIST-559764
# methods
``` js
var parseArgs = require('minimist')
```
## var argv = parseArgs(args, opts={})
Return an argument object `argv` populated with the array arguments from `args`.
`argv._` contains all the arguments that didn't have an option associated with
them.
Numeric-looking arguments will be returned as numbers unless `opts.string` or
`opts.boolean` is set for that argument name.
Any arguments after `'--'` will not be parsed and will end up in `argv._`.
options can be:
* `opts.string` - a string or array of strings argument names to always treat as
strings
* `opts.boolean` - a boolean, string or array of strings to always treat as
booleans. if `true` will treat all double hyphenated arguments without equal signs
as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`)
* `opts.alias` - an object mapping string names to strings or arrays of string
argument names to use as aliases
* `opts.default` - an object mapping string argument names to default values
* `opts.stopEarly` - when true, populate `argv._` with everything after the
first non-option
* `opts['--']` - when true, populate `argv._` with everything before the `--`
and `argv['--']` with everything after the `--`. Here's an example:
```
> require('./')('one two three -- four five --six'.split(' '), { '--': true })
{ _: [ 'one', 'two', 'three' ],
'--': [ 'four', 'five', '--six' ] }
```
Note that with `opts['--']` set, parsing for arguments still stops after the
`--`.
* `opts.unknown` - a function which is invoked with a command line parameter not
defined in the `opts` configuration object. If the function returns `false`, the
unknown option is not added to `argv`.
# install
With [npm](https://npmjs.org) do:
```
npm install minimist
```
# license
MIT
+32
View File
@@ -0,0 +1,32 @@
var parse = require('../');
var test = require('tape');
test('flag boolean true (default all --args to boolean)', function (t) {
var argv = parse(['moo', '--honk', 'cow'], {
boolean: true
});
t.deepEqual(argv, {
honk: true,
_: ['moo', 'cow']
});
t.deepEqual(typeof argv.honk, 'boolean');
t.end();
});
test('flag boolean true only affects double hyphen arguments without equals signs', function (t) {
var argv = parse(['moo', '--honk', 'cow', '-p', '55', '--tacos=good'], {
boolean: true
});
t.deepEqual(argv, {
honk: true,
tacos: 'good',
p: 55,
_: ['moo', 'cow']
});
t.deepEqual(typeof argv.honk, 'boolean');
t.end();
});
+178
View File
@@ -0,0 +1,178 @@
var parse = require('../');
var test = require('tape');
test('flag boolean default false', function (t) {
var argv = parse(['moo'], {
boolean: ['t', 'verbose'],
default: { verbose: false, t: false }
});
t.deepEqual(argv, {
verbose: false,
t: false,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('boolean groups', function (t) {
var argv = parse([ '-x', '-z', 'one', 'two', 'three' ], {
boolean: ['x','y','z']
});
t.deepEqual(argv, {
x : true,
y : false,
z : true,
_ : [ 'one', 'two', 'three' ]
});
t.deepEqual(typeof argv.x, 'boolean');
t.deepEqual(typeof argv.y, 'boolean');
t.deepEqual(typeof argv.z, 'boolean');
t.end();
});
test('boolean and alias with chainable api', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = parse(aliased, {
boolean: 'herp',
alias: { h: 'herp' }
});
var propertyArgv = parse(regular, {
boolean: 'herp',
alias: { h: 'herp' }
});
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
alias: { 'h': 'herp' },
boolean: 'herp'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias array with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var alt = [ '--harp', 'derp' ];
var opts = {
alias: { 'h': ['herp', 'harp'] },
boolean: 'h'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var altPropertyArgv = parse(alt, opts);
var expected = {
harp: true,
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.same(altPropertyArgv, expected);
t.end();
});
test('boolean and alias using explicit true', function (t) {
var aliased = [ '-h', 'true' ];
var regular = [ '--herp', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
// regression, see https://github.com/substack/node-optimist/issues/71
test('boolean and --x=true', function(t) {
var parsed = parse(['--boool', '--other=true'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'true');
parsed = parse(['--boool', '--other=false'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'false');
t.end();
});
test('boolean --boool=true', function (t) {
var parsed = parse(['--boool=true'], {
default: {
boool: false
},
boolean: ['boool']
});
t.same(parsed.boool, true);
t.end();
});
test('boolean --boool=false', function (t) {
var parsed = parse(['--boool=false'], {
default: {
boool: true
},
boolean: ['boool']
});
t.same(parsed.boool, false);
t.end();
});
test('boolean using something similar to true', function (t) {
var opts = { boolean: 'h' };
var result = parse(['-h', 'true.txt'], opts);
var expected = {
h: true,
'_': ['true.txt']
};
t.same(result, expected);
t.end();
});
+31
View File
@@ -0,0 +1,31 @@
var parse = require('../');
var test = require('tape');
test('-', function (t) {
t.plan(5);
t.deepEqual(parse([ '-n', '-' ]), { n: '-', _: [] });
t.deepEqual(parse([ '-' ]), { _: [ '-' ] });
t.deepEqual(parse([ '-f-' ]), { f: '-', _: [] });
t.deepEqual(
parse([ '-b', '-' ], { boolean: 'b' }),
{ b: true, _: [ '-' ] }
);
t.deepEqual(
parse([ '-s', '-' ], { string: 's' }),
{ s: '-', _: [] }
);
});
test('-a -- b', function (t) {
t.plan(3);
t.deepEqual(parse([ '-a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
});
test('move arguments after the -- into their own `--` array', function(t) {
t.plan(1);
t.deepEqual(
parse([ '--name', 'John', 'before', '--', 'after' ], { '--': true }),
{ name: 'John', _: [ 'before' ], '--': [ 'after' ] });
});
+35
View File
@@ -0,0 +1,35 @@
var test = require('tape');
var parse = require('../');
test('boolean default true', function (t) {
var argv = parse([], {
boolean: 'sometrue',
default: { sometrue: true }
});
t.equal(argv.sometrue, true);
t.end();
});
test('boolean default false', function (t) {
var argv = parse([], {
boolean: 'somefalse',
default: { somefalse: false }
});
t.equal(argv.somefalse, false);
t.end();
});
test('boolean default to null', function (t) {
var argv = parse([], {
boolean: 'maybe',
default: { maybe: null }
});
t.equal(argv.maybe, null);
var argv = parse(['--maybe'], {
boolean: 'maybe',
default: { maybe: null }
});
t.equal(argv.maybe, true);
t.end();
})
+22
View File
@@ -0,0 +1,22 @@
var parse = require('../');
var test = require('tape');
test('dotted alias', function (t) {
var argv = parse(['--a.b', '22'], {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 22);
t.equal(argv.aa.bb, 22);
t.end();
});
test('dotted default', function (t) {
var argv = parse('', {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 11);
t.equal(argv.aa.bb, 11);
t.end();
});
test('dotted default with no alias', function (t) {
var argv = parse('', {default: {'a.b': 11}});
t.equal(argv.a.b, 11);
t.end();
});
+16
View File
@@ -0,0 +1,16 @@
var parse = require('../');
var test = require('tape');
test('short -k=v' , function (t) {
t.plan(1);
var argv = parse([ '-b=123' ]);
t.deepEqual(argv, { b: 123, _: [] });
});
test('multi short -k=v' , function (t) {
t.plan(1);
var argv = parse([ '-a=whatever', '-b=robots' ]);
t.deepEqual(argv, { a: 'whatever', b: 'robots', _: [] });
});
+31
View File
@@ -0,0 +1,31 @@
var test = require('tape');
var parse = require('../');
test('long opts', function (t) {
t.deepEqual(
parse([ '--bool' ]),
{ bool : true, _ : [] },
'long boolean'
);
t.deepEqual(
parse([ '--pow', 'xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture sp'
);
t.deepEqual(
parse([ '--pow=xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture eq'
);
t.deepEqual(
parse([ '--host', 'localhost', '--port', '555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures sp'
);
t.deepEqual(
parse([ '--host=localhost', '--port=555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures eq'
);
t.end();
});
+36
View File
@@ -0,0 +1,36 @@
var parse = require('../');
var test = require('tape');
test('nums', function (t) {
var argv = parse([
'-x', '1234',
'-y', '5.67',
'-z', '1e7',
'-w', '10f',
'--hex', '0xdeadbeef',
'789'
]);
t.deepEqual(argv, {
x : 1234,
y : 5.67,
z : 1e7,
w : '10f',
hex : 0xdeadbeef,
_ : [ 789 ]
});
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv.y, 'number');
t.deepEqual(typeof argv.z, 'number');
t.deepEqual(typeof argv.w, 'string');
t.deepEqual(typeof argv.hex, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});
test('already a number', function (t) {
var argv = parse([ '-x', 1234, 789 ]);
t.deepEqual(argv, { x : 1234, _ : [ 789 ] });
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});
+197
View File
@@ -0,0 +1,197 @@
var parse = require('../');
var test = require('tape');
test('parse args', function (t) {
t.deepEqual(
parse([ '--no-moo' ]),
{ moo : false, _ : [] },
'no'
);
t.deepEqual(
parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]),
{ v : ['a','b','c'], _ : [] },
'multi'
);
t.end();
});
test('comprehensive', function (t) {
t.deepEqual(
parse([
'--name=meowmers', 'bare', '-cats', 'woo',
'-h', 'awesome', '--multi=quux',
'--key', 'value',
'-b', '--bool', '--no-meep', '--multi=baz',
'--', '--not-a-flag', 'eek'
]),
{
c : true,
a : true,
t : true,
s : 'woo',
h : 'awesome',
b : true,
bool : true,
key : 'value',
multi : [ 'quux', 'baz' ],
meep : false,
name : 'meowmers',
_ : [ 'bare', '--not-a-flag', 'eek' ]
}
);
t.end();
});
test('flag boolean', function (t) {
var argv = parse([ '-t', 'moo' ], { boolean: 't' });
t.deepEqual(argv, { t : true, _ : [ 'moo' ] });
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('flag boolean value', function (t) {
var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], {
boolean: [ 't', 'verbose' ],
default: { verbose: true }
});
t.deepEqual(argv, {
verbose: false,
t: true,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('newlines in params' , function (t) {
var args = parse([ '-s', "X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
// reproduce in bash:
// VALUE="new
// line"
// node program.js --s="$VALUE"
args = parse([ "--s=X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
t.end();
});
test('strings' , function (t) {
var s = parse([ '-s', '0001234' ], { string: 's' }).s;
t.equal(s, '0001234');
t.equal(typeof s, 'string');
var x = parse([ '-x', '56' ], { string: 'x' }).x;
t.equal(x, '56');
t.equal(typeof x, 'string');
t.end();
});
test('stringArgs', function (t) {
var s = parse([ ' ', ' ' ], { string: '_' })._;
t.same(s.length, 2);
t.same(typeof s[0], 'string');
t.same(s[0], ' ');
t.same(typeof s[1], 'string');
t.same(s[1], ' ');
t.end();
});
test('empty strings', function(t) {
var s = parse([ '-s' ], { string: 's' }).s;
t.equal(s, '');
t.equal(typeof s, 'string');
var str = parse([ '--str' ], { string: 'str' }).str;
t.equal(str, '');
t.equal(typeof str, 'string');
var letters = parse([ '-art' ], {
string: [ 'a', 't' ]
});
t.equal(letters.a, '');
t.equal(letters.r, true);
t.equal(letters.t, '');
t.end();
});
test('string and alias', function(t) {
var x = parse([ '--str', '000123' ], {
string: 's',
alias: { s: 'str' }
});
t.equal(x.str, '000123');
t.equal(typeof x.str, 'string');
t.equal(x.s, '000123');
t.equal(typeof x.s, 'string');
var y = parse([ '-s', '000123' ], {
string: 'str',
alias: { str: 's' }
});
t.equal(y.str, '000123');
t.equal(typeof y.str, 'string');
t.equal(y.s, '000123');
t.equal(typeof y.s, 'string');
t.end();
});
test('slashBreak', function (t) {
t.same(
parse([ '-I/foo/bar/baz' ]),
{ I : '/foo/bar/baz', _ : [] }
);
t.same(
parse([ '-xyz/foo/bar/baz' ]),
{ x : true, y : true, z : '/foo/bar/baz', _ : [] }
);
t.end();
});
test('alias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: 'zoom' }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.f, 11);
t.end();
});
test('multiAlias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: [ 'zm', 'zoom' ] }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.z, argv.zm);
t.equal(argv.f, 11);
t.end();
});
test('nested dotted objects', function (t) {
var argv = parse([
'--foo.bar', '3', '--foo.baz', '4',
'--foo.quux.quibble', '5', '--foo.quux.o_O',
'--beep.boop'
]);
t.same(argv.foo, {
bar : 3,
baz : 4,
quux : {
quibble : 5,
o_O : true
}
});
t.same(argv.beep, { boop : true });
t.end();
});
+9
View File
@@ -0,0 +1,9 @@
var parse = require('../');
var test = require('tape');
test('parse with modifier functions' , function (t) {
t.plan(1);
var argv = parse([ '-b', '123' ], { boolean: 'b' });
t.deepEqual(argv, { b: true, _: [123] });
});
+44
View File
@@ -0,0 +1,44 @@
var parse = require('../');
var test = require('tape');
test('proto pollution', function (t) {
var argv = parse(['--__proto__.x','123']);
t.equal({}.x, undefined);
t.equal(argv.__proto__.x, undefined);
t.equal(argv.x, undefined);
t.end();
});
test('proto pollution (array)', function (t) {
var argv = parse(['--x','4','--x','5','--x.__proto__.z','789']);
t.equal({}.z, undefined);
t.deepEqual(argv.x, [4,5]);
t.equal(argv.x.z, undefined);
t.equal(argv.x.__proto__.z, undefined);
t.end();
});
test('proto pollution (number)', function (t) {
var argv = parse(['--x','5','--x.__proto__.z','100']);
t.equal({}.z, undefined);
t.equal((4).z, undefined);
t.equal(argv.x, 5);
t.equal(argv.x.z, undefined);
t.end();
});
test('proto pollution (string)', function (t) {
var argv = parse(['--x','abc','--x.__proto__.z','def']);
t.equal({}.z, undefined);
t.equal('...'.z, undefined);
t.equal(argv.x, 'abc');
t.equal(argv.x.z, undefined);
t.end();
});
test('proto pollution (constructor)', function (t) {
var argv = parse(['--constructor.prototype.y','123']);
t.equal({}.y, undefined);
t.equal(argv.y, undefined);
t.end();
});
+67
View File
@@ -0,0 +1,67 @@
var parse = require('../');
var test = require('tape');
test('numeric short args', function (t) {
t.plan(2);
t.deepEqual(parse([ '-n123' ]), { n: 123, _: [] });
t.deepEqual(
parse([ '-123', '456' ]),
{ 1: true, 2: true, 3: 456, _: [] }
);
});
test('short', function (t) {
t.deepEqual(
parse([ '-b' ]),
{ b : true, _ : [] },
'short boolean'
);
t.deepEqual(
parse([ 'foo', 'bar', 'baz' ]),
{ _ : [ 'foo', 'bar', 'baz' ] },
'bare'
);
t.deepEqual(
parse([ '-cats' ]),
{ c : true, a : true, t : true, s : true, _ : [] },
'group'
);
t.deepEqual(
parse([ '-cats', 'meow' ]),
{ c : true, a : true, t : true, s : 'meow', _ : [] },
'short group next'
);
t.deepEqual(
parse([ '-h', 'localhost' ]),
{ h : 'localhost', _ : [] },
'short capture'
);
t.deepEqual(
parse([ '-h', 'localhost', '-p', '555' ]),
{ h : 'localhost', p : 555, _ : [] },
'short captures'
);
t.end();
});
test('mixed short bool and capture', function (t) {
t.same(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});
test('short and long', function (t) {
t.deepEqual(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});
+15
View File
@@ -0,0 +1,15 @@
var parse = require('../');
var test = require('tape');
test('stops parsing on the first non-option when stopEarly is set', function (t) {
var argv = parse(['--aaa', 'bbb', 'ccc', '--ddd'], {
stopEarly: true
});
t.deepEqual(argv, {
aaa: 'bbb',
_: ['ccc', '--ddd']
});
t.end();
});
+102
View File
@@ -0,0 +1,102 @@
var parse = require('../');
var test = require('tape');
test('boolean and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'true', '--derp', 'true' ];
var regular = [ '--herp', 'true', '-d', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h',
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, ['--derp', '-d']);
t.end();
});
test('flag boolean true any double hyphen argument is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var argv = parse(['--honk', '--tacos=good', 'cow', '-p', '55'], {
boolean: true,
unknown: unknownFn
});
t.same(unknown, ['--tacos=good', 'cow', '-p']);
t.same(argv, {
honk: true,
_: []
});
t.end();
});
test('string and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'hello', '--derp', 'goodbye' ];
var regular = [ '--herp', 'hello', '-d', 'moon' ];
var opts = {
alias: { h: 'herp' },
string: 'h',
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, ['--derp', '-d']);
t.end();
});
test('default and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'hello' ];
var regular = [ '--herp', 'hello' ];
var opts = {
default: { 'h': 'bar' },
alias: { 'h': 'herp' },
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, []);
t.end();
unknownFn(); // exercise fn for 100% coverage
});
test('value following -- is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '--bad', '--', 'good', 'arg' ];
var opts = {
'--': true,
unknown: unknownFn
};
var argv = parse(aliased, opts);
t.same(unknown, ['--bad']);
t.same(argv, {
'--': ['good', 'arg'],
'_': []
})
t.end();
});
+8
View File
@@ -0,0 +1,8 @@
var parse = require('../');
var test = require('tape');
test('whitespace should be whitespace' , function (t) {
t.plan(1);
var x = parse([ '-x', '\t' ]).x;
t.equal(x, '\t');
});
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/node-tunnel.iml" filepath="$PROJECT_DIR$/.idea/node-tunnel.iml" />
</modules>
</component>
</project>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+797
View File
@@ -0,0 +1,797 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="3caed8aa-31ae-4b3d-ad18-6f9796663516" name="Default" comment="">
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/.travis.yml" afterPath="$PROJECT_DIR$/.travis.yml" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/CHANGELOG.md" afterPath="$PROJECT_DIR$/CHANGELOG.md" />
</list>
<ignored path="$PROJECT_DIR$/.tmp/" />
<ignored path="$PROJECT_DIR$/temp/" />
<ignored path="$PROJECT_DIR$/tmp/" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file leaf-file-name="package.json" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" column="19" lean-forward="false" selection-start-line="2" selection-start-column="19" selection-end-line="2" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="README.md" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="2312">
<caret line="136" column="67" lean-forward="false" selection-start-line="136" selection-start-column="67" selection-end-line="136" selection-end-column="67" />
<folding>
<marker date="1497272379133" expanded="true" signature="590:646" ph="{...}" />
<marker date="1497272379133" expanded="true" signature="601:644" ph="{&quot;host&quot;: 'localhost'...}" />
<marker date="1497272379133" expanded="true" signature="674:737" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="884:1330" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="964:1328" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1103:1192" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1290:1324" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="1357:1419" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="1514:2209" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="1540:1623" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1842:2207" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1981:2070" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2168:2202" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="2237:2300" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="2395:3180" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="2475:3178" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="2615:2704" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2802:2836" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="3207:3269" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="3366:4398" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="3392:3475" ph="//..." />
<marker date="1497272379133" expanded="true" signature="3694:4396" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="3834:3923" ph="//..." />
<marker date="1497272379133" expanded="true" signature="4021:4055" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="4426:4489" ph="{&quot;host&quot;: 'example.com'...}" />
</folding>
</first_editor>
<second_editor />
</state>
</provider>
</entry>
</file>
<file leaf-file-name=".travis.yml" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/.travis.yml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="6" column="0" lean-forward="true" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="tunnel.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/lib/tunnel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="697">
<caret line="41" column="19" lean-forward="false" selection-start-line="41" selection-start-column="19" selection-end-line="41" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="http-over-http-error.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/test/http-over-http-error.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="935">
<caret line="55" column="26" lean-forward="true" selection-start-line="55" selection-start-column="26" selection-end-line="55" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="http-over-http-error2.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/test/http-over-http-error2.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1207">
<caret line="71" column="0" lean-forward="false" selection-start-line="71" selection-start-column="0" selection-end-line="71" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="https-over-http.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/test/https-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1479">
<caret line="87" column="0" lean-forward="false" selection-start-line="87" selection-start-column="0" selection-end-line="87" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="https-over-https.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/test/https-over-https.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="http-over-http.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/test/http-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1088">
<caret line="64" column="26" lean-forward="true" selection-start-line="64" selection-start-column="26" selection-end-line="64" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="CHANGELOG.md" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/CHANGELOG.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="102">
<caret line="6" column="0" lean-forward="false" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</first_editor>
<second_editor />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
</list>
</option>
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>max</find>
<find>onconne</find>
</findStrings>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/test/http-over-http-error.js" />
<option value="$PROJECT_DIR$/README.md" />
<option value="$PROJECT_DIR$/package.json" />
<option value="$PROJECT_DIR$/test/http-over-http-error2.js" />
<option value="$PROJECT_DIR$/test/https-over-http-localaddress.js" />
<option value="$PROJECT_DIR$/test/https-over-http.js" />
<option value="$PROJECT_DIR$/lib/tunnel.js" />
<option value="$PROJECT_DIR$/CHANGELOG.md" />
<option value="$PROJECT_DIR$/.travis.yml" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER">
<package-json value="$PROJECT_DIR$/package.json" />
</component>
<component name="JsFlowSettings">
<service-enabled>false</service-enabled>
<exe-path />
<annotation-enable>false</annotation-enable>
<other-services-enabled>false</other-services-enabled>
<auto-save>true</auto-save>
</component>
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="NodeModulesDirectoryManager">
<handled-path value="$PROJECT_DIR$/node_modules" />
</component>
<component name="ProjectFrameBounds">
<option name="x" value="785" />
<option name="y" value="40" />
<option name="width" value="1788" />
<option name="height" value="1407" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource ProjectPane="true" />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="Scratches" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="node-tunnel" type="b2602c69:ProjectViewProjectNode" />
<item name="node-tunnel" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="node-tunnel" type="b2602c69:ProjectViewProjectNode" />
<item name="node-tunnel" type="462c0819:PsiDirectoryNode" />
<item name="lib" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="node-tunnel" type="b2602c69:ProjectViewProjectNode" />
<item name="node-tunnel" type="462c0819:PsiDirectoryNode" />
<item name="test" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="HbShouldOpenHtmlAsHb" value="" />
<property name="nodejs_interpreter_path" value="$PROJECT_DIR$/../../nvmw/v6.10.3/node" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\koichik\git\koichik\node-tunnel\test" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager">
<configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js">
<node-interpreter>project</node-interpreter>
<node-options />
<gulpfile />
<tasks />
<arguments />
<envs />
</configuration>
<configuration default="true" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application">
<method />
</configuration>
<configuration default="true" type="DartTestRunConfigurationType" factoryName="Dart Test">
<method />
</configuration>
<configuration default="true" type="JavaScriptTestRunnerJest" factoryName="Jest">
<node-interpreter value="project" />
<working-dir value="" />
<envs />
<scope-kind value="ALL" />
<method />
</configuration>
<configuration default="true" type="JavaScriptTestRunnerKarma" factoryName="Karma">
<config-file value="" />
<node-interpreter value="project" />
<envs />
<method />
</configuration>
<configuration default="true" type="JavaScriptTestRunnerProtractor" factoryName="Protractor">
<config-file value="" />
<node-interpreter value="project" />
<envs />
<method />
</configuration>
<configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug">
<method />
</configuration>
<configuration default="true" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" working-dir="">
<method />
</configuration>
<configuration default="true" type="cucumber.js" factoryName="Cucumber.js">
<option name="cucumberJsArguments" value="" />
<option name="executablePath" />
<option name="filePath" />
<method />
</configuration>
<configuration default="true" type="js.build_tools.npm" factoryName="npm">
<command value="run" />
<scripts />
<node-interpreter value="project" />
<envs />
<method />
</configuration>
<configuration default="true" type="mocha-javascript-test-runner" factoryName="Mocha">
<node-interpreter>project</node-interpreter>
<node-options />
<working-directory />
<pass-parent-env>true</pass-parent-env>
<envs />
<ui />
<extra-mocha-options />
<test-kind>DIRECTORY</test-kind>
<test-directory />
<recursive>false</recursive>
<method />
</configuration>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="3caed8aa-31ae-4b3d-ad18-6f9796663516" name="Default" comment="" />
<created>1497256565348</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1497256565348</updated>
<workItem from="1497256566573" duration="8794000" />
<workItem from="1497272051717" duration="2328000" />
<workItem from="1536577850117" duration="8708000" />
<workItem from="1536636907096" duration="739000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="20569000" />
</component>
<component name="ToolWindowManager">
<frame x="785" y="40" width="1788" height="1407" extended-state="0" />
<editor active="true" />
<layout>
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="SvgViewer" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.32967034" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="npm" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" column="19" lean-forward="false" selection-start-line="2" selection-start-column="19" selection-end-line="2" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="2312">
<caret line="136" column="67" lean-forward="false" selection-start-line="136" selection-start-column="67" selection-end-line="136" selection-end-column="67" />
<folding>
<marker date="1497272379133" expanded="true" signature="590:646" ph="{...}" />
<marker date="1497272379133" expanded="true" signature="601:644" ph="{&quot;host&quot;: 'localhost'...}" />
<marker date="1497272379133" expanded="true" signature="674:737" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="884:1330" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="964:1328" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1103:1192" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1290:1324" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="1357:1419" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="1514:2209" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="1540:1623" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1842:2207" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1981:2070" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2168:2202" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="2237:2300" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="2395:3180" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="2475:3178" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="2615:2704" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2802:2836" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="3207:3269" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="3366:4398" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="3392:3475" ph="//..." />
<marker date="1497272379133" expanded="true" signature="3694:4396" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="3834:3923" ph="//..." />
<marker date="1497272379133" expanded="true" signature="4021:4055" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="4426:4489" ph="{&quot;host&quot;: 'example.com'...}" />
</folding>
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/.travis.yml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="6" column="0" lean-forward="true" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http-error.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="935">
<caret line="55" column="26" lean-forward="true" selection-start-line="55" selection-start-column="26" selection-end-line="55" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http-error2.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1207">
<caret line="71" column="0" lean-forward="false" selection-start-line="71" selection-start-column="0" selection-end-line="71" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1479">
<caret line="87" column="0" lean-forward="false" selection-start-line="87" selection-start-column="0" selection-end-line="87" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-https.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1088">
<caret line="64" column="26" lean-forward="true" selection-start-line="64" selection-start-column="26" selection-end-line="64" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/tunnel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="697">
<caret line="41" column="19" lean-forward="false" selection-start-line="41" selection-start-column="19" selection-end-line="41" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" column="19" lean-forward="false" selection-start-line="2" selection-start-column="19" selection-end-line="2" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="2312">
<caret line="136" column="67" lean-forward="false" selection-start-line="136" selection-start-column="67" selection-end-line="136" selection-end-column="67" />
<folding>
<marker date="1497272379133" expanded="true" signature="590:646" ph="{...}" />
<marker date="1497272379133" expanded="true" signature="601:644" ph="{&quot;host&quot;: 'localhost'...}" />
<marker date="1497272379133" expanded="true" signature="674:737" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="884:1330" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="964:1328" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1103:1192" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1290:1324" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="1357:1419" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="1514:2209" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="1540:1623" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1842:2207" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1981:2070" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2168:2202" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="2237:2300" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="2395:3180" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="2475:3178" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="2615:2704" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2802:2836" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="3207:3269" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="3366:4398" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="3392:3475" ph="//..." />
<marker date="1497272379133" expanded="true" signature="3694:4396" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="3834:3923" ph="//..." />
<marker date="1497272379133" expanded="true" signature="4021:4055" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="4426:4489" ph="{&quot;host&quot;: 'example.com'...}" />
</folding>
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/tunnel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2550">
<caret line="150" column="0" lean-forward="false" selection-start-line="150" selection-start-column="0" selection-end-line="150" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/CHANGELOG.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="51">
<caret line="3" column="21" lean-forward="false" selection-start-line="3" selection-start-column="21" selection-end-line="3" selection-end-column="21" />
<folding />
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/.travis.yml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="119">
<caret line="7" column="0" lean-forward="true" selection-start-line="7" selection-start-column="0" selection-end-line="7" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<marker date="1497272379133" expanded="true" signature="590:646" ph="{...}" />
<marker date="1497272379133" expanded="true" signature="601:644" ph="{&quot;host&quot;: 'localhost'...}" />
<marker date="1497272379133" expanded="true" signature="674:737" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="884:1330" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="964:1328" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1103:1192" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1290:1324" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="1357:1419" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="1514:2209" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="1540:1623" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1842:2207" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1981:2070" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2168:2202" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="2237:2300" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="2395:3180" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="2475:3178" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="2615:2704" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2802:2836" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="3207:3269" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="3366:4398" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="3392:3475" ph="//..." />
<marker date="1497272379133" expanded="true" signature="3694:4396" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="3834:3923" ph="//..." />
<marker date="1497272379133" expanded="true" signature="4021:4055" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="4426:4489" ph="{&quot;host&quot;: 'example.com'...}" />
</folding>
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/.travis.yml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/tunnel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2550">
<caret line="150" column="0" lean-forward="false" selection-start-line="150" selection-start-column="0" selection-end-line="150" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-https.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-https-error.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="136">
<caret line="8" column="0" lean-forward="false" selection-start-line="7" selection-start-column="0" selection-end-line="8" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http-error.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1309">
<caret line="77" column="0" lean-forward="false" selection-start-line="77" selection-start-column="0" selection-end-line="77" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-https.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-https.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-https-error.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="2312">
<caret line="136" column="67" lean-forward="false" selection-start-line="136" selection-start-column="67" selection-end-line="136" selection-end-column="67" />
<folding>
<marker date="1497272379133" expanded="true" signature="590:646" ph="{...}" />
<marker date="1497272379133" expanded="true" signature="601:644" ph="{&quot;host&quot;: 'localhost'...}" />
<marker date="1497272379133" expanded="true" signature="674:737" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="884:1330" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="964:1328" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1103:1192" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1290:1324" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="1357:1419" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="1514:2209" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="1540:1623" ph="//..." />
<marker date="1497272379133" expanded="true" signature="1842:2207" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="1981:2070" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2168:2202" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="2237:2300" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="2395:3180" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="2475:3178" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="2615:2704" ph="//..." />
<marker date="1497272379133" expanded="true" signature="2802:2836" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="3207:3269" ph="{&quot;host&quot;: 'example.com'...}" />
<marker date="1497272379133" expanded="true" signature="3366:4398" ph="{&quot;maxSockets&quot;: poolSize...}" />
<marker date="1497272379133" expanded="true" signature="3392:3475" ph="//..." />
<marker date="1497272379133" expanded="true" signature="3694:4396" ph="{&quot;host&quot;: proxyHost...}" />
<marker date="1497272379133" expanded="true" signature="3834:3923" ph="//..." />
<marker date="1497272379133" expanded="true" signature="4021:4055" ph="{&quot;User-Agent&quot;: 'Node'...}" />
<marker date="1497272379133" expanded="true" signature="4426:4489" ph="{&quot;host&quot;: 'example.com'...}" />
</folding>
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" column="19" lean-forward="false" selection-start-line="2" selection-start-column="19" selection-end-line="2" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http-error.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="935">
<caret line="55" column="26" lean-forward="true" selection-start-line="55" selection-start-column="26" selection-end-line="55" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http-error2.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1207">
<caret line="71" column="0" lean-forward="false" selection-start-line="71" selection-start-column="0" selection-end-line="71" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-http-localaddress.js" />
<entry file="file://$PROJECT_DIR$/test/https-over-https.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/http-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1088">
<caret line="64" column="26" lean-forward="true" selection-start-line="64" selection-start-column="26" selection-end-line="64" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/test/https-over-http.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1479">
<caret line="87" column="0" lean-forward="false" selection-start-line="87" selection-start-column="0" selection-end-line="87" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/tunnel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="697">
<caret line="41" column="19" lean-forward="false" selection-start-line="41" selection-start-column="19" selection-end-line="41" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/CHANGELOG.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="102">
<caret line="6" column="0" lean-forward="false" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/.travis.yml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="6" column="0" lean-forward="true" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "4"
- "6"
- "8"
- "10"
+5180 -2568
View File
File diff suppressed because it is too large Load Diff
+17 -16
View File
@@ -21,23 +21,24 @@
"author": "Reactive Circus",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.0.0",
"@actions/exec": "^1.0.1",
"@actions/tool-cache": "^1.1.2"
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4",
"@actions/tool-cache": "^1.6.0",
"minimist": "^1.2.5"
},
"devDependencies": {
"@types/jest": "^24.0.13",
"@types/node": "^12.0.4",
"@typescript-eslint/eslint-plugin": "^2.6.1",
"@typescript-eslint/parser": "^2.6.1",
"eslint": "^6.6.0",
"eslint-config-prettier": "^6.5.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^3.1.1",
"jest": "^24.8.0",
"jest-circus": "^24.7.1",
"prettier": "^1.18.2",
"ts-jest": "^24.0.2",
"typescript": "^3.5.1"
"@types/jest": "^24.9.1",
"@types/node": "^12.12.64",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.12.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^25.5.4",
"jest-circus": "^25.5.4",
"prettier": "^1.19.1",
"ts-jest": "^26.4.1",
"typescript": "^3.9.7"
}
}
+19 -16
View File
@@ -1,25 +1,29 @@
import * as exec from '@actions/exec';
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`;
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
/**
* Creates and launches a new AVD instance with the specified configurations.
*/
export async function launchEmulator(apiLevel: number, target: string, arch: string, profile: string, emulatorOptions: string, disableAnimations: boolean): Promise<void> {
export async function launchEmulator(apiLevel: number, target: string, arch: string, profile: string, avdName: string, emulatorOptions: string, disableAnimations: boolean): Promise<void> {
// create a new AVD
if (profile.trim() !== '') {
console.log(`Creating AVD with custom profile ${profile}`);
await exec.exec(`${AVD_MANAGER_PATH} create avd --force -n test --abi "${target}/${arch}" --package "system-images;android-${apiLevel};${target};${arch}" --device "${profile}"`);
await exec.exec(`avdmanager create avd --force -n "${avdName}" --abi "${target}/${arch}" --package "system-images;android-${apiLevel};${target};${arch}" --device "${profile}"`);
} else {
console.log(`Creating AVD without custom profile.`);
await exec.exec(`sh -c \\"echo no | ${AVD_MANAGER_PATH} create avd --force -n test --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}'"`);
await exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --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 -avd test ${emulatorOptions} &"`, [], {
// turn off hardware acceleration on Linux
if (process.platform === 'linux') {
emulatorOptions += ' -accel off';
}
await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
listeners: {
stderr: (data: Buffer) => {
if (data.toString().includes('invalid command-line parameter')) {
@@ -31,14 +35,14 @@ export async function launchEmulator(apiLevel: number, target: string, arch: str
// wait for emulator to complete booting
await waitForDevice();
await exec.exec(`${ADB_PATH} shell input keyevent 82`);
await exec.exec(`adb shell input keyevent 82`);
// disable animations
if (disableAnimations) {
console.log('Disabling animations.');
await exec.exec(`${ADB_PATH} shell settings put global window_animation_scale 0.0`);
await exec.exec(`${ADB_PATH} shell settings put global transition_animation_scale 0.0`);
await exec.exec(`${ADB_PATH} shell settings put global animator_duration_scale 0.0`);
await exec.exec(`adb shell settings put global window_animation_scale 0.0`);
await exec.exec(`adb shell settings put global transition_animation_scale 0.0`);
await exec.exec(`adb shell settings put global animator_duration_scale 0.0`);
}
}
@@ -47,9 +51,9 @@ export async function launchEmulator(apiLevel: number, target: string, arch: str
*/
export async function killEmulator(): Promise<void> {
try {
await exec.exec(`${ADB_PATH} -s emulator-5554 emu kill`);
await exec.exec(`adb -s emulator-5554 emu kill`);
} catch (error) {
console.log('No emulator running on port 5554');
console.log(error.message);
}
}
@@ -57,7 +61,6 @@ export async function killEmulator(): Promise<void> {
* Wait for emulator to boot.
*/
async function waitForDevice(): Promise<void> {
const adbPath = `${process.env.ANDROID_HOME}/platform-tools/adb`;
let booted = false;
let attempts = 0;
const retryInterval = 2; // retry every 2 seconds
@@ -65,7 +68,7 @@ async function waitForDevice(): Promise<void> {
while (!booted) {
try {
let result = '';
await exec.exec(`${adbPath} shell getprop sys.boot_completed`, [], {
await exec.exec(`adb shell getprop sys.boot_completed`, [], {
listeners: {
stdout: (data: Buffer) => {
result += data.toString();
@@ -78,7 +81,7 @@ async function waitForDevice(): Promise<void> {
break;
}
} catch (error) {
console.log(error.message);
console.warn(error.message);
}
if (attempts < maxAttemps) {
+7 -1
View File
@@ -1,4 +1,4 @@
export const MIN_API_LEVEL = 21;
export const MIN_API_LEVEL = 15;
export const VALID_TARGETS: Array<string> = ['default', 'google_apis'];
export const VALID_ARCHS: Array<string> = ['x86', 'x86_64'];
@@ -28,3 +28,9 @@ export function checkDisableAnimations(disableAnimations: string): void {
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
}
}
export function checkEmulatorBuild(emulatorBuild: string): void {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
}
}
+65 -14
View File
@@ -1,14 +1,21 @@
import * as core from '@actions/core';
import { installAndroidSdk } from './sdk-installer';
import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations } from './input-validator';
import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations, checkEmulatorBuild } from './input-validator';
import { launchEmulator, killEmulator } from './emulator-manager';
import * as exec from '@actions/exec';
import { parseScript } from './script-parser';
async function run() {
try {
// only support running on macOS
// only support running on macOS or Linux
if (process.platform !== 'darwin') {
throw new Error('This action is expected to be run within a macOS virtual machine to enable hardware acceleration.');
if (process.platform === 'linux') {
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.`
);
} else {
throw new Error('Unsupported virtual machine: please use either macos or ubuntu VM.');
}
}
// API level of the platform and system image
@@ -25,12 +32,16 @@ async function run() {
// CPU architecture of the system image
const arch = core.getInput('arch');
checkArch(arch);
console.log(`CPI architecture: ${arch}`);
console.log(`CPU architecture: ${arch}`);
// Hardware profile used for creating the AVD
const profile = core.getInput('profile');
console.log(`Hardware profile: ${profile}`);
// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);
// emulator options
const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`);
@@ -41,18 +52,58 @@ async function run() {
const disableAnimations = disableAnimationsInput === 'true';
console.log(`disable animations: ${disableAnimations}`);
// custom scrpt to run
const script = core.getInput('script', { required: true });
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
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;
// custom script to run
const scriptInput = core.getInput('script', { required: true });
const scripts = parseScript(scriptInput);
console.log(`Script:`);
scripts.forEach(async (script: string) => {
console.log(`${script}`);
});
// install SDK
await installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion);
// launch an emulator
await launchEmulator(apiLevel, target, arch, profile, avdName, emulatorOptions, disableAnimations);
// execute the custom script
try {
// install SDK
await installAndroidSdk(apiLevel, target, arch);
// launch an emulator
await launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations);
// execute the custom script
await exec.exec(`${script}`);
// move to custom working directory if set
if (workingDirectory) {
process.chdir(workingDirectory);
}
for (const script of scripts) {
await exec.exec(`sh -c \\"${script}"`);
}
} catch (error) {
core.setFailed(error.message);
}
+18
View File
@@ -0,0 +1,18 @@
/**
* Convert a (potentially multi-line) script to an array of single-line script(s).
*/
export function parseScript(rawScript: string): Array<string> {
const scripts: Array<string> = rawScript
.trim()
.split(/\r\n|\n|\r/)
.map((value: string) => value.trim())
.filter((value: string) => {
return !value.startsWith('#') && value.length > 0;
});
if (scripts.length == 0) {
throw new Error(`No valid script found.`);
}
return scripts;
}
+61 -6
View File
@@ -1,15 +1,70 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as fs from 'fs';
const BUILD_TOOLS_VERSION = '29.0.2';
const BUILD_TOOLS_VERSION = '30.0.2';
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-6609375_latest.zip';
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip';
/**
* 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.
*/
export async function installAndroidSdk(apiLevel: number, target: string, arch: string): Promise<void> {
const sdkmangerPath = `${process.env.ANDROID_HOME}/tools/bin/sdkmanager`;
console.log('Installing latest build tools, platform tools, platform, and emulator.');
await exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' emulator > /dev/null"`);
export async function installAndroidSdk(apiLevel: number, target: string, arch: string, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise<void> {
const isOnMac = process.platform === 'darwin';
if (!isOnMac) {
await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
}
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;
await io.mkdirP(`${process.env.ANDROID_HOME}/cmdline-tools`);
await exec.exec(`curl -fo commandlinetools.zip ${sdkUrl}`);
await exec.exec(`unzip -q commandlinetools.zip -d ${cmdlineToolsPath}`);
await io.rmRF('commandlinetools.zip');
// add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/tools:${cmdlineToolsPath}/tools/bin:${process.env.ANDROID_HOME}/platform-tools`);
}
// additional permission and license requirements for Linux
const sdkPreviewLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-preview-license`;
if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {
fs.writeFileSync(sdkPreviewLicensePath, '\n84831b9409646a918e30573bab4c9c91346d8abd');
}
// license required for API 30 system images
const sdkArmDbtLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-arm-dbt-license`;
if (apiLevel == 30 && !fs.existsSync(sdkArmDbtLicensePath)) {
fs.writeFileSync(sdkArmDbtLicensePath, '\n859f317696f67ef3d7f30a50a5560e7834b43903');
}
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"`);
if (emulatorBuild) {
console.log(`Installing emulator build ${emulatorBuild}.`);
await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}-${emulatorBuild}.zip`);
await io.rmRF(`${process.env.ANDROID_HOME}/emulator`);
await exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`);
await io.rmRF('emulator.zip');
} else {
console.log('Installing latest emulator.');
await exec.exec(`sh -c \\"sdkmanager --install emulator > /dev/null"`);
}
console.log('Installing system images.');
await exec.exec(`sh -c \\"${sdkmangerPath} --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`);
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`);
if (ndkVersion) {
console.log(`Installing NDK ${ndkVersion}.`);
await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' > /dev/null"`);
}
if (cmakeVersion) {
console.log(`Installing CMake ${cmakeVersion}.`);
await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' > /dev/null"`);
}
}
+1
View File
@@ -0,0 +1 @@
/build
+36
View File
@@ -0,0 +1,36 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.testapp"
minSdkVersion 15
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package com.example.testapp
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.testapp", appContext.packageName)
}
}
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.example.testapp.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,12 @@
package com.example.testapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Test Project</string>
</resources>
@@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
+27
View File
@@ -0,0 +1,27 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.4.0'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+39
View File
@@ -0,0 +1,39 @@
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
# Enable Kotlin incremental compilation
kotlin.incremental=true
# Enable parallel tasks execution for Kotlin Gradle plugin
kotlin.parallel.tasks.in.project=true
# Kotlin code style
kotlin.code.style=official
# Run kapt directly using Gradle workers
kapt.use.worker.api=true
# Enable incremental annotation processor for KAPT
kapt.incremental.apt=true
# Turn off AP discovery in compile path to enable compile avoidance
kapt.include.compile.classpath=false
# Use R8 instead of ProGuard for code shrinking.
android.enableR8.fullMode=true
# Enable gradle workers
android.enableGradleWorkers=true
# Enable AndroidX
android.useAndroidX=true
# Enable rudimentary R class namespacing where each library only contains
# references to the resources it declares instead of declarations plus all
# transitive dependency references.
android.namespacedRClass=true
# Only keep the single relevant constructor for types mentioned in XML files
# instead of using a parameter wildcard which keeps them all.
android.useMinimalKeepRules=true
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Tue Nov 26 18:34:05 AEDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-rc-1-all.zip
Vendored Executable
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
+84
View File
@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+10
View File
@@ -0,0 +1,10 @@
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/Users/Yang/Library/Android/sdk
+2
View File
@@ -0,0 +1,2 @@
rootProject.name='Test Project'
include ':app'