mirror of
https://github.com/expo/turtle.git
synced 2026-11-14 19:12:55 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ebf6c1d2c |
No files matched your search
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": {
|
||||
"node": "8.9"
|
||||
}
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
["module-resolver", {
|
||||
"alias": {
|
||||
"turtle": "./build"
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
+12
-268
@@ -1,281 +1,25 @@
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
nix: &nix
|
||||
docker:
|
||||
- image: lnl7/nix:2.2
|
||||
# Symlink to the readline-enabled bash which is the default command of the container
|
||||
shell: /run/current-system/sw/bin/bash -leo pipefail
|
||||
working_directory: ~/turtle
|
||||
mac:
|
||||
macos: # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions
|
||||
xcode: '10.1.0'
|
||||
working_directory: /Users/distiller/project
|
||||
shell: /bin/bash -leo pipefail
|
||||
android:
|
||||
<<: *nix
|
||||
resource_class: xlarge
|
||||
|
||||
commands:
|
||||
install_nix:
|
||||
steps:
|
||||
- run:
|
||||
name: Install nix
|
||||
command: curl https://nixos.org/releases/nix/nix-2.2.1/install | bash -s -- --no-daemon
|
||||
- run:
|
||||
name: Add nix-initializing line to .bashrc
|
||||
command: echo ". ~/.nix-profile/etc/profile.d/nix.sh" >> ~/.bashrc
|
||||
|
||||
setup:
|
||||
steps:
|
||||
- run:
|
||||
name: Install common packages
|
||||
command: |
|
||||
nix-env -iA \
|
||||
nixpkgs.binutils \
|
||||
nixpkgs.curl \
|
||||
nixpkgs.direnv \
|
||||
nixpkgs.findutils \
|
||||
nixpkgs.gawk \
|
||||
nixpkgs.git \
|
||||
nixpkgs.gnused \
|
||||
nixpkgs.gnutar \
|
||||
nixpkgs.gzip \
|
||||
nixpkgs.openssh \
|
||||
nixpkgs.python \
|
||||
nixpkgs.xz
|
||||
- run:
|
||||
name: Configure direnv
|
||||
command: |
|
||||
mkdir -p ~/.config/direnv
|
||||
echo -e "[whitelist]\nprefix = [ \"$HOME\" ]" > ~/.config/direnv/config.toml
|
||||
echo 'eval "$(direnv export bash)"' >> ~/.bash_profile
|
||||
- run:
|
||||
name: Configure yarn
|
||||
command: echo '--frozen-lockfile true' >> ~/.yarnrc
|
||||
- checkout
|
||||
- run:
|
||||
name: Configure aws-cli
|
||||
command: |
|
||||
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
|
||||
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
|
||||
|
||||
setup_nix:
|
||||
steps:
|
||||
- run:
|
||||
name: Add cp and rm stubs
|
||||
command: |
|
||||
cat \<< 'EOF' >> /bin/cp
|
||||
#! /usr/bin/env bash
|
||||
/usr/bin/env cp "$@"
|
||||
EOF
|
||||
chmod +x /bin/cp
|
||||
|
||||
cat \<< 'EOF' >> /bin/rm
|
||||
#! /usr/bin/env bash
|
||||
/usr/bin/env rm "$@"
|
||||
EOF
|
||||
chmod +x /bin/rm
|
||||
|
||||
decrypt_secrets_if_possible:
|
||||
steps:
|
||||
- run:
|
||||
name: Decrypt secrets if possible
|
||||
command: |
|
||||
if [[ -z "$TURTLE_GIT_CRYPT_KEY_BASE64" ]]; then
|
||||
echo 'git-crypt key not present in environment'
|
||||
else
|
||||
git crypt unlock <(echo $TURTLE_GIT_CRYPT_KEY_BASE64 | base64 --decode)
|
||||
fi
|
||||
|
||||
restore_yarn_cache:
|
||||
parameters:
|
||||
workingdir:
|
||||
type: string
|
||||
default: .
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: yarn-v1-{{ checksum "<< parameters.workingdir >>/yarn.lock" }}
|
||||
|
||||
save_yarn_cache:
|
||||
parameters:
|
||||
workingdir:
|
||||
type: string
|
||||
default: .
|
||||
steps:
|
||||
- save_cache:
|
||||
key: yarn-v1-{{ checksum "<< parameters.workingdir >>/yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn # `yarn cache dir`
|
||||
|
||||
restore_workingdir_cache:
|
||||
parameters:
|
||||
platform:
|
||||
type: string
|
||||
default: ios
|
||||
sdk_version:
|
||||
type: string
|
||||
default: sdk32
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: "workingdir-v1-<< parameters.platform >>-<< parameters.sdk_version >>"
|
||||
|
||||
save_workingdir_cache:
|
||||
parameters:
|
||||
workingdir:
|
||||
type: string
|
||||
default: /Users/distiller/project/workingdir
|
||||
platform:
|
||||
type: string
|
||||
default: ios
|
||||
sdk_version:
|
||||
type: string
|
||||
default: sdk32
|
||||
steps:
|
||||
- save_cache:
|
||||
key: "workingdir-v1-<< parameters.platform >>-<< parameters.sdk_version >>"
|
||||
paths:
|
||||
- "<< parameters.workingdir >>/<< parameters.platform >>/<< parameters.sdk_version >>"
|
||||
|
||||
deploy_turtle:
|
||||
parameters:
|
||||
platform:
|
||||
type: string
|
||||
env:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install curl & jq
|
||||
command: nix-env -iA nixpkgs.curl nixpkgs.jq
|
||||
- run:
|
||||
name: Schedule a Turtle deploy
|
||||
command: |
|
||||
echo "We're going to deploy << parameters.platform >> Turtles to << parameters.env >>"
|
||||
./scripts/deploy.sh "<< parameters.platform >>" "<< parameters.env >>"
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
executor: nix
|
||||
docker:
|
||||
- image: circleci/node:8.12
|
||||
steps:
|
||||
- setup
|
||||
- restore_yarn_cache
|
||||
- checkout
|
||||
- run: yarn
|
||||
- save_yarn_cache
|
||||
- run: yarn lint
|
||||
- run: yarn build
|
||||
|
||||
unit_tests:
|
||||
executor: nix
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/node:8.12
|
||||
steps:
|
||||
- setup
|
||||
- restore_yarn_cache
|
||||
- checkout
|
||||
- run: yarn
|
||||
- save_yarn_cache
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: ./scripts/tests/runUnitTestsCI.sh
|
||||
|
||||
ios_smoke_tests:
|
||||
executor: mac
|
||||
steps:
|
||||
- install_nix
|
||||
- setup
|
||||
- decrypt_secrets_if_possible
|
||||
- restore_yarn_cache
|
||||
- run: yarn
|
||||
- save_yarn_cache
|
||||
- restore_workingdir_cache:
|
||||
sdk_version: sdk32
|
||||
platform: ios
|
||||
- run:
|
||||
name: Run iOS smoke tests
|
||||
command: ./scripts/tests/runIOSSmokeTestsCI.sh
|
||||
- save_workingdir_cache:
|
||||
workingdir: /Users/distiller/project/workingdir
|
||||
sdk_version: sdk32
|
||||
platform: ios
|
||||
|
||||
android_smoke_tests:
|
||||
executor: android
|
||||
steps:
|
||||
- setup
|
||||
- setup_nix
|
||||
- restore_yarn_cache
|
||||
- run: yarn
|
||||
- save_yarn_cache
|
||||
- restore_workingdir_cache:
|
||||
sdk_version: sdk32
|
||||
platform: android
|
||||
- run:
|
||||
name: Fetch Android shell app
|
||||
command: SDK_VERSION=sdk32 ./scripts/android/fetchRemoteAndroidTarball.sh
|
||||
- run:
|
||||
name: Run Android smoke tests
|
||||
command: |
|
||||
nix-shell nix/android-shell.nix --pure \
|
||||
--run "./scripts/tests/runAndroidSmokeTestsCI.sh"
|
||||
- save_workingdir_cache:
|
||||
workingdir: ~/turtle/workingdir
|
||||
sdk_version: sdk32
|
||||
platform: android
|
||||
|
||||
android_staging_deploy:
|
||||
executor: nix
|
||||
steps:
|
||||
- deploy_turtle:
|
||||
platform: android
|
||||
env: staging
|
||||
|
||||
android_production_deploy:
|
||||
executor: nix
|
||||
steps:
|
||||
- deploy_turtle:
|
||||
platform: android
|
||||
env: production
|
||||
|
||||
ios_staging_deploy:
|
||||
executor: nix
|
||||
steps:
|
||||
- deploy_turtle:
|
||||
platform: ios
|
||||
env: staging
|
||||
|
||||
ios_production_deploy:
|
||||
executor: nix
|
||||
steps:
|
||||
- deploy_turtle:
|
||||
platform: ios
|
||||
env: production
|
||||
- run: ./scripts/testUnitCI.sh
|
||||
|
||||
workflows:
|
||||
checks:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build
|
||||
tests:
|
||||
jobs:
|
||||
- unit_tests
|
||||
- ios_smoke_tests
|
||||
- android_smoke_tests
|
||||
deploy:
|
||||
jobs:
|
||||
- android_staging_approve_deploy:
|
||||
type: approval
|
||||
- android_staging_deploy:
|
||||
requires:
|
||||
- android_staging_approve_deploy
|
||||
- android_production_approve_deploy:
|
||||
type: approval
|
||||
- android_production_deploy:
|
||||
requires:
|
||||
- android_production_approve_deploy
|
||||
- ios_staging_approve_deploy:
|
||||
type: approval
|
||||
- ios_staging_deploy:
|
||||
requires:
|
||||
- ios_staging_approve_deploy
|
||||
- ios_production_approve_deploy:
|
||||
type: approval
|
||||
- ios_production_deploy:
|
||||
requires:
|
||||
- ios_production_approve_deploy
|
||||
- test
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
source_local() {
|
||||
file=./.envrc.local
|
||||
@@ -8,6 +8,7 @@ source_local() {
|
||||
watch_file $file
|
||||
}
|
||||
|
||||
source_up || true
|
||||
source_local
|
||||
|
||||
export NODE_ENV=development
|
||||
@@ -15,6 +16,3 @@ export NODE_ENV=development
|
||||
export EXPO_TURTLE_TOOLS_DIR="$(pwd)/tools"
|
||||
|
||||
PATH_add $EXPO_TURTLE_TOOLS_DIR/bin
|
||||
if [ "$CI" = "true" ]; then
|
||||
PATH_add $EXPO_TURTLE_TOOLS_DIR/ci/bin
|
||||
fi
|
||||
+1
-4
@@ -1,13 +1,10 @@
|
||||
!/src/node_modules
|
||||
|
||||
node_modules
|
||||
yarn-error.log
|
||||
google-cloud-credentials.json
|
||||
|
||||
/build/
|
||||
/workingdir/*
|
||||
/artifacts/*
|
||||
shell.apk
|
||||
node_modules
|
||||
.secrets*
|
||||
|
||||
*.swp
|
||||
|
||||
@@ -2,6 +2,7 @@ LOGGER_LEVEL="debug"
|
||||
API_PROTOCOL="http"
|
||||
API_HOSTNAME="localhost"
|
||||
API_PORT="3000"
|
||||
LOGGLY_TOKEN=""
|
||||
SENTRY_DSN=""
|
||||
ENVIRONMENT="development"
|
||||
REDIS_URL="redis://localhost:6379/0"
|
||||
|
||||
+3
-170
@@ -6,185 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.8.8] - 2019-07-11
|
||||
### Fixed
|
||||
- LocalAuthentication module on iOS (fixes [expo/expo#663](https://github.com/expo/expo/issues/663)).
|
||||
|
||||
## [0.8.7] - 2019-07-05
|
||||
### Added
|
||||
- Adhoc Builds: use provisioningProfileId if specified.
|
||||
- Registering supported SDK versions in Redis.
|
||||
- Support for 64 bit builds in SDK33.
|
||||
|
||||
## [0.8.6] - 2019-06-25
|
||||
### Added
|
||||
- Added support for Android build modes: debug or release.
|
||||
### Fixed
|
||||
- Upgrading the shell app for a given SDK version when the previous version is already downloaded (bug in `turtle-cli`).
|
||||
- Updated Android shell app for SDK33 to include `[email protected]` which fixes crashes on Android 9.0 when showing some Lottie animations.
|
||||
- Schema for `buildModes`.
|
||||
- Updated @expo/xdl to 55.0.7 (fixed reading from binary plist).
|
||||
|
||||
## [0.8.4] - 2019-06-13
|
||||
### Changed
|
||||
- Upgraded `sharp` from `0.21.3` to `0.22.1`.
|
||||
- `gulp-cli` is no longer required to build an app with `turtle-cli`.
|
||||
|
||||
## [0.8.3] - 2019-06-13
|
||||
### Added
|
||||
- Running Android Turtle process with `supervisor` (pod is restarted if there were 5 failures in the last 10 minutes).
|
||||
### Changed
|
||||
- `turtle-cli` is now fetching the app manifest before running the build.
|
||||
|
||||
## [0.8.2] - 2019-06-11
|
||||
### Changed
|
||||
- Updated Android shell app for SDK33 to remove `expo-face-detector` and `expo-payments-stripe` from standalone builds.
|
||||
- Updated Android shell app for SDK33 to get rid of `node_modules` for `tools-public`.
|
||||
- From now on, `turtle-cli` installs dependencies for `tools-public` in shell apps for SDK versions < 33.
|
||||
### Fixed
|
||||
- Android builds with turtle-cli by installing all unmodules if `dependencies` is empty.
|
||||
|
||||
## [0.8.1] - 2019-06-10
|
||||
### Changed
|
||||
- Add expo dependencies when resolving unimodules.
|
||||
### Fixed
|
||||
- Support for Android tarballs without `universe-package.json`.
|
||||
|
||||
## [0.8.0] - 2019-06-07
|
||||
### Added
|
||||
- Turtle CLI logo to the project (created by [@zularizal](https://github.com/zularizal))!
|
||||
- Shell apps for SDK 33.
|
||||
- Support for Android App Bundles (App bundles are built by default, use `-t apk` to build apk).
|
||||
### Changed
|
||||
- Updated Android shell app for SDK 32 to fix AppAuth issue (https://github.com/expo/expo/pull/4115) and to fix a newer than expected JSC version.
|
||||
- Default value for build timeout to 15 minutes.
|
||||
- Added filtering of logs (strips out download progress) when downloading SDK tools.
|
||||
- `xdl` -> `@expo/xdl`, updated `@expo/xdl`.
|
||||
### Fixed
|
||||
- Generating Android keystore in turtle when one is not passed to turtle-cli (https://github.com/expo/turtle/pull/65).
|
||||
- Upload logs to s3 if build has been canceled or timed out.
|
||||
- Unhandled exception on asset download (xdl).
|
||||
|
||||
## [0.7.2] - 2019-05-09
|
||||
### Added
|
||||
- Support for Android tarballs without `universe-package.json`.
|
||||
### Changed
|
||||
- Upgraded `@expo/traveling-fastlane-darwin` to 1.9.4 (fixes issues with generating provisioning profiles for ad-hoc builds).
|
||||
### Removed
|
||||
- Support for `CLIENT_LOGGER_LEVEL` env variable.
|
||||
|
||||
## [0.7.1] - 2019-05-06
|
||||
### Fixed
|
||||
- Resolving `fastlane` version when `LC_ALL` is not set.
|
||||
|
||||
## [0.7.0] - 2019-05-06
|
||||
### Added
|
||||
- Support for custom Android builds.
|
||||
### Changed
|
||||
- Refactored logger.
|
||||
### Fixed
|
||||
- `platform` log field wrongly set to `ios` for Android builds (when using `turtle-cli`).
|
||||
- Brought back `jobID` and `experienceName` fields to Android logs.
|
||||
|
||||
## [0.6.1] - 2019-04-23
|
||||
### Added
|
||||
- Checking fastlane version before running build (turtle-cli).
|
||||
### Changed
|
||||
- Updated iOS shell app for SDK 32 to fix AppAuth issue.
|
||||
- Babel replaced by ttypescript.
|
||||
- Updated xdl to 53.5.3 and @expo/spawn-async to 1.4.2, so that error stack traces should be more informative now.
|
||||
### Fixed
|
||||
- Brought back `jobID` and `experienceName` fields to GCloud logs.
|
||||
### Removed
|
||||
- `buidAndroidTarballLocally.sh` script (it has been moved to expo repository - [expo/expo#4022](https://github.com/expo/expo/pull/4022)).
|
||||
|
||||
## [0.6.0] - 2019-04-09
|
||||
### Changed
|
||||
- Removed Branch module from Android shell apps for all SDK versions.
|
||||
|
||||
## [0.5.15] - 2019-04-03
|
||||
### Added
|
||||
- Turtle smoke tests are now being executed on CircleCI from the context of this repository.
|
||||
### Changed
|
||||
- Updated iOS shell app for SDK 32 (fixes [this loading issue for slow connections](https://github.com/expo/expo/issues/3574)).
|
||||
- Minor infra changes (doesn't affect users).
|
||||
- Error logs format - error objects are now passed in an object being passed as the first argument to logger function.
|
||||
### Removed
|
||||
- Sending logs to loggly.
|
||||
|
||||
## [0.5.14] - 2019-03-22
|
||||
### Added
|
||||
- `--config` parameter for turtle-cli commands, which allows to specify a custom path to app.json file
|
||||
### Changed
|
||||
- Updated iOS shell app for SDK 32 (fixes [this notifications issue](https://github.com/expo/expo/issues/3663)).
|
||||
|
||||
## [0.5.13] - 2019-03-12
|
||||
### Added
|
||||
- Android SDK version support validation.
|
||||
### Changed
|
||||
- iOS Enterprise builds don't compile bitcode now.
|
||||
|
||||
## [0.5.12] - 2019-02-26
|
||||
### Added
|
||||
- Google Cloud logger (as a bunyan stream).
|
||||
### Changed
|
||||
- Android builder passes the manifest from a build job message if it's available.
|
||||
|
||||
## [0.5.11] - 2019-02-22
|
||||
### Added
|
||||
- Builder for iOS client apps.
|
||||
|
||||
## [0.5.10] - 2019-02-19
|
||||
### Added
|
||||
- Registering Turtle version in Redis.
|
||||
### Changed
|
||||
- Update Android shell app for SDK 32 to fix [update](https://github.com/expo/expo/issues/3504) and [back button](https://github.com/expo/expo/issues/1786) issues.
|
||||
|
||||
## [0.5.9] - 2019-02-06
|
||||
- Update iOS shell app for SDK 32 (fixes bare notifications [issue](https://github.com/expo/expo/issues/3223)).
|
||||
|
||||
## [0.5.7] - 2019-01-31
|
||||
- Update Android shell app for SDK 32 to fix bundled assets issue.
|
||||
|
||||
## [0.5.6] - 2019-01-25
|
||||
- Update iOS shell app for SDK 30, 31 and 32 (built with xcode 10.1).
|
||||
|
||||
## [0.5.5] - 2019-01-24
|
||||
### Added
|
||||
- Sending build duration in AWS SQS messages (of `FINISHED` and `ERRORED` types).
|
||||
|
||||
## [0.5.4] - 2019-01-22
|
||||
### Changed
|
||||
- Update iOS shell app for SDK32 to [`expo/expo#0847c10`](https://github.com/expo/expo/commit/0847c1073d38c366866f3d9279ddd662da47dc9b), i.e. v2.10.3 with fixed WebView navigation scheme and upgraded Facebook SDK.
|
||||
|
||||
## [0.5.3] - 2019-01-21
|
||||
### Fixed
|
||||
- Building Android standalone apps ([not accepted license issue](https://github.com/expo/expo/issues/3266)) - updated Android build tools (`build-tools;28.0.3`, `platforms;android-28`).
|
||||
|
||||
## [0.5.2] - 2019-01-18
|
||||
### Added
|
||||
- The link (in README) to the repository containing `turtle-cli` usage example.
|
||||
- [Contributing guide](CONTRIBUTING.md).
|
||||
- [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
### Removed
|
||||
- `eslint`
|
||||
### Changed
|
||||
- Fix all `tslint` errors.
|
||||
|
||||
## [0.5.1] - 2019-01-16
|
||||
### Changed
|
||||
- Update Android shell app for SDK32 that includes `[email protected]`.
|
||||
- Update Android shell app for SDK32 that includes react-native-screens@1.0.0-alpha.22
|
||||
|
||||
## [0.5.0] - 2019-01-16
|
||||
### Added
|
||||
- Turtle version is added to the `onFinish` and `onError` build messages.
|
||||
### Changed
|
||||
- A version property value being sent to the queue. It's now the version number
|
||||
from `package.json` instead of `new`/`old` indicator.
|
||||
- Turtle version is added to onFinish and onError build messages.
|
||||
### Removed
|
||||
- `--push-p12-path` parameter for `turtle build:ios` command,
|
||||
a Push Notifications Certificate is no longer required to perform a successful
|
||||
iOS standalone app build.
|
||||
- new/old Turtle version build argument
|
||||
|
||||
## [0.4.3] - 2019-01-09
|
||||
### Changed
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# Contributing to Turtle CLI
|
||||
|
||||
## How to contribute
|
||||
You can contribute to Turtle CLI in various ways, including:
|
||||
- [Reporting bugs or issues](https://github.com/expo/turtle/issues/new) on GitHub. Please make sure to fill in all the details suggested by the template to make sure the issue can be addressed as quickly as possible.
|
||||
- [Submitting improvements to the documentation](https://github.com/expo/expo/tree/master/docs). Updates, enhancements, new guides, spelling fixes…
|
||||
- Helping other people on [Expo Forums](https://forums.expo.io/) and on [Expo Developers Slack](http://slack.expo.io).
|
||||
- Looking at existing [issues](https://github.com/expo/turtle/issues) and adding more information, particularly helping to reproduce the issues.
|
||||
- [Submitting a pull request](#submitting-a-pull-request) with a bug fix or an improvement.
|
||||
|
||||
## Turtle CLI repository
|
||||
|
||||
### Codebase
|
||||
This repository contains the codebase for the `turtle-cli` NPM package. The same codebase is used to run standalone app builders on the Expo servers.
|
||||
|
||||
### Branches
|
||||
The `master` branch of the repository should be kept releasable at any time. This way we can continuously release fixes and improvements without costly managing of different branches and issues will be noticed and fixed quickly. This also ensures other contributors can check out the latest version from GitHub and work on it with minimal disruption from other features in progress.
|
||||
|
||||
## Submitting a pull request
|
||||
To submit a pull request:
|
||||
1. Fork the [repository](https://github.com/expo/turtle) and create a feature branch. (Existing contributors can create feature branches without forking. Prefix the branch name with `@your-github-username/`.)
|
||||
2. Write the description of your pull request. Make sure to include a test plan and test your changes.
|
||||
3. Make sure all tests pass on CircleCI.
|
||||
4. Wait for a review and adjust the code if necessary.
|
||||
|
||||
## Publishing a release
|
||||
- To release a new version of `turtle-cli`, run `yarn release` command.
|
||||
- If you wish to release a new *beta* version of `turtle-cli`, run `yarn release:beta` instead.
|
||||
- Update [Changelog](CHANGELOG.md).
|
||||
- In a terminal, `cd` into [expo/turtle-cli-example](https://github.com/expo/turtle-cli-example) repository, run `yarn update-turtle-cli-version` (this script updates `turtle-cli` version number in CI configuration files and commits the changes) and then run `git push`. Thanks!
|
||||
@@ -1,35 +0,0 @@
|
||||
# Turtle Development
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Working `www` service, see [this README](https://github.com/expo/universe/tree/master/server/www/README.md) for more details.
|
||||
- Own AWS SQS queues. See [this README](https://github.com/expo/universe/tree/master/terraform/turtle-queues/README.md) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
- Run `update-local-secrets` to populate the `.secrets*` files. You'll need to run this any time any secret changes (it shouldn't be very often).
|
||||
- Run `EXPO_UNIVERSE_DIR=/path/to/universe/dir yarn secrets:init-private YOUR_SQS_NICKNAME`. Alternatively, you can create `.secrets.local-queue` file with the SQS queues' urls yourself (`AWS_SQS_OUT_QUEUE_URL`, `AWS_SQS_ANDROID_QUEUE_URL`, `AWS_SQS_ANDROID_PRIORITY_QUEUE_URL`, `AWS_SQS_IOS_QUEUE_URL`, `AWS_SQS_IOS_PRIORITY_QUEUE_URL`)
|
||||
- Run `yarn set-workingdir:local` or `yarn set-workingdir:remote`:
|
||||
- `:local` creates symlinks to the shell apps in `expo` repository (_unfortunately this is currently broken_)
|
||||
- `:remote` downloads precompiled shell apps
|
||||
- Run `yarn` to install node modules.
|
||||
- Run `yarn start:ios` or `yarn start:android` to run Turtle.
|
||||
|
||||
## Updating shell apps
|
||||
|
||||
_TODO_
|
||||
|
||||
## Generating recording files for smoke tests
|
||||
|
||||
Smoke tests are powered by [Nock](https://www.npmjs.com/package/nock) which uses JSON files containing responses to the HTTP requests fired by the build process.
|
||||
|
||||
To record new responses remove old `recording.json` and run tests and then manually schedule a build with `expo-cli` tool.
|
||||
|
||||
As a precaution, the file with HTTP requests/responses contains, among other things, iOS certificates used for the test build, is
|
||||
encrypted with `git-crypt`. Remember not to revoke them, because then the tests will fail.
|
||||
|
||||
## Other notes
|
||||
|
||||
Turtle uses Redis to get notifications (from www) about builds cancellations. To make this feature working, you have to use the same Redis instance as www. Therefore, you need to remember about starting www before starting Turtle. If you forget about it, don't worry, Turtle will remind you to run `yarn start-docker` in `server/www` directory.
|
||||
|
||||
Turtle uploads build artifacts to S3. This could take some time if you have a poor internet connection. To facilitate local development, there is a feature which allows Turtle to fake artifacts upload. `secrets:init-private` script, which you should have run while setting up Turtle, sets fake upload directory to your user's default `Downloads` directory (we're assuming you're using Mac). If you need to test uploading artifacts to S3, you have to edit `.secrets.local` file and comment or remove `TURTLE_FAKE_UPLOAD` and `TURTLE_FAKE_UPLOAD_DIR` environment variables.
|
||||
+30
-23
@@ -20,11 +20,9 @@ RUN dpkg --add-architecture i386 && \
|
||||
libc6:i386\
|
||||
libncurses5:i386\
|
||||
libstdc++6:i386\
|
||||
rsync\
|
||||
unzip\
|
||||
yarn\
|
||||
zlib1g:i386\
|
||||
supervisor\
|
||||
--no-install-recommends && \
|
||||
apt-get clean
|
||||
|
||||
@@ -38,22 +36,30 @@ RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-
|
||||
# Download and untar SDK
|
||||
ENV ANDROID_HOME /usr/local/android-sdk-linux
|
||||
ENV ANDROID_SDK /usr/local/android-sdk-linux
|
||||
ENV ANDROID_SDK_URL https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip
|
||||
ENV ANDROID_SDK_URL https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
|
||||
RUN mkdir -p ${ANDROID_HOME} && \
|
||||
curl -L "${ANDROID_SDK_URL}" > ${ANDROID_HOME}/sdk.zip && \
|
||||
cd ${ANDROID_HOME} && \
|
||||
unzip -qq sdk.zip && \
|
||||
rm sdk.zip && \
|
||||
# prevents warnings about missing repo config
|
||||
mkdir -p ${HOME}/.android && \
|
||||
touch ${HOME}/.android/repositories.cfg
|
||||
|
||||
ENV PATH ${ANDROID_HOME}/platform-tools:${PATH}
|
||||
ENV PATH ${ANDROID_HOME}/tools:${PATH}
|
||||
ENV PATH ${ANDROID_HOME}/tools/bin:${PATH}
|
||||
ENV PATH ${ANDROID_HOME}/build-tools/28.0.3/:${PATH}
|
||||
ENV PATH ${ANDROID_HOME}/build-tools/25.0.0/:${PATH}
|
||||
|
||||
ADD scripts/android/configureAndroidSdk.sh /tmp/configureAndroidSdk.sh
|
||||
RUN /tmp/configureAndroidSdk.sh --install-all-platforms
|
||||
RUN yes | sdkmanager --licenses > /dev/null
|
||||
|
||||
# Install Android SDK components
|
||||
RUN sdkmanager \
|
||||
"platform-tools" \
|
||||
"platforms;android-23" \
|
||||
"build-tools;25.0.0" \
|
||||
"extras;android;m2repository" \
|
||||
"extras;google;m2repository"
|
||||
|
||||
# Install Android NDK
|
||||
ENV ANDROID_NDK_VERSION android-ndk-r10e
|
||||
@@ -89,29 +95,30 @@ RUN rm gradle-4.4-all.zip
|
||||
ENV GRADLE_HOME /usr/local/gradle-4.4
|
||||
ENV PATH ${GRADLE_HOME}/bin:$PATH
|
||||
|
||||
ADD . /app/turtle
|
||||
# Install Gulp
|
||||
RUN npm install -g gulp-cli
|
||||
|
||||
RUN mv /app/turtle/workingdir /app && \
|
||||
for SDK_VERSION in `ls /app/workingdir/android/`; do \
|
||||
echo "preparing $SDK_VERSION shell app" && \
|
||||
# Install Git
|
||||
RUN echo 'deb http://http.debian.net/debian wheezy-backports main' >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get -t wheezy-backports install git-core && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ADD . /app
|
||||
|
||||
RUN for SDK_VERSION in `ls /app/workingdir/android/`; do \
|
||||
cd /app/workingdir/android/$SDK_VERSION && \
|
||||
if [ -f universe-package.json ]; then \
|
||||
mv package.json exponent-package.json && \
|
||||
mv universe-package.json package.json && \
|
||||
yarn install && \
|
||||
mv package.json universe-package.json && \
|
||||
mv exponent-package.json package.json; \
|
||||
else \
|
||||
yarn install; \
|
||||
fi \
|
||||
; done && \
|
||||
mv /app/workingdir /app/turtle
|
||||
mv exponent-package.json package.json \
|
||||
; done
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV "production"
|
||||
ENV TURTLE_WORKING_DIR_PATH /app/turtle/workingdir/
|
||||
ENV TURTLE_WORKING_DIR_PATH /app/workingdir/
|
||||
|
||||
WORKDIR /app/turtle
|
||||
|
||||
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
CMD ["/usr/bin/supervisord"]
|
||||
CMD ["node", "./build/server.js"]
|
||||
@@ -1,43 +1,25 @@
|
||||
<h3 align=center>
|
||||
<img src="logo/horizontal.png" width=50%>
|
||||
</h3>
|
||||
# Turtle CLI
|
||||
|
||||
[](https://circleci.com/gh/expo/turtle)
|
||||
[](https://npmjs.com/package/turtle-cli)
|
||||
[](https://github.com/expo/turtle/blob/master/LICENSE)
|
||||
|
||||
[](http://slack.expo.io)
|
||||
[](https://forums.expo.io/)
|
||||
|
||||
<hr />
|
||||
[](https://rubygems.org/gems/fastlane)
|
||||
[](https://circleci.com/gh/expo/turtle)
|
||||
|
||||
Turtle CLI is a command line interface for building Expo standalone apps.
|
||||
You can use it both on your CI and your private computer.
|
||||
|
||||
- [Changelog](CHANGELOG.md)
|
||||
- [Contributing](CONTRIBUTING.md)
|
||||
- [Development](DEVELOPMENT.md)
|
||||
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
### Glossary
|
||||
|
||||
- **Turtle** - script which takes a shell app, fills it with user's code and produces a standalone app
|
||||
- **Shell app** — precompiled (in case of iOS builds) Expo app which doesn't have user's code
|
||||
- **Standalone app** - .ipa/.apk file, a user uploads standalone apps to the Google Play Store/Apple App Store
|
||||
|
||||
## Installation
|
||||
|
||||
[Installation instructions and documentation here.](https://docs.expo.io/versions/latest/distribution/turtle-cli)
|
||||
|
||||
## Usage example
|
||||
|
||||
See the [expo/turtle-cli-example](https://github.com/expo/turtle-cli-example) repository to learn how to use Turtle CLI in order to build Expo standalone apps on your CI.
|
||||
|
||||
## Do you need help?
|
||||
|
||||
- Make sure you've read [the Expo Docs](https://docs.expo.io/), especially [Building Standalone Apps on Your CI
|
||||
](https://docs.expo.io/versions/latest/distribution/turtle-cli) and [Building Standalone Apps](https://docs.expo.io/versions/latest/distribution/building-standalone-apps) guides.
|
||||
- Search for existing GitHub issues [in this repository](https://github.com/expo/turtle/issues) and in [expo/expo](https://github.com/expo/expo/issues) repository.
|
||||
- Ask for help in **one** of the following ways:
|
||||
* create [a GitHub issue](https://github.com/expo/turtle/issues/new).
|
||||
* create a new topic on [Expo Forums](https://forums.expo.io/).
|
||||
* ask a question on [Expo Developers Slack](http://slack.expo.io).
|
||||
|
||||
## License
|
||||
|
||||
The Turtle CLI source code is made available under the [MIT license](LICENSE).
|
||||
Some of the dependencies are licensed differently, with the BSD license, for example.
|
||||
@@ -1,15 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const subcommand = process.argv[2];
|
||||
if (subcommand) {
|
||||
const [, platform] = subcommand.split(':');
|
||||
if (platform === 'ios' || platform === 'android') {
|
||||
// so that logs have correct platform set
|
||||
process.env.PLATFORM = platform;
|
||||
}
|
||||
}
|
||||
|
||||
require('source-map-support').install();
|
||||
require('../build/bin/env').initOfflineEnv();
|
||||
require('../build/bin/index').run('turtle');
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const util = require('util');
|
||||
const path = require('path');
|
||||
|
||||
const gulp = require('gulp');
|
||||
const babel = require('gulp-babel');
|
||||
const changed = require('gulp-changed');
|
||||
const nodemon = require('gulp-nodemon');
|
||||
const plumber = require('gulp-plumber');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const ts = require('gulp-typescript');
|
||||
const rimraf = require('rimraf');
|
||||
|
||||
const rimrafAsync = util.promisify(rimraf);
|
||||
|
||||
const tsProject = ts.createProject('tsconfig.json');
|
||||
|
||||
gulp.task('clean', async () => {
|
||||
console.log(`Removing build directories...`);
|
||||
await rimrafAsync('build');
|
||||
});
|
||||
|
||||
gulp.task('build:server', () => {
|
||||
console.log(`Compiling the Node.js server JavaScript...`);
|
||||
return tsProject
|
||||
.src()
|
||||
.pipe(plumber())
|
||||
.pipe(changed('build'))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsProject())
|
||||
.pipe(babel())
|
||||
.pipe(
|
||||
sourcemaps.write('.', {
|
||||
includeContent: false,
|
||||
sourceRoot: '../src',
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest('build'));
|
||||
});
|
||||
|
||||
gulp.task('watch:source', async () => {
|
||||
console.log(`Watching for changes to the Node.js server JavaScript...`);
|
||||
gulp.watch('src/**/*.(t|j)s', gulp.series('build:server'));
|
||||
});
|
||||
|
||||
gulp.task('watch:local', createWatchTask('development'));
|
||||
gulp.task('watch:server', createWatchTask('production'));
|
||||
|
||||
function createWatchTask(nodeEnv) {
|
||||
return async () => {
|
||||
console.log(`Starting the Node.js server and watching for changes (NODE_ENV=${nodeEnv})...`);
|
||||
// Run nodemon in a separate process to isolate its side effects on stdio streams
|
||||
let nodeCmd = 'node';
|
||||
if (nodeEnv === 'development') {
|
||||
nodeCmd += ' --inspect=:9315';
|
||||
}
|
||||
nodemon({
|
||||
script: './build/server.js',
|
||||
execMap: {
|
||||
js: nodeCmd,
|
||||
},
|
||||
env: {
|
||||
NODE_ENV: nodeEnv,
|
||||
},
|
||||
|
||||
watch: ['./build'],
|
||||
stdout: false,
|
||||
readable: false,
|
||||
}).on('readable', function() {
|
||||
const bunyan = childProcess.fork(
|
||||
path.join('.', 'node_modules', '.bin', 'bunyan'),
|
||||
['-o', 'short', '-l', 'debug', '--color'],
|
||||
{ silent: true }
|
||||
);
|
||||
|
||||
bunyan.stdout.pipe(process.stdout);
|
||||
bunyan.stderr.pipe(process.stderr);
|
||||
this.stdout.pipe(bunyan.stdin);
|
||||
this.stderr.pipe(bunyan.stdin);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
gulp.task(
|
||||
'watch',
|
||||
gulp.series('clean', 'build:server', gulp.parallel('watch:source', 'watch:local'))
|
||||
);
|
||||
|
||||
gulp.task('default', gulp.series('watch'));
|
||||
@@ -9,6 +9,6 @@
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(test|spec)\\.ts$",
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"moduleFileExtensions": ["ts", "js"],
|
||||
"verbose": true
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testMatch": [ "**/__smoke__/*.test.ts" ],
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"moduleFileExtensions": ["ts", "js"],
|
||||
"verbose": true
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testMatch": [ "**/__tests__/*.ts" ],
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"moduleFileExtensions": ["ts", "js"],
|
||||
"verbose": true
|
||||
}
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
environment: ${NAMESPACE}
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 1200 ## wait 20 minutes for any builds to complete
|
||||
terminationGracePeriodSeconds: 1800 ## wait 30 minutes for any builds to complete
|
||||
tolerations:
|
||||
- key: dedicated
|
||||
operator: Equal
|
||||
@@ -50,6 +50,8 @@ spec:
|
||||
value: exp-shell-app-assets
|
||||
- name: AWS_S3_REGION
|
||||
value: us-west-1
|
||||
- name: LOGGLY_SUBDOMAIN
|
||||
value: exponent
|
||||
- name: TURTLE_TEMPORARY_FILES_DIR
|
||||
value: /tmp/turtle
|
||||
# vars from k8s secrets
|
||||
@@ -98,6 +100,11 @@ spec:
|
||||
secretKeyRef:
|
||||
name: turtle-js-config
|
||||
key: aws_cloudwatch_region
|
||||
- name: LOGGLY_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: turtle-js-config
|
||||
key: loggly_token
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -128,16 +135,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: turtle-js-config
|
||||
key: api_port
|
||||
- name: DATADOG_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: turtle-js-config
|
||||
key: datadog_api_key
|
||||
- name: DATADOG_APP_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: turtle-js-config
|
||||
key: datadog_app_key
|
||||
# other vars
|
||||
- name: GRADLE_DAEMON_DISABLED
|
||||
value: "1"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB |
@@ -1,228 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="151.68256mm"
|
||||
height="150.14914mm"
|
||||
viewBox="0 0 151.68255 150.14914"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="logo-only.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<defs
|
||||
id="defs2">
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath16">
|
||||
<path
|
||||
d="M 0,192 H 192 V 0 H 0 Z"
|
||||
id="path18"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath26">
|
||||
<path
|
||||
d="M -4.88281e-4,192 H 192 V -4.88281e-4 H -4.88281e-4 Z"
|
||||
id="path28"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath34">
|
||||
<path
|
||||
d="M -4.88281e-4,192 H 192 V -4.88281e-4 H -4.88281e-4 Z"
|
||||
id="path36"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath38">
|
||||
<path
|
||||
d="M 0,192 H 192 V 0 H 0 Z"
|
||||
id="path40"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath42">
|
||||
<path
|
||||
d="M 0,192 H 192 V 0 H 0 Z"
|
||||
id="path44"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath116">
|
||||
<path
|
||||
d="M 0,192 H 192 V -4.88281e-4 H 0 Z"
|
||||
id="path118"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath120">
|
||||
<path
|
||||
d="M 0,192 H 192 V 4.88281e-4 H 0 Z"
|
||||
id="path122"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath124">
|
||||
<path
|
||||
d="M 192,192 H 0 V 0 h 192 z"
|
||||
id="path126"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#3d3d3d"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.49497474"
|
||||
inkscape:cx="-192.6803"
|
||||
inkscape:cy="237.87441"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false"
|
||||
units="mm"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:object-paths="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
transform="translate(-29.1587,91.282943)">
|
||||
<g
|
||||
id="g6678"
|
||||
transform="translate(792.65276,-237.50395)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6638"
|
||||
d="m -687.31547,152.53653 -16.98554,9.86968 v 19.204 l 15.52774,-8.98033 a 2.9187589,2.9239263 0 0 1 1.52654,-0.38809 2.9187589,2.9239263 0 0 1 1.39009,0.38809 l 15.52721,8.98033 v -19.204 z"
|
||||
style="opacity:1;fill:#00002f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6640"
|
||||
d="m -687.3165,178.53959 -48.20947,27.88306 v 55.7661 l 48.20947,27.88306 48.20996,-27.88306 v -55.7661 z"
|
||||
style="opacity:1;fill:#00002f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6642"
|
||||
d="m -738.44361,184.99346 -16.985,9.86968 v 19.74092 l 14.06631,8.17418 v -18.04386 a 2.9187589,2.9239263 0 0 1 1.45986,-2.5337 l 15.57269,-9.00617 z"
|
||||
style="opacity:1;fill:#00002f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6644"
|
||||
d="m -634.61997,184.99346 -14.89522,8.65426 14.78772,8.55296 a 2.9187589,2.9239263 0 0 1 1.45986,2.5337 v 18.95491 l 15.63418,-9.08523 v -19.74092 z"
|
||||
style="opacity:1;fill:#00002f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6646"
|
||||
d="m -741.3623,245.97166 -16.30752,9.47642 v 19.74091 l 16.98398,9.87124 16.47753,-9.57513 -15.69413,-9.07748 a 2.9187589,2.9239263 0 0 1 -1.45986,-2.5306 z"
|
||||
style="opacity:1;fill:#00002f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6648"
|
||||
d="m -633.26761,247.66664 v 16.21038 a 2.9187589,2.9239263 0 0 1 -1.45986,2.5306 l -17.35915,10.0397 v 5.2e-4 l 0.20309,-0.11782 15.02182,8.73022 16.98655,-9.87124 v -19.74091 z"
|
||||
style="opacity:1;fill:#00002f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.32291651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
<g
|
||||
id="g6654"
|
||||
transform="translate(-172.35714,278.19048)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6650"
|
||||
d="m -514.88984,-105.94886 a 2.918759,2.9239263 0 0 0 -1.52625,0.388 l -51.12915,29.57121 a 2.918759,2.9239263 0 0 0 -1.45977,2.5338 v 59.142302 a 2.918759,2.9239263 0 0 0 1.45977,2.530901 l 51.12915,29.571197 a 2.918759,2.9239263 0 0 0 2.91661,0 l 51.12917,-29.571197 a 2.918759,2.9239263 0 0 0 1.45977,-2.530901 V -73.45585 a 2.918759,2.9239263 0 0 0 -1.45977,-2.5338 l -51.12917,-29.57121 a 2.918759,2.9239263 0 0 0 -1.39036,-0.388 z m -0.0694,6.298204 48.20963,27.882906 v 55.766001 l -48.20963,27.883001 -48.20965,-27.883001 V -71.76775 Z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17500019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6652"
|
||||
d="m -514.88885,-85.717946 a 2.9118515,2.9308623 0 0 0 -1.52531,0.3904 l -33.47339,19.451796 a 2.9118515,2.9308623 0 0 0 -1.45528,2.5391 v 38.903402 a 2.9118515,2.9308623 0 0 0 1.45528,2.537198 l 33.47339,19.4517023 a 2.9118515,2.9308623 0 0 0 2.91246,0 l 33.47151,-19.4517023 a 2.9118515,2.9308623 0 0 0 1.45528,-2.537198 V -63.33665 a 2.9118515,2.9308623 0 0 0 -1.45528,-2.5391 l -33.47151,-19.451796 a 2.9118515,2.9308623 0 0 0 -1.38715,-0.3904 z m -0.0699,6.312396 30.56093,17.760299 V -26.12465 L -514.95875,-8.3663467 -545.5197,-26.12665 v -35.518601 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17500019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
<g
|
||||
id="g6660"
|
||||
transform="translate(-172.35714,278.19048)">
|
||||
<path
|
||||
id="path6656"
|
||||
d="m -519.66912,-32.288849 -5.84307,5.844 h 25.07044 a 2.9220001,2.9220001 0 0 0 0,-5.844 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#45eba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17499971;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6658"
|
||||
d="m -529.50808,-61.355249 a 2.9213416,2.9213416 0 0 0 -2.0341,5.017 l 12.45256,12.452501 -12.45256,12.452599 a 2.9213416,2.9213416 0 1 0 4.1329,4.129201 l 14.51539,-14.515401 a 2.9213416,2.9213416 0 0 0 0,-4.1328 l -14.51539,-14.515401 a 2.9213416,2.9213416 0 0 0 -2.0988,-0.887699 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#45eba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17500019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
<g
|
||||
id="g6676"
|
||||
transform="translate(-172.35714,278.19048)">
|
||||
<g
|
||||
id="g6672">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17499971;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m -514.89008,-131.96866 a 2.9118515,2.9308623 0 0 0 -1.52426,0.3917 l -19.89585,11.5625 a 2.9118515,2.9308623 0 0 0 -1.45673,2.5388 v 23.125004 a 2.9118515,2.9308623 0 0 0 0.18924,1.02881 l 5.63388,-3.25861 v -19.203604 l 16.98527,-9.87 16.98619,9.87 v 19.203604 l 5.6348,3.25861 a 2.9118515,2.9308623 0 0 0 0.18734,-1.02881 v -23.125004 a 2.9118515,2.9308623 0 0 0 -1.4558,-2.5388 l -19.89678,-11.5625 a 2.9118515,2.9308623 0 0 0 -1.3873,-0.3917 z"
|
||||
id="path6662" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17499971;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m -566.01799,-99.511956 a 2.9118515,2.9308623 0 0 0 -1.52421,0.3918 l -19.89585,11.56251 a 2.9118515,2.9308623 0 0 0 -1.45673,2.5387 v 23.124998 a 2.9118515,2.9308623 0 0 0 1.45673,2.537899 l 18.43343,10.7124 v -6.768201 l -14.06704,-8.174598 v -19.740898 l 16.98522,-9.8699 14.11368,8.2012 5.83733,-3.3766 -18.49521,-10.74751 a 2.9118515,2.9308623 0 0 0 -1.38735,-0.3918 z"
|
||||
id="path6664" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17499971;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m -462.19425,-99.511956 a 2.9118515,2.9308623 0 0 0 -1.52421,0.3918 l -19.27591,11.20211 5.83641,3.3755 14.89526,-8.6547 16.98619,9.8699 v 19.740898 l -15.63409,9.085498 v 6.765401 a 2.9118515,2.9308623 0 0 0 0.10365,-0.058 l 19.89775,-11.5625 a 2.9118515,2.9308623 0 0 0 1.45483,-2.537801 v -23.124996 a 2.9118515,2.9308623 0 0 0 -1.45483,-2.5388 l -19.89775,-11.56251 a 2.9118515,2.9308623 0 0 0 -1.3873,-0.3918 z"
|
||||
id="path6666" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17499971;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m -569.00462,-38.844049 a 2.9118515,2.9308623 0 0 0 -0.77875,0.308999 l -19.89683,11.562501 a 2.9118515,2.9308623 0 0 0 -1.45672,2.538801 v 23.1250003 a 2.9118515,2.9308623 0 0 0 1.45672,2.537801 l 19.89683,11.5624977 a 2.9118515,2.9308623 0 0 0 2.91058,0 l 19.89775,-11.5624977 a 2.9118515,2.9308623 0 0 0 0.77778,-0.66460201 l -5.65379,-3.26999799 -16.47749,9.575199 -16.98435,-9.8709 V -22.74215 l 16.30827,-9.477299 z"
|
||||
id="path6668" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17499971;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m -460.9106,-37.293149 v 6.769201 l 13.39292,7.781798 v 19.7409023 l -16.9862,9.8709 -15.02174,-8.729901 -5.83733,3.375502 19.40327,11.2762977 a 2.9118515,2.9308623 0 0 0 2.91155,0 l 19.89678,-11.5624977 a 2.9118515,2.9308623 0 0 0 1.4558,-2.537801 V -24.433748 a 2.9118515,2.9308623 0 0 0 -1.4558,-2.538801 z"
|
||||
id="path6670" />
|
||||
</g>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#21aba5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17500019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m -531.46275,-116.06384 -0.48112,0.27957 v 5.56039 a 2.9213415,2.9213415 0 0 0 0.0171,0 h 33.93644 a 2.9213415,2.9213415 0 0 0 0.0181,-5.2e-4 v -5.55987 l -0.48109,-0.27957 z"
|
||||
id="path6674"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 80 KiB |
@@ -1,5 +0,0 @@
|
||||
self: super:
|
||||
|
||||
{
|
||||
nodejs = super.nodejs-8_x;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
with (import ./default.nix {});
|
||||
|
||||
let
|
||||
|
||||
ndk = androidenv.androidndk_17c.override { fullNDK = true; };
|
||||
ndk_root = "${ndk}/libexec/${ndk.name}";
|
||||
sdk_path = if stdenv.isDarwin
|
||||
then "/Library/Android/sdk"
|
||||
else "/Android/Sdk"; # intentional Capital S
|
||||
|
||||
in
|
||||
|
||||
mkShell rec {
|
||||
|
||||
LANG="en_US.UTF-8";
|
||||
JAVA_HOME=openjdk8;
|
||||
|
||||
ANDROID_SDK_ROOT=builtins.getEnv("HOME") + sdk_path;
|
||||
ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk-bundle";
|
||||
ANDROID_HOME=ANDROID_SDK_ROOT;
|
||||
ANDROID_NDK=ANDROID_NDK_ROOT;
|
||||
|
||||
nativeBuildInputs = [
|
||||
awscli
|
||||
curl
|
||||
git
|
||||
nodejs-8_x
|
||||
openjdk8
|
||||
yarn
|
||||
];
|
||||
|
||||
passthru = { inherit ndk; };
|
||||
|
||||
shellHook = ''
|
||||
${./install-ndk-17c.sh} ${ndk} ${ndk_root}
|
||||
yes | ${androidenv.androidsdk_9_0}/bin/sdkmanager --licenses --sdk_root="$ANDROID_SDK_ROOT"
|
||||
yes | ${androidenv.androidsdk_9_0}/bin/sdkmanager --sdk_root="$ANDROID_SDK_ROOT" "platforms;android-28" | grep -v =
|
||||
yes | ${androidenv.androidsdk_9_0}/bin/sdkmanager --sdk_root="$ANDROID_SDK_ROOT" "build-tools;28.0.3" | grep -v =
|
||||
${lib.optionalString stdenv.isLinux ''
|
||||
for dep in lib lib64; do
|
||||
if [ -L /$dep ] || [ ! -e /$dep ]; then
|
||||
echo "Creating /$dep"
|
||||
ln -s ${stdenv.cc.libc}/$dep /$dep
|
||||
else
|
||||
echo "Using existing /$dep"
|
||||
fi
|
||||
done
|
||||
''}
|
||||
'';
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
let
|
||||
pin = builtins.fromJSON (builtins.readFile ./nixpkgs.json);
|
||||
|
||||
nixpkgs = builtins.fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs-channels/archive/${pin.rev}.tar.gz";
|
||||
inherit (pin) sha256;
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{ ... } @ args:
|
||||
|
||||
import nixpkgs (args // {
|
||||
config = {
|
||||
allowUnfree = true;
|
||||
android_sdk.accept_license = true;
|
||||
};
|
||||
overlays = [ (import ./all-packages.nix) ];
|
||||
})
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
scriptdir="$(dirname ${BASH_SOURCE[0]})"
|
||||
shellnix="$scriptdir/android-shell.nix"
|
||||
|
||||
ndk=${1:-$(nix-build "$shellnix" -A ndk --no-out-link)}
|
||||
ndkRoot=${2:-$(nix eval --file "$shellnix" --raw ANDROID_NDK_ROOT)}
|
||||
|
||||
mkdir -p "$ANDROID_NDK_ROOT" # Create full path if necessary
|
||||
|
||||
# Replace any existing link or directory with link
|
||||
rm -r "$ANDROID_NDK_ROOT"
|
||||
ln -sn "$ndkRoot" "$ANDROID_NDK_ROOT"
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"url": "https://github.com/NixOS/nixpkgs-channels.git",
|
||||
"rev": "80738ed9dc0ce48d7796baed5364eef8072c794d",
|
||||
"date": "2018-11-17T11:18:10+01:00",
|
||||
"sha256": "0anmvr6b47gbbyl9v2fn86mfkcwgpbd5lf0yf3drgm8pbv57c1dc",
|
||||
"fetchSubmodules": false
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts",
|
||||
"ignore": ["src/**/*.test.ts"],
|
||||
"exec": "ts-node -r tsconfig-paths/register --files src/server.ts"
|
||||
}
|
||||
+43
-45
@@ -1,41 +1,42 @@
|
||||
{
|
||||
"name": "turtle-cli",
|
||||
"version": "0.8.8",
|
||||
"version": "0.5.1",
|
||||
"description": "The command-line tool for building standalone Expo apps",
|
||||
"preferGlobal": true,
|
||||
"scripts": {
|
||||
"start": "EXPENV_OVERRIDES=local-common,local,local-queue expenv nodemon | bunyan -o short -l debug --color",
|
||||
"start": "EXPENV_OVERRIDES=local-common,local,local-queue expenv gulp",
|
||||
"start:ios": "PLATFORM=ios yarn start",
|
||||
"start:android": "PLATFORM=android yarn start",
|
||||
"watch": "EXPENV_OVERRIDES=local-common,local,local-queue expenv gulp watch:source",
|
||||
"bin:turtle": "./bin/turtle.js",
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"lint:fix": "yarn lint --fix",
|
||||
"prepare": "rimraf ./build && yarn build",
|
||||
"build": "ttsc",
|
||||
"prepare": "yarn build",
|
||||
"build": "gulp build:server",
|
||||
"build:production": "./scripts/build.sh",
|
||||
"watch": "ttsc --watch",
|
||||
"build:android-tarball:local": "./scripts/android/buildAndroidTarballLocally.sh",
|
||||
"secrets:init-private": "./scripts/initPrivateSecrets.sh",
|
||||
"set-workingdir:remote": "./scripts/setWorkingdirMode.sh remote",
|
||||
"set-workingdir:local": "./scripts/setWorkingdirMode.sh local",
|
||||
"docker:pull": "retry5 gcloud docker -- pull gcr.io/exponentjs/turtle-android:latest",
|
||||
"docker:pull:tag": "retry5 gcloud docker -- pull gcr.io/exponentjs/turtle-android:${TAG}",
|
||||
"docker:build": "yarn build:production android && gcloud docker -- build -t gcr.io/exponentjs/turtle-android:latest -t gcr.io/exponentjs/turtle-android:${TAG} .",
|
||||
"docker:push": "retry5 gcloud docker -- push gcr.io/exponentjs/turtle-android:${TAG} && retry5 gcloud docker -- push gcr.io/exponentjs/turtle-android:latest",
|
||||
"deploy:android": "./scripts/android/deploy.sh",
|
||||
"test": "yarn jest",
|
||||
"test:integration": "yarn test --config jest/integration-config.json",
|
||||
"test:unit": "yarn test --config jest/unit-config.json",
|
||||
"test:smoke": "yarn test --config jest/smoke-config.json --forceExit",
|
||||
"test:smoke:android": "PLATFORM=android yarn test:smoke --testNamePattern=android",
|
||||
"test:smoke:android": "yarn test:smoke --testNamePattern=android",
|
||||
"test:smoke:ios": "PLATFORM=ios yarn test:smoke --testNamePattern=iOS",
|
||||
"release": "./scripts/release.sh",
|
||||
"release:beta": "./scripts/releaseBeta.sh"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.ts": [
|
||||
"yarn lint:fix",
|
||||
"git add"
|
||||
]
|
||||
"release": "release-it",
|
||||
"release:beta": "yarn release -c .release-it.beta.json --preRelease=beta",
|
||||
"ssh:android:pods-names": "./scripts/ssh/findAndroidPods.sh",
|
||||
"ssh:android:connect": "./scripts/ssh/connectToAndroidPod.sh",
|
||||
"ssh:ios:ips": "./scripts/ssh/findIosIps.sh",
|
||||
"set-priorities": "yarn set-priorities:local",
|
||||
"set-priorities:local": "EXPENV_OVERRIDES=local-common,local,local-queue expenv babel-node scripts/setConfigurations.js",
|
||||
"set-priorities:staging": "expenv babel-node scripts/setConfigurations.js",
|
||||
"set-priorities:production": "EXPENV_OVERRIDES=prod expenv babel-node scripts/setConfigurations.js"
|
||||
},
|
||||
"bin": {
|
||||
"turtle": "./bin/turtle.js"
|
||||
@@ -43,7 +44,7 @@
|
||||
"files": [
|
||||
"bin",
|
||||
"build",
|
||||
"scripts/android/configureAndroidSdk.sh",
|
||||
"scripts/cli",
|
||||
"shellTarballs"
|
||||
],
|
||||
"keywords": [
|
||||
@@ -63,67 +64,64 @@
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/babel-preset-cli": "^0.1.3",
|
||||
"@types/decompress": "^4.2.3",
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/jest": "^23.3.9",
|
||||
"@types/joi": "^13.4.3",
|
||||
"@types/lodash": "^4.14.129",
|
||||
"@types/npm-package-arg": "^6.1.0",
|
||||
"@types/lodash": "^4.14.109",
|
||||
"@types/progress": "^2.0.1",
|
||||
"@types/raven": "^2.5.1",
|
||||
"@types/request": "^2.47.1",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/sharp": "^0.17.8",
|
||||
"@types/tar": "^4.0.0",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/which": "^1.3.1",
|
||||
"husky": "^1.3.1",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-plugin-module-resolver": "^3.1.1",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-changed": "^3.2.0",
|
||||
"gulp-nodemon": "^2.4.1",
|
||||
"gulp-plumber": "^1.2.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-typescript": "^4.0.2",
|
||||
"jest": "^23.6.0",
|
||||
"lint-staged": "^8.1.5",
|
||||
"nock": "^10.0.1",
|
||||
"nodemon": "^1.18.11",
|
||||
"nodemon": "^1.18.7",
|
||||
"release-it": "^10.0.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"source-map-loader": "^0.2.3",
|
||||
"ts-jest": "^23.10.4",
|
||||
"ts-node": "^8.0.3",
|
||||
"ts-transformer-imports": "^0.4.1",
|
||||
"tsconfig-paths": "^3.8.0",
|
||||
"tslint": "^5.10.0",
|
||||
"tslint-eslint-rules": "^5.4.0",
|
||||
"ttypescript": "^1.5.6",
|
||||
"typescript": "^3.4.2"
|
||||
"typescript": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/spawn-async": "^1.4.2",
|
||||
"@expo/xdl": "56.0.1-rc.0",
|
||||
"@google-cloud/logging-bunyan": "^0.9.5",
|
||||
"async-retry": "^1.2.1",
|
||||
"aws-sdk": "^2.226.1",
|
||||
"bunyan": "^1.8.12",
|
||||
"bunyan-debug-stream": "^1.0.8",
|
||||
"bunyan-loggly": "^1.3.4",
|
||||
"bunyan-sentry-stream": "^1.2.1",
|
||||
"commander": "^2.15.1",
|
||||
"copy": "^0.3.1",
|
||||
"decompress": "^4.2.0",
|
||||
"dogapi": "^2.8.3",
|
||||
"fs-extra": "^6.0.0",
|
||||
"ioredis": "^3.2.2",
|
||||
"is-semver": "^1.0.8",
|
||||
"joi": "^13.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.10",
|
||||
"progress": "^2.0.0",
|
||||
"raven": "^2.6.2",
|
||||
"request": "^2.88.0",
|
||||
"request-progress": "^3.0.0",
|
||||
"semver": "^6.0.0",
|
||||
"sharp": "0.22.1",
|
||||
"sharp": "^0.21.0",
|
||||
"source-map-support": "^0.5.6",
|
||||
"tar": "^4.4.6",
|
||||
"uuid": "^3.2.1",
|
||||
"which": "^1.3.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@expo/traveling-fastlane-darwin": "1.9.9"
|
||||
"which": "^1.3.1",
|
||||
"xdl": "52.0.14-alpha.6+371be041"
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
ARTIFACTS_DIR="$ROOT_DIR/artifacts"
|
||||
TEMP_DIR="/tmp/android-shell-app"
|
||||
|
||||
mkdir -p $ARTIFACTS_DIR
|
||||
rm -rf $TEMP_DIR
|
||||
mkdir -p $TEMP_DIR
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/tools-public $TEMP_DIR/tools-public
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/secrets $TEMP_DIR/secrets
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/template-files $TEMP_DIR/template-files
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/android $TEMP_DIR/android
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/package.json $TEMP_DIR/package.json
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/expokit-npm-package $TEMP_DIR/expokit-npm-package
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/exponent/packages $TEMP_DIR/packages
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/libraries/eslint-config-universe $TEMP_DIR/eslint-config-universe
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/libraries/json-file $TEMP_DIR/json-file
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/libraries/osascript $TEMP_DIR/osascript
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/libraries/schemer $TEMP_DIR/schemer
|
||||
ln -s ${EXPO_UNIVERSE_DIR}/package.json $TEMP_DIR/universe-package.json
|
||||
|
||||
# generate dynamic macros
|
||||
mkdir -p $TEMP_DIR/android/expoview/src/main/java/host/exp/exponent/generated/
|
||||
cd $TEMP_DIR/tools-public
|
||||
./generate-dynamic-macros-android.sh
|
||||
rm -rf $TEMP_DIR/secrets
|
||||
|
||||
cd $TEMP_DIR; tar -czhf $ARTIFACTS_DIR/android-shell-builder.tar.gz .
|
||||
rm -rf $TEMP_DIR
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SDKMANAGER=$ANDROID_HOME/tools/bin/sdkmanager
|
||||
|
||||
yes | $SDKMANAGER --licenses > /dev/null
|
||||
if [[ "$1" = "--install-all-platforms" ]]; then
|
||||
yes | $SDKMANAGER \
|
||||
"platforms;android-23" \
|
||||
"platforms;android-24" \
|
||||
"platforms;android-25" \
|
||||
"platforms;android-26" \
|
||||
"platforms;android-27" | grep -v '='; [[ ${PIPESTATUS[1]} = 0 ]]
|
||||
fi
|
||||
yes | $SDKMANAGER "platforms;android-28" | grep -v '='; [[ ${PIPESTATUS[1]} = 0 ]]
|
||||
yes | $SDKMANAGER "platform-tools" | grep -v '='; [[ ${PIPESTATUS[1]} = 0 ]]
|
||||
yes | $SDKMANAGER "build-tools;28.0.3" | grep -v '='; [[ ${PIPESTATUS[1]} = 0 ]]
|
||||
yes | $SDKMANAGER \
|
||||
"extras;android;m2repository" \
|
||||
"extras;google;m2repository" \
|
||||
"extras;google;google_play_services" | grep -v '='; [[ ${PIPESTATUS[1]} = 0 ]]
|
||||
@@ -1,4 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
$ROOT_DIR/scripts/fetchRemoteTarballs.sh android
|
||||
ARTIFACTS_DIR="$ROOT_DIR/artifacts"
|
||||
WORKING_DIR="$ROOT_DIR/workingdir/android"
|
||||
ANDROID_SHELL_TARBALLS_DIRECTORY="$ROOT_DIR/shellTarballs/android"
|
||||
|
||||
mkdir -p $ARTIFACTS_DIR
|
||||
|
||||
for SDK_VERSION in `ls $ANDROID_SHELL_TARBALLS_DIRECTORY`; do
|
||||
mkdir -p $WORKING_DIR/$SDK_VERSION
|
||||
TARBALL_URI=$(head -n 1 $ANDROID_SHELL_TARBALLS_DIRECTORY/$SDK_VERSION)
|
||||
if command -v run >/dev/null 2>&1 ; then
|
||||
run expo.s3env --keep AWS_ACCESS_KEY_ID --keep AWS_SECRET_ACCESS_KEY --command retry5 aws s3 cp $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz --no-progress
|
||||
else
|
||||
# outside of universe aws credentials might not be set
|
||||
if ! aws iam get-user >/dev/null 2>&1 ; then aws configure ; fi
|
||||
aws s3 cp $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz --no-progress
|
||||
fi
|
||||
tar zxf $ARTIFACTS_DIR/$SDK_VERSION.tar.gz -C $WORKING_DIR/$SDK_VERSION
|
||||
rm -rf $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz
|
||||
done
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
printf "READY\n"; # signal to superivsord that listener is ready
|
||||
|
||||
while read line; do
|
||||
echo "[supervisord:eventlistener] Processing Event: $line" >&2;
|
||||
kill -SIGQUIT $PPID
|
||||
done < /dev/stdin
|
||||
+2
-9
@@ -2,23 +2,16 @@
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Please specify a platform to build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLATFORM=$1
|
||||
export PLATFORM=$1
|
||||
|
||||
yarn
|
||||
yarn run build
|
||||
if [ "$PLATFORM" = "android" ]; then
|
||||
$ROOT_DIR/scripts/android/fetchRemoteAndroidTarball.sh
|
||||
elif [ "$PLATFORM" = "ios" ]; then
|
||||
$ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
|
||||
fi
|
||||
|
||||
yarn set-workingdir:remote
|
||||
|
||||
if [[ -z "${TARBALL}" ]]; then
|
||||
echo "TARBALL is not set, building tarball skipped"
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses > /dev/null
|
||||
$ANDROID_HOME/tools/bin/sdkmanager \
|
||||
"platform-tools" \
|
||||
"platforms;android-23" \
|
||||
"build-tools;25.0.0" \
|
||||
"extras;android;m2repository" \
|
||||
"extras;google;m2repository"
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_NAME=`basename "$0"`
|
||||
DEPLOY_ENDPOINT_URL="https://circleci.com/api/v1.1/project/github/expo/turtle-deploy/tree/master"
|
||||
|
||||
if [[ -z "$1" || -z "$2" ]]; then
|
||||
echo "usage: ${bold}$SCRIPT_NAME PLATFORM ENVIRONMENT [FORCE]${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLATFORM="$1"
|
||||
ENVIRONMENT="$2"
|
||||
|
||||
if [[ "$3" = "--force" ]]; then
|
||||
FORCE_DEPLOY="true"
|
||||
fi
|
||||
|
||||
if [[ "$PLATFORM" != "android" && "$PLATFORM" != "ios" ]]; then
|
||||
echo "unsupported PLATFORM passed: ${bold}$PLATFORM${normal}"
|
||||
echo "valid values are: ${bold}android${normal}, ${bold}ios${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$ENVIRONMENT" != "staging" && "$ENVIRONMENT" != "production" ]]; then
|
||||
echo "unsupported ENVIRONMENT passed: ${bold}$ENVIRONMENT${normal}"
|
||||
echo "valid values are: ${bold}staging${normal}, ${bold}production${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -z "$CIRCLE_SHA1" ]]; then
|
||||
TURTLE_COMMIT="$CIRCLE_SHA1"
|
||||
elif [[ -z "$FORCE_DEPLOY" ]]; then
|
||||
read -p "You're about to schedule a deploy, are you sure you want to deploy the current commit? (y/n) " -n 1 -r
|
||||
echo # (optional) move to a new line
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "exiting..."
|
||||
exit 0
|
||||
else
|
||||
TURTLE_COMMIT=`git log --pretty=format:%H -1`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$TURTLE_BOT_CIRCLE_API_USER_TOKEN" ]]; then
|
||||
echo "Please set the TURTLE_BOT_CIRCLE_API_USER_TOKEN env variable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $FORCE_DEPLOY = "true" ]]; then
|
||||
curl -X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
-u $TURTLE_BOT_CIRCLE_API_USER_TOKEN: \
|
||||
-d '
|
||||
{
|
||||
"build_parameters": {
|
||||
"CIRCLE_JOB": "deploy_'$PLATFORM'",
|
||||
"TURTLE_ENV": "'$ENVIRONMENT'",
|
||||
"TURTLE_FORCE_DEPLOY": "true"
|
||||
}
|
||||
}' \
|
||||
$DEPLOY_ENDPOINT_URL | jq -r .build_url
|
||||
else
|
||||
curl -X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
-u $TURTLE_BOT_CIRCLE_API_USER_TOKEN: \
|
||||
-d '
|
||||
{
|
||||
"build_parameters": {
|
||||
"CIRCLE_JOB": "deploy_'$PLATFORM'",
|
||||
"TURTLE_ENV": "'$ENVIRONMENT'",
|
||||
"TURTLE_COMMIT": "'$TURTLE_COMMIT'"
|
||||
}
|
||||
}' \
|
||||
$DEPLOY_ENDPOINT_URL | jq -r .build_url
|
||||
fi
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
PLATFORM=$1
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
ARTIFACTS_DIR="$ROOT_DIR/artifacts"
|
||||
WORKING_DIR="$ROOT_DIR/workingdir/$PLATFORM"
|
||||
SHELL_TARBALLS_DIRECTORY="$ROOT_DIR/shellTarballs/$PLATFORM"
|
||||
|
||||
mkdir -p $ARTIFACTS_DIR
|
||||
|
||||
convert_s3_to_https() {
|
||||
BUCKET=`echo $1 | sed 's/s3:\/\/\([^/]*\).*/\1/'`
|
||||
KEY=`echo $1 | sed 's/s3:\/\/[^/]*\/\(.*\)/\1/'`
|
||||
echo "https://s3.amazonaws.com/$BUCKET/$KEY"
|
||||
}
|
||||
|
||||
download_shell_app() {
|
||||
SDK_VERSION=$1
|
||||
mkdir -p $WORKING_DIR/$SDK_VERSION
|
||||
TARBALL_S3_URL=$(head -n 1 $SHELL_TARBALLS_DIRECTORY/$SDK_VERSION)
|
||||
TARBALL_HTTPS_URL=$(convert_s3_to_https $TARBALL_S3_URL)
|
||||
curl -s $TARBALL_HTTPS_URL -o $ARTIFACTS_DIR/$SDK_VERSION.tar.gz
|
||||
tar zxf $ARTIFACTS_DIR/$SDK_VERSION.tar.gz -C $WORKING_DIR/$SDK_VERSION
|
||||
rm -rf $ARTIFACTS_DIR/$SDK_VERSION.tar.gz
|
||||
}
|
||||
|
||||
if [[ -z "$SDK_VERSION" ]]; then
|
||||
for SDK_VERSION in `ls $SHELL_TARBALLS_DIRECTORY`; do
|
||||
download_shell_app $SDK_VERSION
|
||||
done
|
||||
else
|
||||
if [ ! -d "$WORKING_DIR/$SDK_VERSION" ]; then
|
||||
download_shell_app $SDK_VERSION
|
||||
fi
|
||||
fi
|
||||
@@ -13,17 +13,13 @@ fi
|
||||
TURTLE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
TERRAFORM_DIR="${EXPO_UNIVERSE_DIR}/terraform"
|
||||
|
||||
ANDROID_NORMAL=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_android_in | head -1 | cut -d"=" -f2 | xargs`
|
||||
ANDROID_PRIORITY=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_android_in_priority | head -1 | cut -d"=" -f2 | xargs`
|
||||
IOS_NORMAL=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_ios_in | head -1 | cut -d"=" -f2 | xargs`
|
||||
IOS_PRIORITY=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_ios_in_priority | head -1 | cut -d"=" -f2 | xargs`
|
||||
ANDROID=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_android_in | head -1 | cut -d"=" -f2 | xargs`
|
||||
IOS=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_ios_in | head -1 | cut -d"=" -f2 | xargs`
|
||||
OUT=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_out | head -1 | cut -d"=" -f2 | xargs`
|
||||
|
||||
cat << EOF > $TURTLE_DIR/.secrets.local-queue
|
||||
AWS_SQS_ANDROID_QUEUE_URL="$ANDROID_NORMAL"
|
||||
AWS_SQS_ANDROID_PRIORITY_QUEUE_URL="$ANDROID_PRIORITY"
|
||||
AWS_SQS_IOS_QUEUE_URL="$IOS_NORMAL"
|
||||
AWS_SQS_IOS_PRIORITY_QUEUE_URL="$IOS_PRIORITY"
|
||||
AWS_SQS_ANDROID_QUEUE_URL="$ANDROID"
|
||||
AWS_SQS_IOS_QUEUE_URL="$IOS"
|
||||
AWS_SQS_OUT_QUEUE_URL="$OUT"
|
||||
EOF
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
$ROOT_DIR/scripts/fetchRemoteTarballs.sh ios
|
||||
ARTIFACTS_DIR="$ROOT_DIR/artifacts"
|
||||
WORKING_DIR="$ROOT_DIR/workingdir/ios"
|
||||
IOS_SHELL_TARBALLS_DIRECTORY="$ROOT_DIR/shellTarballs/ios"
|
||||
|
||||
mkdir -p $ARTIFACTS_DIR
|
||||
|
||||
for SDK_VERSION in `ls $IOS_SHELL_TARBALLS_DIRECTORY`; do
|
||||
mkdir -p $WORKING_DIR/$SDK_VERSION
|
||||
TARBALL_URI=$(head -n 1 $IOS_SHELL_TARBALLS_DIRECTORY/$SDK_VERSION)
|
||||
if command -v run >/dev/null 2>&1 ; then
|
||||
run expo.s3env --keep AWS_ACCESS_KEY_ID --keep AWS_SECRET_ACCESS_KEY --command retry5 aws s3 cp $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz --no-progress
|
||||
else
|
||||
# outside of universe aws credentials might not be set
|
||||
if ! aws iam get-user >/dev/null 2>&1 ; then aws configure ; fi
|
||||
aws s3 cp $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz --no-progress
|
||||
fi
|
||||
tar zxf $ARTIFACTS_DIR/$SDK_VERSION.tar.gz -C $WORKING_DIR/$SDK_VERSION
|
||||
rm -rf $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz
|
||||
done
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
$ROOT_DIR/node_modules/.bin/release-it
|
||||
echo
|
||||
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
echo "!!!! PLEASE REMEMBER TO UPDATE CHANGELOG !!!!"
|
||||
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
$ROOT_DIR/node_modules/.bin/release-it -c $ROOT_DIR/.release-it.beta.json --preRelease=beta
|
||||
@@ -0,0 +1,73 @@
|
||||
import { env } from 'turtle/utils/env';
|
||||
import { connect, RedisClient } from 'turtle/utils/redis';
|
||||
import { PLATFORMS } from 'turtle/constants';
|
||||
import {
|
||||
createConfigurationsKey,
|
||||
createDefaultConfigurationKey,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
} from 'turtle/utils/priorities';
|
||||
|
||||
const TIMEOUT = 3000;
|
||||
|
||||
// Turtle before doing anything (and after every build) checks for the first config
|
||||
// (an element in config table with key=i) that is not taken (config.i is null/outdated)
|
||||
// or is taken by this process itself.
|
||||
// If Turtle dies unexpectedly the configuration will become free after TTL expires,
|
||||
// so the state (number of HP/NP turtles) should come back to normal soon.
|
||||
|
||||
// For example, if a configuration table looks like this:
|
||||
// [ HIGH_CONFIGURATION, NORMAL_CONFIGURATION, HIGH_CONFIGURATION ]
|
||||
// If there is one turtle it runs with High Priority first.
|
||||
// If there are two turtles, one runs with High Priority first, the second one with Normal Priority first.
|
||||
// If the first dies, and the third is started in his place, it gets 3rd config (again HP-first),
|
||||
// but after some time, because of TTL for config reservations,
|
||||
// they should come back to using configurations no. 1 and 2.
|
||||
// If there are no more available configurations from the table, the default configuration is used.
|
||||
|
||||
|
||||
const AVAILABLE_CONFIGURATIONS_IOS = [
|
||||
HIGH_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
];
|
||||
|
||||
const AVAILABLE_CONFIGURATIONS_ANDROID = [
|
||||
HIGH_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
HIGH_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
NORMAL_CONFIGURATION,
|
||||
];
|
||||
|
||||
|
||||
const DEFAULT_CONFIGURATION = HIGH_CONFIGURATION;
|
||||
|
||||
async function run() {
|
||||
const redis = await connect(TIMEOUT, RedisClient.Configuration);
|
||||
|
||||
await redis.set(createConfigurationsKey(PLATFORMS.IOS), JSON.stringify(AVAILABLE_CONFIGURATIONS_IOS));
|
||||
await redis.set(createConfigurationsKey(PLATFORMS.ANDROID), JSON.stringify(AVAILABLE_CONFIGURATIONS_ANDROID));
|
||||
await redis.set(createDefaultConfigurationKey(PLATFORMS.IOS), JSON.stringify(DEFAULT_CONFIGURATION));
|
||||
await redis.set(createDefaultConfigurationKey(PLATFORMS.ANDROID), JSON.stringify(DEFAULT_CONFIGURATION));
|
||||
|
||||
redis.disconnect();
|
||||
}
|
||||
|
||||
run()
|
||||
.then(() => console.log('All done'))
|
||||
.catch(err => console.error('Error:', err));
|
||||
@@ -6,69 +6,55 @@ DOWNLOAD_DIR="${EXPO_DOWNLOAD_DIR:-$HOME/Downloads}"
|
||||
|
||||
mkdir -p $DOWNLOAD_DIR
|
||||
|
||||
if [ ! -f "$ROOT_DIR/.secrets.local-queue" ]; then
|
||||
echo "You need to run \"yarn secrets:init-private [SQS_NAME]\" or create the .secrets.local-queue file yourself with AWS_SQS_ANDROID_QUEUE_URL, AWS_SQS_ANDROID_PRIORITY_QUEUE_URL, AWS_SQS_IOS_QUEUE_URL, AWS_SQS_IOS_PRIORITY_QUEUE_URL and AWS_SQS_OUT_QUEUE_URL variables."
|
||||
if [ ! -f "$ROOT_DIR/.secrets.local-queue" ]; then
|
||||
echo "You need to run \"yarn secrets:init-private [SQS_NAME]\" or create .secrets.local-queue file yourself with AWS_SQS_ANDROID_QUEUE_URL, AWS_SQS_IOS_QUEUE_URL and AWS_SQS_OUT_QUEUE_URL variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# @dsokal: this is currently broken
|
||||
#
|
||||
# setup_local() {
|
||||
# ensure_link_exists() {
|
||||
# if [ ! -L "$2" ]; then
|
||||
# ln -s $1 $2
|
||||
# echo "$2 link created"
|
||||
# fi
|
||||
# }
|
||||
# WORKINGDIR="$ROOT_DIR/workingdir/local"
|
||||
#
|
||||
# if [[ -d $WORKINGDIR ]]; then
|
||||
# echo "Local workingdir already prepared"
|
||||
# return
|
||||
# fi
|
||||
#
|
||||
# if [[ -z "$EXPO_UNIVERSE_DIR" ]]; then
|
||||
# echo "EXPO_UNIVERSE_DIR needs to be set"
|
||||
# exit -1
|
||||
# fi
|
||||
#
|
||||
# cd $EXPO_UNIVERSE_DIR/exponent && yarn
|
||||
# cd $EXPO_UNIVERSE_DIR/exponent/tools-public && yarn && ./generate-dynamic-macros-android.sh
|
||||
#
|
||||
# mkdir -p $WORKINGDIR
|
||||
# echo "$WORKINGDIR directory created"
|
||||
#
|
||||
# LINKS=("template-files" "package.json" "android" "expokit-npm-package" "packages" "client-builds" "cpp" "exponent-view-template" "ios" "shellAppBase-builds" "shellAppWorkspaces" "node_modules")
|
||||
#
|
||||
# for LINKNAME in ${LINKS[@]}; do
|
||||
# ensure_link_exists "$EXPO_UNIVERSE_DIR/exponent/$LINKNAME" "$WORKINGDIR/$LINKNAME"
|
||||
# done
|
||||
# }
|
||||
setup_local() {
|
||||
ensure_link_exists() {
|
||||
if [ ! -L "$2" ]; then
|
||||
ln -s $1 $2
|
||||
echo "$2 link created"
|
||||
fi
|
||||
}
|
||||
WORKINGDIR="$ROOT_DIR/workingdir/local"
|
||||
|
||||
if [[ -d $WORKINGDIR ]]; then
|
||||
echo "Local workingdir already prepared"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "$EXPO_UNIVERSE_DIR" ]]; then
|
||||
echo "EXPO_UNIVERSE_DIR needs to be set"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
cd $EXPO_UNIVERSE_DIR/exponent && yarn
|
||||
cd $EXPO_UNIVERSE_DIR/exponent/tools-public && yarn && ./generate-dynamic-macros-android.sh
|
||||
|
||||
mkdir -p $WORKINGDIR
|
||||
echo "$WORKINGDIR directory created"
|
||||
|
||||
LINKS=("template-files" "package.json" "android" "expokit-npm-package" "packages" "client-builds" "cpp" "exponent-view-template" "ios" "shellAppBase-builds" "shellAppWorkspaces")
|
||||
|
||||
for LINKNAME in ${LINKS[@]}; do
|
||||
ensure_link_exists "$EXPO_UNIVERSE_DIR/exponent/$LINKNAME" "$WORKINGDIR/$LINKNAME"
|
||||
done
|
||||
}
|
||||
|
||||
setup_remote() {
|
||||
if [[ -d $ROOT_DIR/workingdir/android && -d $ROOT_DIR/workingdir/ios ]]; then
|
||||
echo "Shell apps already fetched"
|
||||
else
|
||||
echo "Fetching shell apps"
|
||||
if [ "$PLATFORM" != 'ios' ]; then
|
||||
if [ "$PLATFORM" != 'ios']; then
|
||||
$ROOT_DIR/scripts/android/fetchRemoteAndroidTarball.sh
|
||||
fi
|
||||
if [ "$PLATFORM" != 'android' ]; then
|
||||
if [ "$PLATFORM" != 'android']; then
|
||||
$ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
for sdk in $ROOT_DIR/workingdir/android/* ; do
|
||||
if [[ -f "$sdk/universe-package.json" ]]; then
|
||||
mv $sdk/package.json $sdk/exponent-package.json
|
||||
mv $sdk/universe-package.json $sdk/package.json
|
||||
cd $sdk && yarn
|
||||
else
|
||||
if [[ -f "$sdk/package.json" ]]; then
|
||||
cd $sdk && yarn
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
switch_local() {
|
||||
@@ -89,10 +75,8 @@ EOF
|
||||
}
|
||||
|
||||
if [ "$1" == "local" ]; then
|
||||
# setup_local
|
||||
# switch_local
|
||||
echo "Setting local workingdir is currently broken, sorry :("
|
||||
exit -1
|
||||
setup_local
|
||||
switch_local
|
||||
elif [ "$1" == "remote" ]; then
|
||||
setup_remote
|
||||
switch_remote
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Please provide an environment name as a first argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$2" ]; then
|
||||
echo "Please provide a pod name as a second argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kubectl --namespace $1 exec -it $2 -- /bin/bash
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENVIRONMENT=${1:-production}
|
||||
|
||||
kubectl get pods --namespace $ENVIRONMENT | grep "turtle-android-" | sed -e "s/ */ /g" | cut -d" " -f1
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENVIRONMENT=${1:-production}
|
||||
|
||||
govc vm.ip -wait 5s turtle-js-"${ENVIRONMENT}"-*
|
||||
@@ -1,26 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ueo pipefail
|
||||
|
||||
export NODE_ENV=test
|
||||
|
||||
export ENVIRONMENT="development"
|
||||
export AWS_ACCESS_KEY_ID="aws_id"
|
||||
export AWS_SECRET_ACCESS_KEY="secret"
|
||||
export AWS_SQS_ANDROID_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_android_in.fifo"
|
||||
export AWS_SQS_ANDROID_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_android_in_priority.fifo"
|
||||
export AWS_SQS_IOS_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_ios_in.fifo"
|
||||
export AWS_SQS_IOS_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_ios_in_priority.fifo"
|
||||
export AWS_SQS_OUT_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_out.fifo"
|
||||
export AWS_SQS_IOS_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_staging_ios_in.fifo"
|
||||
export AWS_SQS_IOS_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_staging_ios_in.fifo"
|
||||
export AWS_SQS_ANDROID_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_staging_android_in.fifo"
|
||||
export AWS_SQS_ANDROID_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_staging_android_in.fifo"
|
||||
export AWS_SQS_OUT_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_staging_out.fifo"
|
||||
export AWS_SQS_REGION="aws.west2"
|
||||
export AWS_S3_BUCKET="exp-shell-app-assets"
|
||||
export AWS_S3_REGION="us-west-1"
|
||||
export AWS_CLOUDWATCH_REGION="us-west-1"
|
||||
export API_PROTOCOL="http"
|
||||
export API_HOSTNAME="staging.expo.io"
|
||||
export API_PORT="443"
|
||||
export API_HOSTNAME="localhost"
|
||||
export API_PORT="3000"
|
||||
export REDIS_URL="noop"
|
||||
export REDIS_CONFIG_URL="noop"
|
||||
export DATADOG_DISABLED="true"
|
||||
|
||||
PLATFORM=android yarn set-workingdir:remote
|
||||
|
||||
yarn test:smoke:android
|
||||
smoke_result=$?
|
||||
|
||||
yarn test:unit
|
||||
unit_result=$?
|
||||
|
||||
[[ $smoke_result != 0 || $unit_result != 0 ]] && \
|
||||
echo && \
|
||||
echo "Tests failed." && \
|
||||
exit -1
|
||||
|
||||
exit 0
|
||||
@@ -1,15 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ueo pipefail
|
||||
|
||||
export NODE_ENV=test
|
||||
|
||||
export ENVIRONMENT="development"
|
||||
export AWS_SQS_ANDROID_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_android_in.fifo"
|
||||
export AWS_SQS_ANDROID_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_android_in_priority.fifo"
|
||||
export AWS_SQS_IOS_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_ios_in.fifo"
|
||||
export AWS_SQS_IOS_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_ios_in_priority.fifo"
|
||||
export AWS_SQS_OUT_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_dominik_out.fifo"
|
||||
export AWS_SQS_IOS_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_jakub_ios_in.fifo"
|
||||
export AWS_SQS_ANDROID_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_jakub_android_in.fifo"
|
||||
export AWS_SQS_IOS_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_jakub_ios_in_priority.fifo"
|
||||
export AWS_SQS_ANDROID_PRIORITY_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_jakub_android_in_priority.fifo"
|
||||
export AWS_SQS_OUT_QUEUE_URL="https://sqs.us-west-2.amazonaws.com/274251141632/turtle_jobs_local_jakub_out.fifo"
|
||||
export AWS_SQS_REGION="aws.west2"
|
||||
export AWS_S3_BUCKET="exp-shell-app-assets"
|
||||
export AWS_S3_REGION="us-west-1"
|
||||
@@ -19,9 +17,18 @@ export API_HOSTNAME="staging.expo.io"
|
||||
export API_PORT="443"
|
||||
export REDIS_URL="noop"
|
||||
export REDIS_CONFIG_URL="noop"
|
||||
export DATADOG_DISABLED="true"
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
SDK_VERSION=sdk32 $ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
|
||||
PLATFORM=ios yarn set-workingdir:remote
|
||||
|
||||
yarn test:smoke:ios
|
||||
smoke_result=$?
|
||||
|
||||
yarn test:unit
|
||||
unit_result=$?
|
||||
|
||||
[[ $smoke_result != 0 || $unit_result != 0 ]] && \
|
||||
echo && \
|
||||
echo "Tests failed." && \
|
||||
exit -1
|
||||
|
||||
exit 0
|
||||
@@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ueo pipefail
|
||||
|
||||
export NODE_ENV=test
|
||||
|
||||
export ENVIRONMENT="development"
|
||||
@@ -21,6 +19,8 @@ export API_HOSTNAME="noop"
|
||||
export API_PORT="443"
|
||||
export REDIS_URL="noop"
|
||||
export REDIS_CONFIG_URL="noop"
|
||||
export DATADOG_DISABLED="true"
|
||||
|
||||
yarn test:unit
|
||||
unit_result=$?
|
||||
|
||||
exit $unit_result
|
||||
@@ -0,0 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
|
||||
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
|
||||
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
|
||||
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
|
||||
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-89b2d24c86737d5a489c6a18467e31b2f9ce9005.tar.gz
|
||||
s3://exp-artifacts/android-shell-builder-5c339ff7bab6f2161d378cc70e1bf04d32ad98dd.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/android-shell-builder-f796aaffa0b5715079b2950faf26c2ff4e53e4c0.tar.gz
|
||||
s3://exp-artifacts/android-shell-builder-cb03fdc12dc74f699a6d3f22219cbcd30e01ac46.tar.gz
|
||||
@@ -1 +0,0 @@
|
||||
s3://exp-artifacts/android-shell-builder-aebd669d6573b0b3c802871d94cdb8b4116678d2.tar.gz
|
||||
@@ -1 +0,0 @@
|
||||
s3://exp-artifacts/ios-expo-client-a2b1e16f330b36fa0dc627a76caabe4d2ff45f77.tar.gz
|
||||
@@ -0,0 +1 @@
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-26-db356a09877642d64023af98e4e2e1e632fbe4df.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-30-6863276e6b9605e31228c845a047387571959e9b.tar.gz
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-30-7d017a96d00fbf18ee7e5581ea47528b9cc78afd.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-e1a8bd0ab2e1eba546792c0ffc282b5b202ccc39.tar.gz
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-56824ecea6ba10f5a9d8991545a1c47ee3284f5f.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-dc981c7110b7d508de8e9c352064dc3d745c9f72.tar.gz
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-e3855783ed0618f4dcf326dc353c6e2951e05c49.tar.gz
|
||||
@@ -1 +0,0 @@
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-a2b1e16f330b36fa0dc627a76caabe4d2ff45f77.tar.gz
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -13,28 +13,3 @@ export const TYPE_DIMENSIONS = (type: string) => [
|
||||
{ Name: 'host', Value: config.hostname },
|
||||
],
|
||||
];
|
||||
|
||||
export const PRIORITY_DIMENSIONS = (type: string, priority: string) => [
|
||||
[
|
||||
{ Name: 'env', Value: config.deploymentEnv },
|
||||
{ Name: 'platform', Value: config.platform },
|
||||
{ Name: 'priority', Value: priority },
|
||||
],
|
||||
[
|
||||
{ Name: 'env', Value: config.deploymentEnv },
|
||||
{ Name: 'platform', Value: config.platform },
|
||||
{ Name: 'type', Value: type },
|
||||
],
|
||||
[
|
||||
{ Name: 'env', Value: config.deploymentEnv },
|
||||
{ Name: 'platform', Value: config.platform },
|
||||
{ Name: 'type', Value: type },
|
||||
{ Name: 'priority', Value: priority },
|
||||
],
|
||||
[
|
||||
{ Name: 'env', Value: config.deploymentEnv },
|
||||
{ Name: 'platform', Value: config.platform },
|
||||
{ Name: 'type', Value: type },
|
||||
{ Name: 'host', Value: config.hostname },
|
||||
],
|
||||
];
|
||||
@@ -179,7 +179,7 @@ function _pushMetrics(metrics: IMetricsChunk[]) {
|
||||
metrics.forEach((metricsChunk) => {
|
||||
cloudWatch.putMetricData(metricsChunk, (err) => {
|
||||
if (err) {
|
||||
logger.warn({ err }, '[cloudwatch]');
|
||||
logger.warn('[cloudwatch]', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ export function sum(metrics: IMetric[]) {
|
||||
const reduced = metrics.reduce((acc: IMetric | null, i: IMetric) => {
|
||||
let result = acc;
|
||||
if (!result) {
|
||||
result = { ...i };
|
||||
result = {...i};
|
||||
} else {
|
||||
result.Value += i.Value;
|
||||
}
|
||||
|
||||
+4
-4
@@ -36,7 +36,7 @@ export async function receiveMessage(priority: string) {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error({ err }, 'Receive error');
|
||||
logger.error('Receive error', err);
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
@@ -62,13 +62,13 @@ export async function changeMessageVisibility(priority: string, receiptHandle: s
|
||||
}
|
||||
|
||||
// Every VISIBILITY_TIMEOUT_SEC / 3 seconds we are telling AWS SQS
|
||||
// that we're still processing the message, so it does not
|
||||
// that we're still proccessing the message, so it does not
|
||||
// send the build job to another turtle agent
|
||||
export function changeMessageVisibilityRecurring(priority: string, receiptHandle: string, jobId: string) {
|
||||
return setInterval(() => {
|
||||
if (getCurrentJobId() === jobId) {
|
||||
changeMessageVisibility(priority, receiptHandle).catch((err) => {
|
||||
logger.warn({ err }, 'Error at change msg visibility');
|
||||
logger.warn('Error at change msg visibility', err);
|
||||
});
|
||||
}
|
||||
}, (VISIBILITY_TIMEOUT_SEC * 1000) / 3);
|
||||
@@ -86,6 +86,6 @@ export async function sendMessage(jobId: string, status: JOB_UPDATE_TYPE, data =
|
||||
return await sqs.sendMessage(params).promise();
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Error sending SQS message: ${JSON.stringify(params)}}`);
|
||||
logger.error(`Error sending SQS message: ${JSON.stringify(params)}}`, err);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from 'fs-extra';
|
||||
import _ from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
|
||||
import { createBuilderAction } from 'turtle/bin/utils/builder';
|
||||
@@ -14,30 +15,18 @@ export default (program: any, setCommonCommandOptions: any) => {
|
||||
.option(
|
||||
'--keystore-path <app.jks>',
|
||||
'path to your Keystore (please provide Keystore password and Key password as EXPO_ANDROID_KEYSTORE_PASSWORD'
|
||||
+ ' and EXPO_ANDROID_KEY_PASSWORD env variables)',
|
||||
+ 'and EXPO_ANDROID_KEY_PASSWORD env variables)',
|
||||
)
|
||||
.option('--keystore-alias <alias>', 'keystore Alias')
|
||||
.option(
|
||||
'-t --type <build>',
|
||||
'type of build: app-bundle|apk',
|
||||
/^(app-bundle|apk)$/i,
|
||||
'app-bundle',
|
||||
)
|
||||
.option(
|
||||
'-m --mode <build>',
|
||||
'type of build: debug|release',
|
||||
/^(debug|release)$/i,
|
||||
'release',
|
||||
)
|
||||
.description(
|
||||
'Build a standalone APK or App Bundle for your project, either signed and ready for submission to'
|
||||
+ ' the Google Play Store or in debug mode.',
|
||||
'Build a standalone APK for your project, signed and ready for submission to the Google Play Store.',
|
||||
)
|
||||
.asyncAction(
|
||||
createBuilderAction({
|
||||
program,
|
||||
command,
|
||||
prepareCredentials,
|
||||
buildJobObject,
|
||||
builder,
|
||||
platform: PLATFORMS.ANDROID,
|
||||
os: ['darwin', 'linux'],
|
||||
@@ -45,6 +34,20 @@ export default (program: any, setCommonCommandOptions: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
const buildJobObject = (appJSON: any, { releaseChannel, username, projectDir, publicUrl }: any, credentials: any) => ({
|
||||
config: {
|
||||
..._.get(appJSON, 'expo.android.config', {}),
|
||||
releaseChannel,
|
||||
publicUrl,
|
||||
},
|
||||
id: uuid.v4(),
|
||||
platform: PLATFORMS.ANDROID,
|
||||
sdkVersion: _.get(appJSON, 'expo.sdkVersion'),
|
||||
projectDir,
|
||||
experienceName: `@${username}/${_.get(appJSON, 'expo.slug')}`,
|
||||
...(credentials && { credentials }),
|
||||
});
|
||||
|
||||
const prepareCredentials = async (cmd: any) => {
|
||||
const { keystorePath, keystoreAlias } = cmd;
|
||||
const keystorePassword = process.env.EXPO_ANDROID_KEYSTORE_PASSWORD;
|
||||
|
||||
@@ -20,8 +20,7 @@ function setCommonCommandOptions(cmd: any) {
|
||||
.option(
|
||||
'--release-channel <channel-name>',
|
||||
'pull from specified release channel (default: default)',
|
||||
)
|
||||
.option('-c --config <config-file>', 'specify a path to app.json');
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import fs from 'fs-extra';
|
||||
import _ from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
|
||||
import { createBuilderAction } from 'turtle/bin/utils/builder';
|
||||
import builder from 'turtle/builders/ios';
|
||||
import { IOS_BUILD_TYPES, PLATFORMS } from 'turtle/constants';
|
||||
import { IOS, PLATFORMS } from 'turtle/constants';
|
||||
|
||||
export default (program: any, setCommonCommandOptions: any) => {
|
||||
const command = program.command('build:ios [project-dir]');
|
||||
@@ -31,6 +32,7 @@ export default (program: any, setCommonCommandOptions: any) => {
|
||||
program,
|
||||
command,
|
||||
prepareCredentials,
|
||||
buildJobObject,
|
||||
builder,
|
||||
platform: PLATFORMS.IOS,
|
||||
os: 'darwin',
|
||||
@@ -38,8 +40,23 @@ export default (program: any, setCommonCommandOptions: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
const buildJobObject = (appJSON: any, { releaseChannel, buildType, username, publicUrl }: any, credentials: any) => ({
|
||||
config: {
|
||||
..._.get(appJSON, 'expo.ios.config', {}),
|
||||
buildType,
|
||||
releaseChannel,
|
||||
bundleIdentifier: _.get(appJSON, 'expo.ios.bundleIdentifier'),
|
||||
publicUrl,
|
||||
},
|
||||
id: uuid.v4(),
|
||||
platform: PLATFORMS.IOS,
|
||||
sdkVersion: _.get(appJSON, 'expo.sdkVersion'),
|
||||
experienceName: `@${username}/${_.get(appJSON, 'expo.slug')}`,
|
||||
...(credentials && { credentials }),
|
||||
});
|
||||
|
||||
const prepareCredentials = async (cmd: any) => {
|
||||
if (cmd.type !== IOS_BUILD_TYPES.ARCHIVE) {
|
||||
if (cmd.type !== IOS.BUILD_TYPES.ARCHIVE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import setup from 'turtle/bin/setup/setup';
|
||||
import { PLATFORMS } from 'turtle/constants';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
const l = logger.child({ buildPhase: 'setting up environment' });
|
||||
const l = logger.withFields({ buildPhase: 'setting up environment' });
|
||||
|
||||
function createSetupCommand(platform: string, os?: string) {
|
||||
return (program: any) => {
|
||||
@@ -33,7 +33,8 @@ async function setupAction(program: any, cmd: any, platform: string, os?: string
|
||||
await setup(platform, sdkVersion);
|
||||
l.info('it\'s all set!');
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Failed to setup environment for ${platform} builds`);
|
||||
logger.error(`Failed to setup environment for ${platform} builds`);
|
||||
logger.error(err.stack);
|
||||
if (err instanceof ErrorWithCommandHelp) {
|
||||
cmd.help();
|
||||
} else if (err instanceof ErrorWithProgramHelp) {
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import path from 'path';
|
||||
|
||||
import { LoggerDetach, ModuleVersion } from '@expo/xdl';
|
||||
import program, { Command } from 'commander';
|
||||
import fs from 'fs-extra';
|
||||
import { LoggerDetach, ModuleVersion } from 'xdl';
|
||||
|
||||
import * as commands from 'turtle/bin/commands';
|
||||
import logger from 'turtle/logger';
|
||||
@@ -17,16 +17,16 @@ Command.prototype.asyncAction = function asyncAction(asyncFn: (...asyncFnArgs: a
|
||||
return this.action(async (...args) => {
|
||||
try {
|
||||
await checkForUpdateAsync();
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'Failed to check for turtle-cli update.');
|
||||
} catch (e) {
|
||||
logger.warn('Failed to check for turtle-cli update.');
|
||||
}
|
||||
return await asyncFn(...args);
|
||||
});
|
||||
};
|
||||
|
||||
export function run(programName: string) {
|
||||
runAsync(programName).catch((err) => {
|
||||
logger.error({ err }, 'Uncaught Error');
|
||||
runAsync(programName).catch((e) => {
|
||||
logger.error('Uncaught Error', e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import path from 'path';
|
||||
import util from 'util';
|
||||
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import fs from 'fs-extra';
|
||||
import _which from 'which';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import ensureAndroidNDKIsPresent from 'turtle/bin/setup/android/ndk';
|
||||
import ensureAndroidSDKIsPresent from 'turtle/bin/setup/android/sdk';
|
||||
@@ -20,6 +20,10 @@ const REQUIRED_TOOLS: IToolDefinition[] = [
|
||||
command: 'bash',
|
||||
missingDescription: 'Please install bash',
|
||||
},
|
||||
{
|
||||
command: 'gulp',
|
||||
missingDescription: 'Run `npm install -g gulp-cli` (or `yarn global add gulp-cli`) to install gulp',
|
||||
},
|
||||
{
|
||||
command: 'javac',
|
||||
missingDescription: 'Please install JDK (version 8 or newer) - check https://jdk.java.net/',
|
||||
@@ -37,7 +41,7 @@ const REQUIRED_TOOLS: IToolDefinition[] = [
|
||||
},
|
||||
];
|
||||
const LOGGER_FIELDS = { buildPhase: 'setting up environment' };
|
||||
const l = logger.child(LOGGER_FIELDS);
|
||||
const l = logger.withFields(LOGGER_FIELDS);
|
||||
|
||||
export default async function setup(sdkVersion?: string) {
|
||||
await checkSystem(REQUIRED_TOOLS);
|
||||
@@ -63,26 +67,18 @@ function formatShellAppTarballUriPath(sdkMajorVersion: string) {
|
||||
return path.join(config.directories.shellTarballsDir, PLATFORMS.ANDROID, `sdk${sdkMajorVersion}`);
|
||||
}
|
||||
|
||||
async function _shellAppPostDownloadAction(sdkVersion: string, workingdir: string) {
|
||||
async function _shellAppPostDownloadAction(workingdir: string) {
|
||||
const inWorkingdir = (filename: string) => path.join(workingdir, filename);
|
||||
|
||||
if (await fs.pathExists(inWorkingdir('universe-package.json'))) {
|
||||
// legacy shell app built from universe
|
||||
await fs.move(inWorkingdir('package.json'), inWorkingdir('exponent-package.json'));
|
||||
await fs.move(inWorkingdir('universe-package.json'), inWorkingdir('package.json'));
|
||||
await _installNodeModules(workingdir);
|
||||
await fs.move(inWorkingdir('package.json'), inWorkingdir('universe-package.json'));
|
||||
await fs.move(inWorkingdir('exponent-package.json'), inWorkingdir('package.json'));
|
||||
} else {
|
||||
// new shell app built from expo or not
|
||||
await _installNodeModules(workingdir);
|
||||
}
|
||||
await fs.move(inWorkingdir('package.json'), inWorkingdir('exponent-package.json'));
|
||||
await fs.move(inWorkingdir('universe-package.json'), inWorkingdir('package.json'));
|
||||
await _installNodeModules(workingdir);
|
||||
await fs.move(inWorkingdir('package.json'), inWorkingdir('universe-package.json'));
|
||||
await fs.move(inWorkingdir('exponent-package.json'), inWorkingdir('package.json'));
|
||||
|
||||
// TODO: remove this after making sure that we don't need node_modules in tools-public for older sdks
|
||||
if (ExponentTools.parseSdkMajorVersion(sdkVersion) < 33) {
|
||||
const toolsPublicDir = path.join(workingdir, 'tools-public');
|
||||
await _installNodeModules(toolsPublicDir);
|
||||
}
|
||||
// TODO: remove following lines after upgrading android shell app
|
||||
const toolsPublicDir = path.join(workingdir, 'tools-public');
|
||||
await _installNodeModules(toolsPublicDir);
|
||||
}
|
||||
|
||||
async function _installNodeModules(cwd: string) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
import decompress from 'decompress';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import * as utils from 'turtle/bin/setup/utils/common';
|
||||
import { formatArtifactDownloadPath } from 'turtle/bin/setup/utils/common';
|
||||
import download from 'turtle/bin/setup/utils/downloader';
|
||||
import config from 'turtle/config';
|
||||
import logger from 'turtle/logger';
|
||||
@@ -12,14 +12,13 @@ import logger from 'turtle/logger';
|
||||
const ANDROID_NDK_URL = `https://dl.google.com/android/repository/android-ndk-r10e-${os.platform()}-x86_64.zip`;
|
||||
|
||||
const LOGGER_FIELDS = { buildPhase: 'setting up environment' };
|
||||
const l = logger.child(LOGGER_FIELDS);
|
||||
const l = logger.withFields(LOGGER_FIELDS);
|
||||
|
||||
export default async function ensureAndroidNDKIsPresent() {
|
||||
const androidNdkDir = path.join(config.directories.androidDependenciesDir, 'ndk');
|
||||
await utils.removeDirectoryUnlessReady(androidNdkDir);
|
||||
if (!(await fs.pathExists(androidNdkDir))) {
|
||||
await fs.ensureDir(androidNdkDir);
|
||||
const androidNdkDownloadPath = utils.formatArtifactDownloadPath(ANDROID_NDK_URL);
|
||||
const androidNdkDownloadPath = formatArtifactDownloadPath(ANDROID_NDK_URL);
|
||||
|
||||
try {
|
||||
l.info('Downloading Android NDK');
|
||||
@@ -27,7 +26,6 @@ export default async function ensureAndroidNDKIsPresent() {
|
||||
await download(ANDROID_NDK_URL, androidNdkDownloadPath);
|
||||
l.info('Decompressing Android NDK');
|
||||
await decompress(androidNdkDownloadPath, androidNdkDir, { strip: 1 });
|
||||
await utils.markDirectoryAsReady(androidNdkDir);
|
||||
l.info('Android NDK installed successfully');
|
||||
} catch (err) {
|
||||
await fs.remove(androidNdkDir);
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import decompress from 'decompress';
|
||||
import fs from 'fs-extra';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import * as utils from 'turtle/bin/setup/utils/common';
|
||||
import { formatArtifactDownloadPath } from 'turtle/bin/setup/utils/common';
|
||||
import download from 'turtle/bin/setup/utils/downloader';
|
||||
import config from 'turtle/config';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
const ANDROID_SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip';
|
||||
const ANDROID_SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip';
|
||||
|
||||
const LOGGER_FIELDS = { buildPhase: 'setting up environment' };
|
||||
const l = logger.child(LOGGER_FIELDS);
|
||||
const l = logger.withFields(LOGGER_FIELDS);
|
||||
|
||||
export default async function ensureAndroidSDKIsPresent() {
|
||||
const androidSdkDir = path.join(config.directories.androidDependenciesDir, 'sdk');
|
||||
await utils.removeDirectoryUnlessReady(androidSdkDir);
|
||||
if (!(await fs.pathExists(androidSdkDir))) {
|
||||
await fs.ensureDir(androidSdkDir);
|
||||
const androidSdkDownloadPath = utils.formatArtifactDownloadPath(ANDROID_SDK_URL);
|
||||
const androidSdkDownloadPath = formatArtifactDownloadPath(ANDROID_SDK_URL);
|
||||
|
||||
try {
|
||||
l.info('Downloading Android SDK');
|
||||
@@ -32,7 +31,6 @@ export default async function ensureAndroidSDKIsPresent() {
|
||||
await fs.remove(androidSdkDownloadPath);
|
||||
l.info('Configuring Android SDK, this may take a while');
|
||||
await _configureSdk(androidSdkDir);
|
||||
await utils.markDirectoryAsReady(androidSdkDir);
|
||||
l.info('Android SDK installed and configured successfully');
|
||||
} catch (err) {
|
||||
await fs.remove(androidSdkDir);
|
||||
@@ -54,7 +52,7 @@ async function _configureSdk(androidSdkDir: string) {
|
||||
await ExponentTools.spawnAsyncThrowError('./configureAndroidSdk.sh', [], {
|
||||
pipeToLogger: true,
|
||||
loggerFields: LOGGER_FIELDS,
|
||||
cwd: path.join(config.directories.rootDir, 'scripts/android'),
|
||||
cwd: path.join(config.directories.rootDir, 'scripts/cli'),
|
||||
env: {
|
||||
...process.env,
|
||||
ANDROID_HOME: androidSdkDir,
|
||||
@@ -75,6 +73,6 @@ function _createPaths(androidSdkDir: string) {
|
||||
path.join(androidSdkDir, 'platform-tools'),
|
||||
path.join(androidSdkDir, 'tools'),
|
||||
path.join(androidSdkDir, 'tools/bin'),
|
||||
path.join(androidSdkDir, 'build-tools/28.0.3'),
|
||||
path.join(androidSdkDir, 'build-tools/25.0.0'),
|
||||
];
|
||||
}
|
||||
+1
-27
@@ -1,9 +1,6 @@
|
||||
import last from 'lodash/last';
|
||||
import trim from 'lodash/trim';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import { checkSystem, ensureShellAppIsPresent } from 'turtle/bin/setup/utils/common';
|
||||
import { IToolDefinition } from 'turtle/bin/setup/utils/toolsDetector';
|
||||
@@ -19,21 +16,6 @@ const REQUIRED_TOOLS: IToolDefinition[] = [
|
||||
{
|
||||
command: 'fastlane',
|
||||
missingDescription: 'Please check https://docs.fastlane.tools/getting-started/ios/setup/',
|
||||
versionCheckFn: async () => {
|
||||
const MINIMAL_VERSION = '2.99.0';
|
||||
|
||||
const { stdout } = await ExponentTools.spawnAsyncThrowError(
|
||||
'fastlane',
|
||||
['--version'],
|
||||
{ stdio: 'pipe', env: { ...process.env, FASTLANE_SKIP_UPDATE_CHECK: '1', LC_ALL: 'en_US.UTF-8' } },
|
||||
);
|
||||
const fastlaneVersion = parseFastlaneVersion(stdout);
|
||||
if (fastlaneVersion && !semver.satisfies(fastlaneVersion, `>= ${MINIMAL_VERSION}`)) {
|
||||
throw new Error(
|
||||
`Your fastlane on version ${fastlaneVersion}. Please upgrade it to at least ${MINIMAL_VERSION}.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
command: 'xcode',
|
||||
@@ -65,11 +47,3 @@ export default async function setup(sdkVersion?: string) {
|
||||
function formatShellAppTarballUriPath(sdkMajorVersion: string) {
|
||||
return path.join(config.directories.shellTarballsDir, PLATFORMS.IOS, `sdk${sdkMajorVersion}`);
|
||||
}
|
||||
|
||||
function parseFastlaneVersion(stdout: string) {
|
||||
const lastLine = last(trim(stdout).split('\n'));
|
||||
if (!lastLine) {
|
||||
return null;
|
||||
}
|
||||
return last(lastLine.split(' ')) || null;
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
import path from 'path';
|
||||
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import fs from 'fs-extra';
|
||||
import tar from 'tar';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import download from 'turtle/bin/setup/utils/downloader';
|
||||
import { ensureToolsAreInstalled, IToolDefinition } from 'turtle/bin/setup/utils/toolsDetector';
|
||||
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
|
||||
import config from 'turtle/config';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
const l = logger.child({ buildPhase: 'setting up environment' });
|
||||
const l = logger.withFields({ buildPhase: 'setting up environment' });
|
||||
|
||||
interface IShellAppFormaters {
|
||||
formatShellAppDirectory: (config: IShellAppDirectoryConfig) => string;
|
||||
formatShellAppDirectory: (sdkVersion: string) => string;
|
||||
formatShellAppTarballUriPath: (sdkMajorVersion: string) => string;
|
||||
}
|
||||
|
||||
type PostDownloadAction = (sdkVersion: string, workingdir: string) => void;
|
||||
type PostDownloadAction = (workingdir: string) => void;
|
||||
|
||||
export async function checkSystem(requiredTools: IToolDefinition[]) {
|
||||
await ensureToolsAreInstalled(requiredTools);
|
||||
@@ -28,18 +27,15 @@ export async function ensureShellAppIsPresent(
|
||||
formatters: IShellAppFormaters,
|
||||
postDownloadAction?: PostDownloadAction,
|
||||
) {
|
||||
const workingdir = formatters.formatShellAppDirectory({ sdkVersion });
|
||||
const shellAppMetadata = await _readShellAppTarballS3Uri(sdkVersion, formatters);
|
||||
await removeDirectoryUnlessReady(workingdir, shellAppMetadata);
|
||||
const workingdir = formatters.formatShellAppDirectory(sdkVersion);
|
||||
if (await fs.pathExists(workingdir)) {
|
||||
return;
|
||||
}
|
||||
l.info(`shell app for SDK ${sdkVersion} doesn't exist, downloading...`);
|
||||
await _downloadShellApp(sdkVersion, workingdir, formatters);
|
||||
if (postDownloadAction) {
|
||||
await postDownloadAction(sdkVersion, workingdir);
|
||||
await postDownloadAction(workingdir);
|
||||
}
|
||||
await markDirectoryAsReady(workingdir, shellAppMetadata);
|
||||
}
|
||||
|
||||
async function _downloadShellApp(sdkVersion: string, targetDirectory: string, formatters: IShellAppFormaters) {
|
||||
@@ -68,31 +64,3 @@ async function _readShellAppTarballS3Uri(sdkVersion: string, formatters: IShellA
|
||||
const data = await fs.readFile(filePath, 'utf8');
|
||||
return data.trim();
|
||||
}
|
||||
|
||||
export async function removeDirectoryUnlessReady(dir: string, metadata?: string) {
|
||||
const readyFilePath = path.join(dir, '.ready');
|
||||
const readyFileExists = await fs.pathExists(readyFilePath);
|
||||
let shouldRemove = false;
|
||||
if (readyFileExists) {
|
||||
if (metadata !== undefined) {
|
||||
const readyFileContents = (await fs.readFile(readyFilePath, 'utf-8')).trim();
|
||||
if (readyFileContents !== metadata) {
|
||||
shouldRemove = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldRemove = true;
|
||||
}
|
||||
if (shouldRemove) {
|
||||
await fs.remove(dir);
|
||||
}
|
||||
}
|
||||
|
||||
export async function markDirectoryAsReady(dir: string, metadata?: string) {
|
||||
const readyFilePath = path.join(dir, '.ready');
|
||||
if (metadata === undefined) {
|
||||
await fs.open(readyFilePath, 'w');
|
||||
} else {
|
||||
await fs.writeFile(readyFilePath, metadata);
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,17 @@ import _which from 'which';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
const which = util.promisify(_which);
|
||||
const l = logger.child({ buildPhase: 'setting up environment' });
|
||||
const l = logger.withFields({ buildPhase: 'setting up environment' });
|
||||
|
||||
export interface IToolDefinition {
|
||||
command: string;
|
||||
missingDescription: string;
|
||||
testFn?: () => Promise<boolean>;
|
||||
versionCheckFn?: () => void;
|
||||
}
|
||||
|
||||
export async function ensureToolsAreInstalled(tools: IToolDefinition[]) {
|
||||
let isAnyToolMissing = false;
|
||||
for (const { command, missingDescription, testFn, versionCheckFn } of tools) {
|
||||
for (const { command, missingDescription, testFn } of tools) {
|
||||
try {
|
||||
if (testFn) {
|
||||
if (!await testFn()) {
|
||||
@@ -27,17 +26,9 @@ export async function ensureToolsAreInstalled(tools: IToolDefinition[]) {
|
||||
} catch (err) {
|
||||
isAnyToolMissing = true;
|
||||
if (!testFn) {
|
||||
l.error({ err }, `${command} is missing in your $PATH`);
|
||||
}
|
||||
l.error({ err }, missingDescription);
|
||||
}
|
||||
if (versionCheckFn) {
|
||||
try {
|
||||
await versionCheckFn();
|
||||
} catch (err) {
|
||||
isAnyToolMissing = true;
|
||||
l.error({ err }, `wrong version of ${command} installed`);
|
||||
l.error(`${command} is missing in your $PATH`);
|
||||
}
|
||||
l.error(missingDescription);
|
||||
}
|
||||
}
|
||||
if (isAnyToolMissing) {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import _ from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
|
||||
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
|
||||
import setup from 'turtle/bin/setup/setup';
|
||||
import * as ProjectUtils from 'turtle/bin/utils/project';
|
||||
import * as UserUtils from 'turtle/bin/utils/user';
|
||||
import { getExperienceUrl } from 'turtle/builders/utils/common';
|
||||
import logger from 'turtle/logger';
|
||||
import { sanitizeJob } from 'turtle/validator';
|
||||
|
||||
@@ -15,6 +12,7 @@ export function createBuilderAction({
|
||||
program,
|
||||
command,
|
||||
prepareCredentials,
|
||||
buildJobObject,
|
||||
builder,
|
||||
platform,
|
||||
os,
|
||||
@@ -57,25 +55,25 @@ export function createBuilderAction({
|
||||
const args = {
|
||||
releaseChannel: cmd.releaseChannel || 'default',
|
||||
buildType: cmd.type,
|
||||
buildMode: cmd.mode,
|
||||
username: userData.username || 'anonymous',
|
||||
projectDir: ProjectUtils.resolveAbsoluteDir(projectDirArg),
|
||||
publicUrl: cmd.publicUrl,
|
||||
};
|
||||
|
||||
const appJSON = await ProjectUtils.loadAppJSON(projectDirArg, cmd.config);
|
||||
const appJSON = await ProjectUtils.loadAppJSON(projectDirArg);
|
||||
const sdkVersion = _.get(appJSON, 'expo.sdkVersion');
|
||||
await setup(platform, sdkVersion);
|
||||
const credentials = await prepareCredentials(cmd);
|
||||
const rawJob = {
|
||||
...await buildJobObject(platform, appJSON, args, credentials),
|
||||
...buildJobObject(appJSON, args, credentials),
|
||||
...cmd.buildDir && { fakeUploadDir: ProjectUtils.resolveAbsoluteDir(cmd.buildDir) },
|
||||
...cmd.output && { fakeUploadBuildPath: ProjectUtils.resolveAbsoluteDir(cmd.output) },
|
||||
};
|
||||
const job = await sanitizeJob(rawJob);
|
||||
await builder(job);
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Failed to build standalone app');
|
||||
logger.error(`Failed to build standalone app: ${err.message}`);
|
||||
logger.error(err.stack);
|
||||
if (err instanceof ErrorWithCommandHelp) {
|
||||
command.help();
|
||||
} else if (err instanceof ErrorWithProgramHelp) {
|
||||
@@ -85,38 +83,3 @@ export function createBuilderAction({
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const buildJobObject = async (
|
||||
platform: 'android' | 'ios',
|
||||
appJSON: any,
|
||||
{ releaseChannel, buildType, buildMode, username, publicUrl, projectDir }: any,
|
||||
credentials: any,
|
||||
) => {
|
||||
const job = {
|
||||
config: {
|
||||
..._.get(appJSON, 'expo.ios.config', {}),
|
||||
buildType,
|
||||
...(platform === 'android' ? { buildMode } : {}),
|
||||
releaseChannel,
|
||||
...(platform === 'ios' ? { bundleIdentifier: _.get(appJSON, 'expo.ios.bundleIdentifier') } : {}),
|
||||
...(platform === 'android' ? { androidPackage: _.get(appJSON, 'expo.android.package') } : {}),
|
||||
publicUrl,
|
||||
},
|
||||
id: uuid.v4(),
|
||||
platform,
|
||||
projectDir,
|
||||
sdkVersion: _.get(appJSON, 'expo.sdkVersion'),
|
||||
experienceName: `@${username}/${_.get(appJSON, 'expo.slug')}`,
|
||||
...(credentials && { credentials }),
|
||||
};
|
||||
const url = getExperienceUrl(job.experienceName, job.config.publicUrl);
|
||||
|
||||
const manifest = await ExponentTools.getManifestAsync(url, {
|
||||
'Exponent-SDK-Version': _.get(appJSON, 'expo.sdkVersion'),
|
||||
'Exponent-Platform': platform,
|
||||
'Expo-Release-Channel': releaseChannel,
|
||||
'Accept': 'application/expo+json,application/json',
|
||||
});
|
||||
|
||||
return { ...job, manifest };
|
||||
};
|
||||
@@ -1,17 +1,12 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
export async function loadAppJSON(projectDirArg: string, config: string | null) {
|
||||
let appJSONPath;
|
||||
if (config) {
|
||||
appJSONPath = resolveAbsoluteDir(config);
|
||||
} else {
|
||||
const projectDir = resolveAbsoluteDir(projectDirArg);
|
||||
appJSONPath = path.join(projectDir, 'app.json');
|
||||
}
|
||||
export async function loadAppJSON(projectDirArg: string) {
|
||||
const projectDir = resolveAbsoluteDir(projectDirArg);
|
||||
const appJSONPath = path.join(projectDir, 'app.json');
|
||||
const appJSONExists = await fs.pathExists(appJSONPath);
|
||||
if (!appJSONExists) {
|
||||
throw new Error(`Couldn't find app.json.`);
|
||||
throw new Error(`Couldn't find app.json in ${projectDir} directory.`);
|
||||
} else {
|
||||
return require(appJSONPath);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { User as UserManager } from '@expo/xdl';
|
||||
import { User as UserManager } from 'xdl';
|
||||
|
||||
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
class BuildError extends Error {
|
||||
public reason: BuildErrorReason;
|
||||
|
||||
constructor(reason: BuildErrorReason, message: string) {
|
||||
super(message);
|
||||
this.name = 'BuildError';
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
enum BuildErrorReason {
|
||||
SESSION_EXPIRED = 'session-expired',
|
||||
PROVISIONING_PROFILE_MISSING = 'provisioning-profile-missing',
|
||||
}
|
||||
|
||||
export default BuildError;
|
||||
export { BuildErrorReason };
|
||||
+11
-29
@@ -1,36 +1,28 @@
|
||||
import path from 'path';
|
||||
|
||||
import { AndroidShellApp, ImageUtils } from '@expo/xdl';
|
||||
import fs from 'fs-extra';
|
||||
import _ from 'lodash';
|
||||
import semver from 'semver';
|
||||
import { ANDROID_BUILD_TYPES } from 'turtle/constants';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import { AndroidShellApp, ImageUtils } from 'xdl';
|
||||
|
||||
import getOrCreateCredentials from 'turtle/builders/utils/android/credentials';
|
||||
import { formatShellAppDirectory } from 'turtle/builders/utils/android/workingdir';
|
||||
import * as commonUtils from 'turtle/builders/utils/common';
|
||||
import * as imageHelpers from 'turtle/builders/utils/image';
|
||||
import { resolveNativeModules } from 'turtle/builders/utils/unimodules';
|
||||
import { uploadBuildToS3 } from 'turtle/builders/utils/uploader';
|
||||
import { ensureCanBuildSdkVersion } from 'turtle/builders/utils/version';
|
||||
import config from 'turtle/config';
|
||||
import { IAndroidCredentials, IJob, IJobResult } from 'turtle/job';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
export default async function buildAndroid(jobData: IJob): Promise<IJobResult> {
|
||||
await ensureCanBuildSdkVersion(jobData);
|
||||
const credentials = await getOrCreateCredentials(jobData);
|
||||
const outputFilePath = await runShellAppBuilder(jobData, credentials);
|
||||
|
||||
const apkFilePath = await runShellAppBuilder(jobData, credentials);
|
||||
const randomHex = uuidv4().replace(/-/g, '');
|
||||
const s3FileExtension = jobData.config.buildType === ANDROID_BUILD_TYPES.APP_BUNDLE ? 'aab' : 'apk';
|
||||
const s3Filename = `${jobData.experienceName}-${randomHex}-signed.${s3FileExtension}`;
|
||||
const s3Filename = `${jobData.experienceName}-${randomHex}-signed.apk`;
|
||||
const s3FileKey = `android/${s3Filename}`;
|
||||
const fakeUploadFilename = s3Filename.replace('/', '\\');
|
||||
|
||||
const artifactUrl = await uploadBuildToS3({
|
||||
uploadPath: outputFilePath,
|
||||
uploadPath: apkFilePath,
|
||||
s3FileKey,
|
||||
...config.builder.fakeUpload && {
|
||||
fakeUploadBuildPath:
|
||||
@@ -66,34 +58,24 @@ async function runShellAppBuilder(
|
||||
ImageUtils.setResizeImageFunction(imageHelpers.resizeIconWithSharpAsync);
|
||||
ImageUtils.setGetImageDimensionsFunction(imageHelpers.getImageDimensionsWithSharpAsync);
|
||||
|
||||
const fileExtension = jobData.config.buildType === ANDROID_BUILD_TYPES.APP_BUNDLE ? 'aab' : 'apk';
|
||||
const outputFilePath = path.join(temporaryFilesRoot, `shell-signed-${jobData.id}.${fileExtension}`);
|
||||
const outputFilePath = path.join(temporaryFilesRoot, `shell-signed-${jobData.id}.apk`);
|
||||
|
||||
const { config: jobConfig, manifest, sdkVersion: sdkVersionFromJob } = jobData;
|
||||
const sdkVersion = _.get(manifest, 'sdkVersion', sdkVersionFromJob);
|
||||
const workingDir = formatShellAppDirectory({ sdkVersion });
|
||||
|
||||
logger.info({ buildPhase: 'resolve native modules' }, 'Resolving universal modules dependencies');
|
||||
const enabledModules = semver.satisfies(sdkVersion, '>= 33.0.0')
|
||||
? await resolveNativeModules(workingDir, _.get(manifest, 'dependencies'))
|
||||
: null;
|
||||
const { manifest: { sdkVersion: sdkVersionFromManifest = null } = {}, sdkVersion: sdkVersionFromJob } = jobData;
|
||||
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
|
||||
const workingDir = formatShellAppDirectory(sdkVersion);
|
||||
|
||||
try {
|
||||
await AndroidShellApp.createAndroidShellAppAsync({
|
||||
url: commonUtils.getExperienceUrl(jobData.experienceName, jobData.config.publicUrl),
|
||||
sdkVersion,
|
||||
url: commonUtils.getExperienceUrl(jobData),
|
||||
sdkVersion: _.get(jobData, 'manifest.sdkVersion') || jobData.sdkVersion,
|
||||
keystore: tempKeystorePath,
|
||||
manifest,
|
||||
alias: credentials.keystoreAlias,
|
||||
keystorePassword: credentials.keystorePassword,
|
||||
keyPassword: credentials.keyPassword,
|
||||
privateConfigFile: tempShellAppConfigPath,
|
||||
releaseChannel: jobConfig.releaseChannel,
|
||||
releaseChannel: jobData.config.releaseChannel,
|
||||
workingDir,
|
||||
outputFile: outputFilePath,
|
||||
modules: enabledModules,
|
||||
buildType: jobData.config.buildType,
|
||||
buildMode: jobData.config.buildMode,
|
||||
});
|
||||
} catch (err) {
|
||||
commonUtils.logErrorOnce(err);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IosIPABuilder as createIPABuilder } from '@expo/xdl';
|
||||
import _ from 'lodash';
|
||||
import { IosIPABuilder as createIPABuilder } from 'xdl';
|
||||
|
||||
import { IContext } from 'turtle/builders/ios/context';
|
||||
import { logErrorOnce } from 'turtle/builders/utils/common';
|
||||
@@ -29,11 +29,11 @@ export default async function buildArchive(ctx: IContext, job: IJob) {
|
||||
}
|
||||
|
||||
async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, manifest: any) {
|
||||
const l = logger.child({ buildPhase: 'building and signing IPA' });
|
||||
const l = logger.withFields({ buildPhase: 'building and signing IPA' });
|
||||
l.info('building and signing IPA');
|
||||
|
||||
const {
|
||||
credentials: { provisioningProfile, certPassword, teamId },
|
||||
credentials: { provisioningProfile, certPassword, teamId, password },
|
||||
config: { bundleIdentifier: bundleIdentifierFromConfig },
|
||||
} = job;
|
||||
|
||||
@@ -51,8 +51,9 @@ async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, m
|
||||
certPath: ctx.tempCertPath,
|
||||
certPassword,
|
||||
teamID: teamId,
|
||||
password,
|
||||
bundleIdentifier,
|
||||
workspacePath: ctx.workspacePath,
|
||||
workingDir: ctx.workingDir,
|
||||
manifest,
|
||||
});
|
||||
await ipaBuilder.build();
|
||||
|
||||
+19
-59
@@ -1,11 +1,11 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import { IosShellApp } from '@expo/xdl';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { IosShellApp } from 'xdl';
|
||||
|
||||
import { formatShellAppDirectory } from 'turtle/builders/utils/ios/workingdir';
|
||||
import config from 'turtle/config';
|
||||
import { IOS_BUILD_TYPES, PLATFORMS } from 'turtle/constants/index';
|
||||
import { IOS, PLATFORMS } from 'turtle/constants/index';
|
||||
import { IJob } from 'turtle/job';
|
||||
|
||||
export interface IContext {
|
||||
@@ -22,11 +22,10 @@ export interface IContext {
|
||||
tempCertPath: string;
|
||||
uploadPath: string;
|
||||
workingDir: string;
|
||||
workspacePath: string;
|
||||
applicationFilesSrc: string;
|
||||
}
|
||||
|
||||
const { EXPOKIT_APP, EXPONENT_APP } = IosShellApp;
|
||||
const { DEFAULT_EXPOKIT_WORKSPACE_NAME } = IosShellApp;
|
||||
const { BUILD_TYPES } = IOS;
|
||||
|
||||
export function createBuilderContext(job: IJob): IContext {
|
||||
const { join } = path;
|
||||
@@ -40,7 +39,7 @@ export function createBuilderContext(job: IJob): IContext {
|
||||
} = job;
|
||||
|
||||
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
|
||||
const workingDir = formatShellAppDirectory({ sdkVersion, buildType: buildType! });
|
||||
const workingDir = formatShellAppDirectory(sdkVersion);
|
||||
|
||||
const context: any = {
|
||||
appDir: join(config.directories.temporaryFilesRoot, appUUID),
|
||||
@@ -56,67 +55,28 @@ export function createBuilderContext(job: IJob): IContext {
|
||||
context.tempCertPath = join(context.appDir, 'cert.p12');
|
||||
context.baseArchiveDir = join(context.appDir, 'archive');
|
||||
|
||||
if (buildType === IOS_BUILD_TYPES.CLIENT) {
|
||||
context.applicationFilesSrc = join(
|
||||
workingDir,
|
||||
'expo-client-build',
|
||||
'**',
|
||||
'*',
|
||||
);
|
||||
} else {
|
||||
context.applicationFilesSrc = join(
|
||||
workingDir,
|
||||
'shellAppBase-builds',
|
||||
buildType as string,
|
||||
'**',
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
if (buildType === IOS_BUILD_TYPES.ARCHIVE) {
|
||||
context.outputPath = join(context.appDir, 'archive.xcarchive');
|
||||
context.uploadPath = join(context.buildDir, 'archive.ipa');
|
||||
context.archiveDir = join(
|
||||
context.baseArchiveDir,
|
||||
'Release',
|
||||
`${EXPOKIT_APP}.xcarchive`,
|
||||
'Products',
|
||||
'Applications',
|
||||
`${EXPOKIT_APP}.app`,
|
||||
);
|
||||
context.workspacePath = path.join(
|
||||
workingDir,
|
||||
'shellAppWorkspaces',
|
||||
'ios',
|
||||
'default',
|
||||
`${EXPOKIT_APP}.xcworkspace`,
|
||||
);
|
||||
} else if (buildType === IOS_BUILD_TYPES.CLIENT) {
|
||||
context.outputPath = join(context.appDir, 'archive.xcarchive');
|
||||
context.uploadPath = join(context.buildDir, 'archive.ipa');
|
||||
context.archiveDir = join(
|
||||
context.baseArchiveDir,
|
||||
`${EXPONENT_APP}.xcarchive`,
|
||||
'Products',
|
||||
'Applications',
|
||||
`${EXPONENT_APP}.app`,
|
||||
);
|
||||
context.workspacePath = path.join(
|
||||
workingDir,
|
||||
'ios',
|
||||
`${EXPONENT_APP}.xcworkspace`,
|
||||
);
|
||||
} else if (buildType === IOS_BUILD_TYPES.SIMULATOR) {
|
||||
if (buildType === BUILD_TYPES.SIMULATOR) {
|
||||
context.outputPath = join(context.appDir, 'archive.tar.gz');
|
||||
context.uploadPath = join(context.appDir, 'archive.tar.gz');
|
||||
context.archiveDir = join(
|
||||
context.baseArchiveDir,
|
||||
'Release',
|
||||
`${EXPOKIT_APP}.app`,
|
||||
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`,
|
||||
);
|
||||
} else if (buildType === BUILD_TYPES.ARCHIVE) {
|
||||
context.outputPath = join(context.appDir, 'archive.xcarchive');
|
||||
context.uploadPath = join(context.buildDir, 'archive.ipa');
|
||||
context.archiveDir = join(
|
||||
context.baseArchiveDir,
|
||||
'Release',
|
||||
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.xcarchive`,
|
||||
'Products',
|
||||
'Applications',
|
||||
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`,
|
||||
);
|
||||
}
|
||||
|
||||
const s3FileExtension = buildType === IOS_BUILD_TYPES.SIMULATOR ? 'tar.gz' : 'ipa';
|
||||
const s3FileExtension = buildType === BUILD_TYPES.SIMULATOR ? 'tar.gz' : 'ipa';
|
||||
const s3Filename = `${job.experienceName}-${appUUID}-${buildType}.${s3FileExtension}`;
|
||||
if (config.builder.fakeUpload) {
|
||||
const fakeUploadFilename = s3Filename.replace('/', '\\');
|
||||
|
||||
+34
-14
@@ -1,21 +1,24 @@
|
||||
import path from 'path';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import pick from 'lodash/pick';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import buildArchive from 'turtle/builders/ios/archive';
|
||||
import { createBuilderContext, IContext } from 'turtle/builders/ios/context';
|
||||
import buildSimulator from 'turtle/builders/ios/simulator';
|
||||
import { logErrorOnce } from 'turtle/builders/utils/common';
|
||||
import prepareAdHocBuildCredentials from 'turtle/builders/utils/ios/adhocBuild';
|
||||
import { uploadBuildToS3 } from 'turtle/builders/utils/uploader';
|
||||
import { ensureCanBuildSdkVersion } from 'turtle/builders/utils/version';
|
||||
import config from 'turtle/config';
|
||||
import { IOS_BUILD_TYPES } from 'turtle/constants';
|
||||
import { IOS, PLATFORMS } from 'turtle/constants/index';
|
||||
import { IJob, IJobResult } from 'turtle/job';
|
||||
|
||||
const { BUILD_TYPES } = IOS;
|
||||
|
||||
let SUPPORTED_SDK_VERSIONS: number[];
|
||||
|
||||
export default async function iosBuilder(job: IJob): Promise<IJobResult> {
|
||||
if (job.config.buildType !== IOS_BUILD_TYPES.CLIENT) {
|
||||
await ensureCanBuildSdkVersion(job);
|
||||
}
|
||||
await ensureCanBuildSdkVersion(job);
|
||||
|
||||
const ctx = createBuilderContext(job);
|
||||
|
||||
@@ -23,15 +26,10 @@ export default async function iosBuilder(job: IJob): Promise<IJobResult> {
|
||||
await initBuilder(ctx);
|
||||
|
||||
const { buildType } = job.config;
|
||||
|
||||
if (buildType === IOS_BUILD_TYPES.CLIENT) {
|
||||
await prepareAdHocBuildCredentials(job);
|
||||
}
|
||||
|
||||
if ([IOS_BUILD_TYPES.ARCHIVE, IOS_BUILD_TYPES.CLIENT].includes(buildType!)) {
|
||||
await buildArchive(ctx, job);
|
||||
} else if (buildType === IOS_BUILD_TYPES.SIMULATOR) {
|
||||
if (buildType === BUILD_TYPES.SIMULATOR) {
|
||||
await buildSimulator(ctx, job);
|
||||
} else if (buildType === BUILD_TYPES.ARCHIVE) {
|
||||
await buildArchive(ctx, job);
|
||||
} else {
|
||||
throw new Error(`Unsupported iOS build type: ${buildType}`);
|
||||
}
|
||||
@@ -62,3 +60,25 @@ async function cleanup(ctx: IContext) {
|
||||
|
||||
const getTemporaryDirs = (ctx: IContext) =>
|
||||
Object.values(pick(ctx, ['appDir', 'provisioningProfileDir']));
|
||||
|
||||
async function ensureCanBuildSdkVersion(job: IJob) {
|
||||
if (config.builder.useLocalWorkingDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SUPPORTED_SDK_VERSIONS) {
|
||||
SUPPORTED_SDK_VERSIONS = await findSupportedSdkVersions();
|
||||
}
|
||||
|
||||
const targetMajorSdkVersion = ExponentTools.parseSdkMajorVersion(job.sdkVersion || job.manifest.sdkVersion);
|
||||
if (!SUPPORTED_SDK_VERSIONS.includes(targetMajorSdkVersion)) {
|
||||
throw new Error(`Unsupported SDK Version!`);
|
||||
}
|
||||
}
|
||||
|
||||
async function findSupportedSdkVersions(): Promise<number[]> {
|
||||
const SDK_DIR_PREFIX = 'sdk';
|
||||
const files = await fs.readdir(path.join(config.directories.workingDir, PLATFORMS.IOS));
|
||||
const sdks = files.filter((file) => file.startsWith(SDK_DIR_PREFIX));
|
||||
return sdks.map((sdk) => parseInt(sdk.substr(SDK_DIR_PREFIX.length), 10));
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { IJob } from 'turtle/job';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
export default async function buildSimulator(ctx: IContext, job: IJob) {
|
||||
const l = logger.child({ buildPhase: 'running simulator builder' });
|
||||
const l = logger.withFields({ buildPhase: 'running simulator builder' });
|
||||
l.info('running simulator build');
|
||||
await runShellAppBuilder(ctx, job);
|
||||
l.info('build complete');
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import path from 'path';
|
||||
|
||||
import { Credentials } from '@expo/xdl';
|
||||
import fs from 'fs-extra';
|
||||
import get from 'lodash/get';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import { AndroidKeystore } from 'xdl';
|
||||
|
||||
import * as sqs from 'turtle/aws/sqs';
|
||||
import { UPDATE_CREDENTIALS } from 'turtle/constants/build';
|
||||
@@ -13,31 +12,22 @@ import { isOffline } from 'turtle/turtleContext';
|
||||
|
||||
async function getOrCreateCredentials(jobData: IJob): Promise<IAndroidCredentials> {
|
||||
if (!jobData.credentials) {
|
||||
const l = logger.child({ buildPhase: 'generating keystore' });
|
||||
const l = logger.withFields({ buildPhase: 'generating keystore' });
|
||||
const credentials: any = {};
|
||||
l.info('Creating keystore');
|
||||
const keystoreFilename = `/tmp/keystore-${jobData.id}.tmp.jks`;
|
||||
credentials.keystorePassword = uuidv4().replace(/-/g, '');
|
||||
credentials.keyPassword = uuidv4().replace(/-/g, '');
|
||||
credentials.keystoreAlias = Buffer.from(jobData.experienceName).toString('base64');
|
||||
const androidPackage =
|
||||
get(jobData, 'manifest.android.package')
|
||||
|| get(jobData, 'config.androidPackage');
|
||||
if (!androidPackage) {
|
||||
throw new Error(
|
||||
'Android package name is not set in the app manifest (please update app.json if you\'re using turtle-cli).',
|
||||
);
|
||||
}
|
||||
const androidPackage = jobData.manifest.android.package;
|
||||
|
||||
await Credentials.Android.createKeystore(
|
||||
{
|
||||
keystorePath: keystoreFilename,
|
||||
keystorePassword: credentials.keystorePassword,
|
||||
keyPassword: credentials.keyPassword,
|
||||
keyAlias: credentials.keystoreAlias,
|
||||
},
|
||||
await AndroidKeystore.createKeystore({
|
||||
keystorePassword: credentials.keystorePassword,
|
||||
keyPassword: credentials.keyPassword,
|
||||
keystoreFilename,
|
||||
keystoreAlias: credentials.keystoreAlias,
|
||||
androidPackage,
|
||||
);
|
||||
});
|
||||
|
||||
credentials.keystore = (await fs.readFile(keystoreFilename)).toString('base64');
|
||||
l.info('Keystore created successfully');
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import path from 'path';
|
||||
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
|
||||
import config from 'turtle/config';
|
||||
import { PLATFORMS } from 'turtle/constants';
|
||||
|
||||
export function formatShellAppDirectory({ sdkVersion }: IShellAppDirectoryConfig) {
|
||||
export function formatShellAppDirectory(sdkVersion: string) {
|
||||
const majorSdkVersion = ExponentTools.parseSdkMajorVersion(sdkVersion);
|
||||
return config.builder.useLocalWorkingDir
|
||||
? path.join(config.directories.workingDir, 'local')
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import * as url from 'url';
|
||||
|
||||
import config from 'turtle/config';
|
||||
import { IJob } from 'turtle/job';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
export function getExperienceUrl(experienceName: string, publicUrl?: string) {
|
||||
export function getExperienceUrl(job: IJob) {
|
||||
const { experienceName, config: jobConfig } = job;
|
||||
const { publicUrl } = jobConfig;
|
||||
|
||||
// publicUrl is passed in if user wants to build an externally hosted app
|
||||
if (publicUrl) {
|
||||
return publicUrl;
|
||||
|
||||
@@ -18,15 +18,15 @@ export async function resizeIconWithSharpAsync(iconSizePx: number, iconFilename:
|
||||
await fs.writeFile(filename, buffer);
|
||||
}
|
||||
|
||||
export async function getImageDimensionsWithSharpAsync(dirname: string, filename: string) {
|
||||
const filepath = path.join(dirname, filename);
|
||||
export async function getImageDimensionsWithSharpAsync(basename: string, dirname: string) {
|
||||
const filename = path.join(dirname, basename);
|
||||
|
||||
// PLEASE DON'T REMOVE THE FOLLOWING LINE (sharp caches files taking path as the cache key)
|
||||
sharp.cache(false);
|
||||
|
||||
try {
|
||||
const { width, height } = await sharp(filepath).metadata();
|
||||
return { width, height };
|
||||
const meta = await sharp(filename).metadata();
|
||||
return [meta.width, meta.height];
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import path from 'path';
|
||||
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import { IosCodeSigning } from '@expo/xdl';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import * as sqs from 'turtle/aws/sqs';
|
||||
import BuildError, { BuildErrorReason } from 'turtle/builders/BuildError';
|
||||
import { UPDATE_CREDENTIALS } from 'turtle/constants/build';
|
||||
import { IJob } from 'turtle/job';
|
||||
import logger from 'turtle/logger';
|
||||
import { isOffline } from 'turtle/turtleContext';
|
||||
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const travelingFastlane = process.platform === 'darwin' ? require('@expo/traveling-fastlane-darwin')() : null;
|
||||
|
||||
async function prepareAdHocBuildCredentials(job: IJob) {
|
||||
if (process.platform !== 'darwin') {
|
||||
throw new Error('This function should be called only on macOS!');
|
||||
}
|
||||
|
||||
const { bundleIdentifier } = job.config;
|
||||
const {
|
||||
certP12,
|
||||
certPassword,
|
||||
teamId,
|
||||
appleSession,
|
||||
udids,
|
||||
provisioningProfileId,
|
||||
} = job.credentials;
|
||||
|
||||
const certSerialNumber = IosCodeSigning.findP12CertSerialNumber(certP12, certPassword);
|
||||
const args = [
|
||||
...(provisioningProfileId ? ['--profile-id', provisioningProfileId] : []),
|
||||
teamId,
|
||||
udids!.join(','),
|
||||
bundleIdentifier,
|
||||
certSerialNumber || '__last__',
|
||||
];
|
||||
|
||||
try {
|
||||
if (provisioningProfileId) {
|
||||
logger.info(
|
||||
`Using ad hoc provisioning profile id: ${provisioningProfileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const credentials = await runFastlaneAction(
|
||||
travelingFastlane.manageAdHocProvisioningProfile,
|
||||
args,
|
||||
{ env: { FASTLANE_SESSION: appleSession } },
|
||||
);
|
||||
|
||||
logger.info('New ad hoc provisioning profile successfully created');
|
||||
job.credentials.provisioningProfile = credentials.provisioningProfile;
|
||||
|
||||
if (!credentials.provisioningProfileUpdateTimestamp) {
|
||||
// dont need to persist profile because nothing changed
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOffline()) {
|
||||
const provisioningProfilePath = path.join(job.projectDir, `adhoc.mobileprovision`);
|
||||
await fs.writeFile(provisioningProfilePath, Buffer.from(credentials.provisioningProfile, 'base64'));
|
||||
logger.info(`Saved provisioning profile to ${provisioningProfilePath}`);
|
||||
} else {
|
||||
await sqs.sendMessage(job.id, UPDATE_CREDENTIALS, {
|
||||
provisioningProfileId: credentials.provisioningProfileId,
|
||||
provisioningProfile: credentials.provisioningProfile,
|
||||
});
|
||||
logger.info('Ad Hoc provisioning profile sent to storage successfully');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof BuildError) {
|
||||
logger.error('Apple Session expired! Please authenticate again using expo-cli.');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async function runFastlaneAction(action: string, args: string[], { env }: any) {
|
||||
const { stderr } = await spawnAsync(
|
||||
action,
|
||||
args,
|
||||
{
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
env: { ...process.env, ...env },
|
||||
},
|
||||
);
|
||||
const { result, ...rest } = JSON.parse(stderr.trim());
|
||||
if (result === 'failure') {
|
||||
const { reason, rawDump, type } = rest;
|
||||
let errorMsg = `Reason: ${reason}`;
|
||||
if (rawDump) {
|
||||
errorMsg += `, raw: ${JSON.stringify(rawDump)}`;
|
||||
}
|
||||
if (type === 'session-expired') {
|
||||
throw new BuildError(BuildErrorReason.SESSION_EXPIRED, errorMsg);
|
||||
} else {
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
} else {
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareAdHocBuildCredentials;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IosKeychain } from '@expo/xdl';
|
||||
import { IosKeychain } from 'xdl';
|
||||
|
||||
import { IContext } from 'turtle/builders/ios/context';
|
||||
import * as fileUtils from 'turtle/builders/utils/file';
|
||||
@@ -9,20 +9,20 @@ interface IKeychain {
|
||||
}
|
||||
|
||||
export async function create(ctx: IContext): Promise<IKeychain> {
|
||||
const l = logger.child({ buildPhase: 'creating keychain' });
|
||||
const l = logger.withFields({ buildPhase: 'creating keychain' });
|
||||
try {
|
||||
l.info('creating keychain...');
|
||||
const keychainInfo = await IosKeychain.createKeychain(ctx.appUUID, false);
|
||||
l.info('done creating keychain');
|
||||
return keychainInfo;
|
||||
} catch (err) {
|
||||
l.error({ err }, 'unable to create keychain');
|
||||
l.error('unable to create keychain');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove(ctx: IContext, keychainPath: string) {
|
||||
const l = logger.child({ buildPhase: 'deleting keychain' });
|
||||
const l = logger.withFields({ buildPhase: 'deleting keychain' });
|
||||
try {
|
||||
l.info('delete keychain...');
|
||||
const keychainInfo = await IosKeychain.deleteKeychain({
|
||||
@@ -32,7 +32,7 @@ export async function remove(ctx: IContext, keychainPath: string) {
|
||||
l.info('done deleting keychain');
|
||||
return keychainInfo;
|
||||
} catch (err) {
|
||||
l.error({ err }, 'unable to delete keychain');
|
||||
l.error('unable to delete keychain');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export async function importCert(
|
||||
certPassword,
|
||||
}: { keychainPath: string; certP12: string; certPassword: string },
|
||||
) {
|
||||
const l = logger.child({ buildPhase: 'importing certificate into keychain' });
|
||||
const l = logger.withFields({ buildPhase: 'importing certificate into keychain' });
|
||||
try {
|
||||
l.info('importing distribution certificate into keychain...');
|
||||
const { tempCertPath: certPath } = ctx;
|
||||
@@ -57,7 +57,7 @@ export async function importCert(
|
||||
await IosKeychain.importIntoKeychain({ keychainPath, certPath, certPassword });
|
||||
l.info('done importing distribution certificate into keychain');
|
||||
} catch (err) {
|
||||
l.error({ err }, 'unable to import distribution certificate into keychain');
|
||||
l.error('unable to import distribution certificate into keychain');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import path from 'path';
|
||||
import util from 'util';
|
||||
|
||||
import { ImageUtils, IosShellApp } from '@expo/xdl';
|
||||
import copy from 'copy';
|
||||
import _ from 'lodash';
|
||||
import { ImageUtils, IosShellApp } from 'xdl';
|
||||
|
||||
import { IContext } from 'turtle/builders/ios/context';
|
||||
import * as commonUtils from 'turtle/builders/utils/common';
|
||||
import * as imageHelpers from 'turtle/builders/utils/image';
|
||||
import { IOS_BUILD_TYPES } from 'turtle/constants/index';
|
||||
import { PLATFORMS } from 'turtle/constants';
|
||||
import { IJob } from 'turtle/job';
|
||||
import logger from 'turtle/logger/index';
|
||||
|
||||
@@ -18,8 +18,14 @@ export default async function runShellAppBuilder(ctx: IContext, job: IJob): Prom
|
||||
const { config: jobConfig, manifest, sdkVersion: sdkVersionFromJob } = job;
|
||||
const { buildType, releaseChannel } = jobConfig;
|
||||
const sdkVersion = _.get(manifest, 'sdkVersion', sdkVersionFromJob);
|
||||
|
||||
await copyAsync(ctx.applicationFilesSrc, ctx.baseArchiveDir);
|
||||
const applicationFilesSrc = path.join(
|
||||
ctx.workingDir,
|
||||
'shellAppBase-builds',
|
||||
buildType as string,
|
||||
'**',
|
||||
'*',
|
||||
);
|
||||
await copyAsync(applicationFilesSrc, ctx.baseArchiveDir);
|
||||
|
||||
logger.info(
|
||||
{ buildPhase: 'icons setup' },
|
||||
@@ -28,26 +34,18 @@ export default async function runShellAppBuilder(ctx: IContext, job: IJob): Prom
|
||||
ImageUtils.setResizeImageFunction(imageHelpers.resizeIconWithSharpAsync);
|
||||
ImageUtils.setGetImageDimensionsFunction(imageHelpers.getImageDimensionsWithSharpAsync);
|
||||
const shellAppParams = {
|
||||
url: commonUtils.getExperienceUrl(job),
|
||||
sdkVersion,
|
||||
action: 'configure',
|
||||
type: buildType,
|
||||
releaseChannel,
|
||||
archivePath: ctx.archiveDir,
|
||||
privateConfigData: job.config,
|
||||
expoSourcePath: path.join(ctx.workingDir, 'ios'),
|
||||
manifest,
|
||||
output: ctx.outputPath,
|
||||
verbose: true,
|
||||
output: ctx.outputPath,
|
||||
expoSourcePath: path.join(ctx.workingDir, PLATFORMS.IOS),
|
||||
manifest,
|
||||
};
|
||||
if (buildType === IOS_BUILD_TYPES.CLIENT) {
|
||||
Object.assign(shellAppParams, {
|
||||
workspacePath: path.join(ctx.workingDir, 'ios'),
|
||||
});
|
||||
} else {
|
||||
Object.assign(shellAppParams, {
|
||||
url: commonUtils.getExperienceUrl(job.experienceName, job.config.publicUrl),
|
||||
releaseChannel,
|
||||
sdkVersion,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info({ buildPhase: 'configuring NSBundle' }, 'configuring NSBundle...');
|
||||
return await IosShellApp.configureAndCopyArchiveAsync(shellAppParams);
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import path from 'path';
|
||||
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
|
||||
import config from 'turtle/config';
|
||||
import { IOS_BUILD_TYPES, PLATFORMS } from 'turtle/constants';
|
||||
import { PLATFORMS } from 'turtle/constants';
|
||||
|
||||
export function formatShellAppDirectory({ sdkVersion, buildType }: IShellAppDirectoryConfig) {
|
||||
const { workingDir } = config.directories;
|
||||
if (buildType === IOS_BUILD_TYPES.CLIENT) {
|
||||
return path.join(workingDir, PLATFORMS.IOS, 'client');
|
||||
} else if (config.builder.useLocalWorkingDir) {
|
||||
return path.join(workingDir, 'local');
|
||||
} else {
|
||||
const majorVersion = ExponentTools.parseSdkMajorVersion(sdkVersion);
|
||||
return path.join(workingDir, PLATFORMS.IOS, `sdk${majorVersion}`);
|
||||
}
|
||||
export function formatShellAppDirectory(sdkVersion: string) {
|
||||
const majorVersion = ExponentTools.parseSdkMajorVersion(sdkVersion);
|
||||
return config.builder.useLocalWorkingDir
|
||||
? path.join(config.directories.workingDir, 'local')
|
||||
: path.join(config.directories.workingDir, PLATFORMS.IOS, `sdk${majorVersion}`);
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import fs from 'fs-extra';
|
||||
import filter from 'lodash/filter';
|
||||
import keys from 'lodash/keys';
|
||||
import merge from 'lodash/merge';
|
||||
import values from 'lodash/values';
|
||||
import path from 'path';
|
||||
import logger from 'turtle/logger';
|
||||
|
||||
export interface IUnimoduleEntry {
|
||||
name: string;
|
||||
version: string;
|
||||
dirname: string;
|
||||
}
|
||||
|
||||
interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
dirname: string; // could be different than name
|
||||
dependencies: string[];
|
||||
}
|
||||
|
||||
export async function resolveNativeModules(workingdir: string, packages?: string[]): Promise<IUnimoduleEntry[]> {
|
||||
const resolver = new Resolver(workingdir);
|
||||
await resolver.init();
|
||||
|
||||
if (!packages) {
|
||||
resolver.addAll();
|
||||
} else {
|
||||
const corePackages = await readCorePackages(workingdir);
|
||||
for (const pkg of corePackages) {
|
||||
if (resolver.isUnimodule(pkg)) {
|
||||
resolver.addModule(pkg);
|
||||
}
|
||||
}
|
||||
for (const pkg of packages) {
|
||||
if (resolver.isUnimodule(pkg)) {
|
||||
resolver.addModule(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolver.getModules();
|
||||
}
|
||||
|
||||
async function readCorePackages(workingdir: string) {
|
||||
const pkgJsonPath = path.join(workingdir, 'node_modules', 'react-native-unimodules', 'package.json');
|
||||
const expoPkgJsonPath = path.join(workingdir, 'packages', 'expo', 'package.json');
|
||||
if (await fs.pathExists(pkgJsonPath) && await fs.pathExists(expoPkgJsonPath)) {
|
||||
const packageJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf-8'));
|
||||
const expoPackageJson = JSON.parse(await fs.readFile(expoPkgJsonPath, 'utf-8'));
|
||||
return keys(packageJson.dependencies).concat(keys(expoPackageJson.dependencies));
|
||||
} else {
|
||||
logger.warn('No core packages detected');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
interface IDependencyInfo {
|
||||
[key: string]: IPackageInfo;
|
||||
}
|
||||
|
||||
class Resolver {
|
||||
private workingdir: string;
|
||||
private modulesMap: {[key: string]: IUnimoduleEntry};
|
||||
private dependencyMap: IDependencyInfo;
|
||||
|
||||
constructor(workingdir: string) {
|
||||
this.workingdir = workingdir;
|
||||
this.modulesMap = {};
|
||||
this.dependencyMap = {};
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.dependencyMap = await generateDependenciesInfo(this.workingdir);
|
||||
}
|
||||
|
||||
public isUnimodule(moduleName: string): boolean {
|
||||
return !!this.dependencyMap[moduleName];
|
||||
}
|
||||
|
||||
public addAll() {
|
||||
for (const moduleName of Object.keys(this.dependencyMap)) {
|
||||
this.addModule(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
public addModule(moduleName: string) {
|
||||
if (this.dependencyMap[moduleName]) {
|
||||
this.resolveModule(moduleName);
|
||||
} else {
|
||||
throw new Error('Attempting to add unknown module');
|
||||
}
|
||||
}
|
||||
|
||||
public getModules(): IUnimoduleEntry[] {
|
||||
return values(this.modulesMap);
|
||||
}
|
||||
|
||||
private async resolveModule(moduleName: string) {
|
||||
if (this.modulesMap[moduleName]) {
|
||||
return;
|
||||
}
|
||||
const pkg = this.dependencyMap[moduleName];
|
||||
logger.info(`Adding ${moduleName}:${pkg.version}`);
|
||||
this.modulesMap[moduleName] = {
|
||||
name: pkg.name,
|
||||
dirname: pkg.dirname,
|
||||
version: pkg.version,
|
||||
};
|
||||
for (const dep of pkg.dependencies) {
|
||||
this.resolveModule(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function generateDependenciesInfo(workingdir: string): Promise<IDependencyInfo> {
|
||||
const dir = await fs.readdir(path.join(workingdir, 'packages'));
|
||||
const map: IDependencyInfo = {};
|
||||
|
||||
// scrap directory for package metadata
|
||||
for (const pkgName of dir) {
|
||||
const pkg = await getPackage(workingdir, pkgName);
|
||||
if (pkg) {
|
||||
map[pkg.name] = pkg;
|
||||
}
|
||||
}
|
||||
|
||||
// handle @unimodules directory
|
||||
const unimodulesDir = await fs.readdir(path.join(workingdir, 'packages', '@unimodules'));
|
||||
for (const pkgName of unimodulesDir) {
|
||||
const pkg = await getPackage(workingdir, path.join('@unimodules', pkgName));
|
||||
if (pkg) {
|
||||
map[pkg.name] = pkg;
|
||||
}
|
||||
}
|
||||
|
||||
// remove non unimodule dependencies
|
||||
for (const pkg of Object.keys(map)) {
|
||||
map[pkg].dependencies = filter(map[pkg].dependencies, (dep) => !!map[dep]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
async function getPackage(workingdir: string, pkgName: string): Promise<IPackageInfo | null> {
|
||||
const pkgPath = path.join(workingdir, 'packages', pkgName);
|
||||
const isAndroid = await fs.pathExists(path.join(pkgPath, 'android'));
|
||||
const isIos = await fs.pathExists(path.join(pkgPath, 'ios'));
|
||||
const isUnimodule = await fs.pathExists(path.join(pkgPath, 'unimodule.json'));
|
||||
const hasPackageJson = await fs.pathExists(path.join(pkgPath, 'package.json'));
|
||||
if (!isUnimodule || !hasPackageJson || (!isAndroid && !isIos)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const packageJson = JSON.parse(await fs.readFile(path.join(pkgPath, 'package.json'), 'utf-8'));
|
||||
|
||||
const pkgDeps = merge({}, // reading everything to make sure
|
||||
packageJson.dependencies,
|
||||
packageJson.peerDependencies,
|
||||
packageJson.unimodulePeerDependencies,
|
||||
);
|
||||
const info: IPackageInfo = {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
dirname: pkgName,
|
||||
dependencies: Object.keys(pkgDeps),
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
@@ -12,14 +12,14 @@ interface IUploadCtx {
|
||||
|
||||
export async function uploadBuildToS3(ctx: IUploadCtx) {
|
||||
if (config.builder.fakeUpload) {
|
||||
const l = logger.child({ buildPhase: 'copying build artifact' });
|
||||
const l = logger.withFields({ buildPhase: 'copying build artifact' });
|
||||
const { fakeUploadBuildPath, uploadPath } = ctx;
|
||||
l.info('copying build to fake upload directory');
|
||||
await fs.copy(uploadPath, fakeUploadBuildPath as string);
|
||||
l.info(`copied build to ${fakeUploadBuildPath}`);
|
||||
return fakeUploadBuildPath as string;
|
||||
} else {
|
||||
const l = logger.child({ buildPhase: 'uploading to S3' });
|
||||
const l = logger.withFields({ buildPhase: 'uploading to S3' });
|
||||
l.info('uploading build artifact to S3');
|
||||
const { Location: fileLocation } = await uploadFile({
|
||||
key: ctx.s3FileKey as string,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { ExponentTools } from '@expo/xdl';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import config from 'turtle/config';
|
||||
import { IJob } from 'turtle/job';
|
||||
|
||||
interface IVersionsMap {
|
||||
[platform: string]: number[];
|
||||
}
|
||||
|
||||
const versions: IVersionsMap = {};
|
||||
|
||||
export async function ensureCanBuildSdkVersion(job: IJob) {
|
||||
if (config.builder.useLocalWorkingDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
const platformVersions = await findSupportedSdkVersions(job.platform);
|
||||
const targetMajorSdkVersion = ExponentTools.parseSdkMajorVersion(job.sdkVersion || job.manifest.sdkVersion);
|
||||
if (!platformVersions.includes(targetMajorSdkVersion)) {
|
||||
throw new Error(`Unsupported SDK Version!`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function findSupportedSdkVersions(platform: 'android' | 'ios'): Promise<number[]> {
|
||||
if (versions[platform]) {
|
||||
return versions[platform];
|
||||
} else {
|
||||
const SDK_DIR_PREFIX = 'sdk';
|
||||
const files = await fs.readdir(path.join(config.directories.workingDir, platform));
|
||||
const sdks = files.filter((file) => file.startsWith(SDK_DIR_PREFIX));
|
||||
versions[platform] = sdks.map((sdk) => parseInt(sdk.substr(SDK_DIR_PREFIX.length), 10));
|
||||
return versions[platform];
|
||||
}
|
||||
}
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user