mirror of
https://github.com/expo/turtle.git
synced 2026-11-14 19:12:55 -10:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09045d6422 | ||
|
|
22e47e4e43 | ||
|
|
1d4728e402 | ||
|
|
de4837f361 | ||
|
|
20d393e48e | ||
|
|
95714bdf5e | ||
|
|
8e2b593e3e | ||
|
|
885cf08a9d | ||
|
|
bd816e1124 | ||
|
|
98549f147c | ||
|
|
9de691c60e | ||
|
|
71e270dc50 | ||
|
|
26e684069d | ||
|
|
1c67df516e | ||
|
|
d7ed489876 | ||
|
|
267e487494 | ||
|
|
b07d830eb9 | ||
|
|
bfaaf09e57 | ||
|
|
33b2a569aa | ||
|
|
2febf6bcdd | ||
|
|
e2c9ee0e39 | ||
|
|
f017540571 | ||
|
|
5b65eb3d6e | ||
|
|
60874f31c9 | ||
|
|
78beb3c0b3 | ||
|
|
03633d94e5 | ||
|
|
392e4487b7 | ||
|
|
f316cb0e0c | ||
|
|
968ce7d8bb | ||
|
|
5876a11568 | ||
|
|
7448e1f4e4 | ||
|
|
4338e71005 | ||
|
|
7323412095 |
No files matched your search
+195
-9
@@ -1,4 +1,139 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
nix: &nix
|
||||
docker:
|
||||
- image: lnl7/nix:2.1.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.direnv \
|
||||
nixpkgs.findutils \
|
||||
nixpkgs.gawk \
|
||||
nixpkgs.git \
|
||||
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 >>"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -9,17 +144,68 @@ jobs:
|
||||
- run: yarn
|
||||
- run: yarn lint
|
||||
- run: yarn build
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/node:8.12
|
||||
|
||||
unit_tests:
|
||||
executor: nix
|
||||
steps:
|
||||
- checkout
|
||||
- setup
|
||||
- restore_yarn_cache
|
||||
- run: yarn
|
||||
- run: ./scripts/testUnitCI.sh
|
||||
- save_yarn_cache
|
||||
- run:
|
||||
name: Run iOS smoke 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
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
checks:
|
||||
jobs:
|
||||
- build
|
||||
- test
|
||||
tests:
|
||||
jobs:
|
||||
- unit_tests
|
||||
- ios_smoke_tests
|
||||
- android_smoke_tests
|
||||
@@ -15,4 +15,6 @@ export NODE_ENV=development
|
||||
|
||||
export EXPO_TURTLE_TOOLS_DIR="$(pwd)/tools"
|
||||
|
||||
PATH_add $EXPO_TURTLE_TOOLS_DIR/bin
|
||||
if [ -z "$EXPO_UNIVERSE_DIR" ]; then
|
||||
PATH_add $EXPO_TURTLE_TOOLS_DIR/bin
|
||||
fi
|
||||
+3
-1
@@ -1,10 +1,12 @@
|
||||
!/src/node_modules
|
||||
|
||||
node_modules
|
||||
yarn-error.log
|
||||
|
||||
/build/
|
||||
/workingdir/*
|
||||
/artifacts/*
|
||||
shell.apk
|
||||
node_modules
|
||||
.secrets*
|
||||
|
||||
*.swp
|
||||
|
||||
@@ -2,7 +2,6 @@ LOGGER_LEVEL="debug"
|
||||
API_PROTOCOL="http"
|
||||
API_HOSTNAME="localhost"
|
||||
API_PORT="3000"
|
||||
LOGGLY_TOKEN=""
|
||||
SENTRY_DSN=""
|
||||
ENVIRONMENT="development"
|
||||
REDIS_URL="redis://localhost:6379/0"
|
||||
|
||||
+35
-2
@@ -6,9 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Register Turtle version in Redis
|
||||
- Turtle smoke tests are now being executed on CircleCI from the context of this repository.
|
||||
### Changed
|
||||
- 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.8] - 2019-02-06
|
||||
## [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
|
||||
- Register 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
|
||||
|
||||
@@ -102,13 +102,6 @@ ENV PATH ${GRADLE_HOME}/bin:$PATH
|
||||
# Install Gulp
|
||||
RUN npm install -g gulp-cli
|
||||
|
||||
# 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 \
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(test|spec)\\.ts$",
|
||||
"moduleFileExtensions": ["ts", "js"],
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"verbose": true
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testMatch": [ "**/__smoke__/*.test.ts" ],
|
||||
"moduleFileExtensions": ["ts", "js"],
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"verbose": true
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
"testMatch": [ "**/__tests__/*.ts" ],
|
||||
"moduleFileExtensions": ["ts", "js"],
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"verbose": true
|
||||
}
|
||||
@@ -50,8 +50,6 @@ 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
|
||||
@@ -100,11 +98,6 @@ 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:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
self: super:
|
||||
|
||||
{
|
||||
nodejs = super.nodejs-8_x;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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
|
||||
''}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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) ];
|
||||
})
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"url": "https://github.com/NixOS/nixpkgs-channels.git",
|
||||
"rev": "80738ed9dc0ce48d7796baed5364eef8072c794d",
|
||||
"date": "2018-11-17T11:18:10+01:00",
|
||||
"sha256": "0anmvr6b47gbbyl9v2fn86mfkcwgpbd5lf0yf3drgm8pbv57c1dc",
|
||||
"fetchSubmodules": false
|
||||
}
|
||||
+23
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "turtle-cli",
|
||||
"version": "0.5.10",
|
||||
"version": "0.5.14",
|
||||
"description": "The command-line tool for building standalone Expo apps",
|
||||
"preferGlobal": true,
|
||||
"scripts": {
|
||||
@@ -10,6 +10,7 @@
|
||||
"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": "yarn build",
|
||||
"build": "gulp build:server",
|
||||
"build:production": "./scripts/build.sh",
|
||||
@@ -26,7 +27,7 @@
|
||||
"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": "yarn test:smoke --testNamePattern=android",
|
||||
"test:smoke:android": "PLATFORM=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",
|
||||
@@ -38,6 +39,17 @@
|
||||
"set-priorities:staging": "expenv babel-node scripts/setConfigurations.js",
|
||||
"set-priorities:production": "EXPENV_OVERRIDES=prod expenv babel-node scripts/setConfigurations.js"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.ts": [
|
||||
"yarn lint:fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"bin": {
|
||||
"turtle": "./bin/turtle.js"
|
||||
},
|
||||
@@ -88,7 +100,9 @@
|
||||
"gulp-plumber": "^1.2.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-typescript": "^4.0.2",
|
||||
"husky": "^1.3.1",
|
||||
"jest": "^23.6.0",
|
||||
"lint-staged": "^8.1.5",
|
||||
"nock": "^10.0.1",
|
||||
"nodemon": "^1.18.7",
|
||||
"release-it": "^10.0.0",
|
||||
@@ -96,14 +110,16 @@
|
||||
"source-map-loader": "^0.2.3",
|
||||
"ts-jest": "^23.10.4",
|
||||
"tslint": "^5.10.0",
|
||||
"tslint-eslint-rules": "^5.4.0",
|
||||
"typescript": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/spawn-async": "^1.4.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",
|
||||
@@ -122,6 +138,9 @@
|
||||
"tar": "^4.4.6",
|
||||
"uuid": "^3.2.1",
|
||||
"which": "^1.3.1",
|
||||
"xdl": "52.0.14-alpha.6+371be041"
|
||||
"xdl": "53.1.6-alpha.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@expo/traveling-fastlane-darwin": "^1.8.1"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
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
|
||||
$ROOT_DIR/scripts/fetchRemoteTarballs.sh android
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/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
|
||||
@@ -1,24 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
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
|
||||
$ROOT_DIR/scripts/fetchRemoteTarballs.sh ios
|
||||
@@ -1,37 +1,25 @@
|
||||
#!/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_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_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_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="localhost"
|
||||
export API_PORT="3000"
|
||||
export API_HOSTNAME="staging.expo.io"
|
||||
export API_PORT="443"
|
||||
export REDIS_URL="noop"
|
||||
export REDIS_CONFIG_URL="noop"
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
$ROOT_DIR/scripts/android/fetchRemoteAndroidTarball.sh
|
||||
|
||||
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,13 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ueo pipefail
|
||||
|
||||
export NODE_ENV=test
|
||||
|
||||
export ENVIRONMENT="development"
|
||||
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_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_REGION="aws.west2"
|
||||
export AWS_S3_BUCKET="exp-shell-app-assets"
|
||||
export AWS_S3_REGION="us-west-1"
|
||||
@@ -18,18 +20,7 @@ export API_PORT="443"
|
||||
export REDIS_URL="noop"
|
||||
export REDIS_CONFIG_URL="noop"
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
$ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
SDK_VERSION=sdk32 $ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
|
||||
|
||||
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,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ueo pipefail
|
||||
|
||||
export NODE_ENV=test
|
||||
|
||||
export ENVIRONMENT="development"
|
||||
@@ -21,6 +23,3 @@ export REDIS_URL="noop"
|
||||
export REDIS_CONFIG_URL="noop"
|
||||
|
||||
yarn test:unit
|
||||
unit_result=$?
|
||||
|
||||
exit $unit_result
|
||||
@@ -0,0 +1 @@
|
||||
s3://exp-artifacts/ios-expo-client-15bb883c22173d5b01df19421580170e776041d0.tar.gz
|
||||
@@ -1 +1 @@
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-f6e1921542b2e26b4b9221dd2eddb74ea5cfdf74.tar.gz
|
||||
s3://exp-artifacts/ios-shell-builder-sdk-latest-d9cabffbcba20e074e91e4d7f8d862c98acfc680.tar.gz
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -179,7 +179,7 @@ function _pushMetrics(metrics: IMetricsChunk[]) {
|
||||
metrics.forEach((metricsChunk) => {
|
||||
cloudWatch.putMetricData(metricsChunk, (err) => {
|
||||
if (err) {
|
||||
logger.warn('[cloudwatch]', err);
|
||||
logger.warn({ err }, '[cloudwatch]');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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('Receive error', err);
|
||||
logger.error({ err }, 'Receive error');
|
||||
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 proccessing the message, so it does not
|
||||
// that we're still processing 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('Error at change msg visibility', err);
|
||||
logger.warn({ err }, 'Error at change msg visibility');
|
||||
});
|
||||
}
|
||||
}, (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(`Error sending SQS message: ${JSON.stringify(params)}}`, err);
|
||||
logger.error({ err }, `Error sending SQS message: ${JSON.stringify(params)}}`);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@ 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 {
|
||||
|
||||
@@ -33,8 +33,7 @@ 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(`Failed to setup environment for ${platform} builds`);
|
||||
logger.error(err.stack);
|
||||
logger.error({ err }, `Failed to setup environment for ${platform} builds`);
|
||||
if (err instanceof ErrorWithCommandHelp) {
|
||||
cmd.help();
|
||||
} else if (err instanceof ErrorWithProgramHelp) {
|
||||
|
||||
+4
-4
@@ -17,16 +17,16 @@ Command.prototype.asyncAction = function asyncAction(asyncFn: (...asyncFnArgs: a
|
||||
return this.action(async (...args) => {
|
||||
try {
|
||||
await checkForUpdateAsync();
|
||||
} catch (e) {
|
||||
logger.warn('Failed to check for turtle-cli update.');
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'Failed to check for turtle-cli update.');
|
||||
}
|
||||
return await asyncFn(...args);
|
||||
});
|
||||
};
|
||||
|
||||
export function run(programName: string) {
|
||||
runAsync(programName).catch((e) => {
|
||||
logger.error('Uncaught Error', e);
|
||||
runAsync(programName).catch((err) => {
|
||||
logger.error({ err }, 'Uncaught Error');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ 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.withFields({ buildPhase: 'setting up environment' });
|
||||
|
||||
interface IShellAppFormaters {
|
||||
formatShellAppDirectory: (sdkVersion: string) => string;
|
||||
formatShellAppDirectory: (config: IShellAppDirectoryConfig) => string;
|
||||
formatShellAppTarballUriPath: (sdkMajorVersion: string) => string;
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ export async function ensureShellAppIsPresent(
|
||||
formatters: IShellAppFormaters,
|
||||
postDownloadAction?: PostDownloadAction,
|
||||
) {
|
||||
const workingdir = formatters.formatShellAppDirectory(sdkVersion);
|
||||
const workingdir = formatters.formatShellAppDirectory({ sdkVersion });
|
||||
if (await fs.pathExists(workingdir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ export async function ensureToolsAreInstalled(tools: IToolDefinition[]) {
|
||||
} catch (err) {
|
||||
isAnyToolMissing = true;
|
||||
if (!testFn) {
|
||||
l.error(`${command} is missing in your $PATH`);
|
||||
l.error({ err }, `${command} is missing in your $PATH`);
|
||||
}
|
||||
l.error(missingDescription);
|
||||
l.error({ err }, missingDescription);
|
||||
}
|
||||
}
|
||||
if (isAnyToolMissing) {
|
||||
|
||||
@@ -60,7 +60,7 @@ export function createBuilderAction({
|
||||
publicUrl: cmd.publicUrl,
|
||||
};
|
||||
|
||||
const appJSON = await ProjectUtils.loadAppJSON(projectDirArg);
|
||||
const appJSON = await ProjectUtils.loadAppJSON(projectDirArg, cmd.config);
|
||||
const sdkVersion = _.get(appJSON, 'expo.sdkVersion');
|
||||
await setup(platform, sdkVersion);
|
||||
const credentials = await prepareCredentials(cmd);
|
||||
@@ -72,8 +72,7 @@ export function createBuilderAction({
|
||||
const job = await sanitizeJob(rawJob);
|
||||
await builder(job);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to build standalone app: ${err.message}`);
|
||||
logger.error(err.stack);
|
||||
logger.error({ err }, 'Failed to build standalone app');
|
||||
if (err instanceof ErrorWithCommandHelp) {
|
||||
command.help();
|
||||
} else if (err instanceof ErrorWithProgramHelp) {
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
export async function loadAppJSON(projectDirArg: string) {
|
||||
const projectDir = resolveAbsoluteDir(projectDirArg);
|
||||
const appJSONPath = path.join(projectDir, 'app.json');
|
||||
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');
|
||||
}
|
||||
const appJSONExists = await fs.pathExists(appJSONPath);
|
||||
if (!appJSONExists) {
|
||||
throw new Error(`Couldn't find app.json in ${projectDir} directory.`);
|
||||
throw new Error(`Couldn't find app.json.`);
|
||||
} else {
|
||||
return require(appJSONPath);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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 };
|
||||
@@ -10,11 +10,13 @@ import { formatShellAppDirectory } from 'turtle/builders/utils/android/workingdi
|
||||
import * as commonUtils from 'turtle/builders/utils/common';
|
||||
import * as imageHelpers from 'turtle/builders/utils/image';
|
||||
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 apkFilePath = await runShellAppBuilder(jobData, credentials);
|
||||
const randomHex = uuidv4().replace(/-/g, '');
|
||||
@@ -60,20 +62,21 @@ async function runShellAppBuilder(
|
||||
|
||||
const outputFilePath = path.join(temporaryFilesRoot, `shell-signed-${jobData.id}.apk`);
|
||||
|
||||
const { manifest: { sdkVersion: sdkVersionFromManifest = null } = {}, sdkVersion: sdkVersionFromJob } = jobData;
|
||||
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
|
||||
const workingDir = formatShellAppDirectory(sdkVersion);
|
||||
const { config: jobConfig, manifest, sdkVersion: sdkVersionFromJob } = jobData;
|
||||
const sdkVersion = _.get(manifest, 'sdkVersion', sdkVersionFromJob);
|
||||
const workingDir = formatShellAppDirectory({ sdkVersion });
|
||||
|
||||
try {
|
||||
await AndroidShellApp.createAndroidShellAppAsync({
|
||||
url: commonUtils.getExperienceUrl(jobData),
|
||||
sdkVersion: _.get(jobData, 'manifest.sdkVersion') || jobData.sdkVersion,
|
||||
sdkVersion,
|
||||
keystore: tempKeystorePath,
|
||||
manifest,
|
||||
alias: credentials.keystoreAlias,
|
||||
keystorePassword: credentials.keystorePassword,
|
||||
keyPassword: credentials.keyPassword,
|
||||
privateConfigFile: tempShellAppConfigPath,
|
||||
releaseChannel: jobData.config.releaseChannel,
|
||||
releaseChannel: jobConfig.releaseChannel,
|
||||
workingDir,
|
||||
outputFile: outputFilePath,
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, m
|
||||
l.info('building and signing IPA');
|
||||
|
||||
const {
|
||||
credentials: { provisioningProfile, certPassword, teamId, password },
|
||||
credentials: { provisioningProfile, certPassword, teamId },
|
||||
config: { bundleIdentifier: bundleIdentifierFromConfig },
|
||||
} = job;
|
||||
|
||||
@@ -51,9 +51,8 @@ async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, m
|
||||
certPath: ctx.tempCertPath,
|
||||
certPassword,
|
||||
teamID: teamId,
|
||||
password,
|
||||
bundleIdentifier,
|
||||
workingDir: ctx.workingDir,
|
||||
workspacePath: ctx.workspacePath,
|
||||
manifest,
|
||||
});
|
||||
await ipaBuilder.build();
|
||||
|
||||
+53
-12
@@ -22,9 +22,11 @@ export interface IContext {
|
||||
tempCertPath: string;
|
||||
uploadPath: string;
|
||||
workingDir: string;
|
||||
workspacePath: string;
|
||||
applicationFilesSrc: string;
|
||||
}
|
||||
|
||||
const { DEFAULT_EXPOKIT_WORKSPACE_NAME } = IosShellApp;
|
||||
const { EXPOKIT_APP, EXPONENT_APP } = IosShellApp;
|
||||
const { BUILD_TYPES } = IOS;
|
||||
|
||||
export function createBuilderContext(job: IJob): IContext {
|
||||
@@ -39,7 +41,7 @@ export function createBuilderContext(job: IJob): IContext {
|
||||
} = job;
|
||||
|
||||
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
|
||||
const workingDir = formatShellAppDirectory(sdkVersion);
|
||||
const workingDir = formatShellAppDirectory({ sdkVersion, buildType: buildType! });
|
||||
|
||||
const context: any = {
|
||||
appDir: join(config.directories.temporaryFilesRoot, appUUID),
|
||||
@@ -55,24 +57,63 @@ export function createBuilderContext(job: IJob): IContext {
|
||||
context.tempCertPath = join(context.appDir, 'cert.p12');
|
||||
context.baseArchiveDir = join(context.appDir, 'archive');
|
||||
|
||||
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',
|
||||
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`,
|
||||
if (buildType === BUILD_TYPES.CLIENT) {
|
||||
context.applicationFilesSrc = join(
|
||||
workingDir,
|
||||
'expo-client-build',
|
||||
'**',
|
||||
'*',
|
||||
);
|
||||
} else if (buildType === BUILD_TYPES.ARCHIVE) {
|
||||
} else {
|
||||
context.applicationFilesSrc = join(
|
||||
workingDir,
|
||||
'shellAppBase-builds',
|
||||
buildType as string,
|
||||
'**',
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
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`,
|
||||
`${EXPOKIT_APP}.xcarchive`,
|
||||
'Products',
|
||||
'Applications',
|
||||
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`,
|
||||
`${EXPOKIT_APP}.app`,
|
||||
);
|
||||
context.workspacePath = path.join(
|
||||
workingDir,
|
||||
'shellAppWorkspaces',
|
||||
'ios',
|
||||
'default',
|
||||
`${EXPOKIT_APP}.xcworkspace`,
|
||||
);
|
||||
} else if (buildType === 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 === 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`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+14
-32
@@ -1,24 +1,23 @@
|
||||
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, PLATFORMS } from 'turtle/constants/index';
|
||||
import { IOS } 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> {
|
||||
await ensureCanBuildSdkVersion(job);
|
||||
if (job.config.buildType !== BUILD_TYPES.CLIENT) {
|
||||
await ensureCanBuildSdkVersion(job);
|
||||
}
|
||||
|
||||
const ctx = createBuilderContext(job);
|
||||
|
||||
@@ -26,10 +25,15 @@ export default async function iosBuilder(job: IJob): Promise<IJobResult> {
|
||||
await initBuilder(ctx);
|
||||
|
||||
const { buildType } = job.config;
|
||||
if (buildType === BUILD_TYPES.SIMULATOR) {
|
||||
await buildSimulator(ctx, job);
|
||||
} else if (buildType === BUILD_TYPES.ARCHIVE) {
|
||||
|
||||
if (buildType === BUILD_TYPES.CLIENT) {
|
||||
await prepareAdHocBuildCredentials(job);
|
||||
}
|
||||
|
||||
if ([BUILD_TYPES.ARCHIVE, BUILD_TYPES.CLIENT].includes(buildType!)) {
|
||||
await buildArchive(ctx, job);
|
||||
} else if (buildType === BUILD_TYPES.SIMULATOR) {
|
||||
await buildSimulator(ctx, job);
|
||||
} else {
|
||||
throw new Error(`Unsupported iOS build type: ${buildType}`);
|
||||
}
|
||||
@@ -60,25 +64,3 @@ 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));
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import path from 'path';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import { AndroidKeystore } from 'xdl';
|
||||
import { Credentials } from 'xdl';
|
||||
|
||||
import * as sqs from 'turtle/aws/sqs';
|
||||
import { UPDATE_CREDENTIALS } from 'turtle/constants/build';
|
||||
@@ -21,13 +21,15 @@ async function getOrCreateCredentials(jobData: IJob): Promise<IAndroidCredential
|
||||
credentials.keystoreAlias = Buffer.from(jobData.experienceName).toString('base64');
|
||||
const androidPackage = jobData.manifest.android.package;
|
||||
|
||||
await AndroidKeystore.createKeystore({
|
||||
keystorePassword: credentials.keystorePassword,
|
||||
keyPassword: credentials.keyPassword,
|
||||
keystoreFilename,
|
||||
keystoreAlias: credentials.keystoreAlias,
|
||||
await Credentials.Android.createKeystore(
|
||||
{
|
||||
keystorePath: keystoreFilename,
|
||||
keystorePassword: credentials.keystorePassword,
|
||||
keyPassword: credentials.keyPassword,
|
||||
keyAlias: credentials.keystoreAlias,
|
||||
},
|
||||
androidPackage,
|
||||
});
|
||||
);
|
||||
|
||||
credentials.keystore = (await fs.readFile(keystoreFilename)).toString('base64');
|
||||
l.info('Keystore created successfully');
|
||||
|
||||
@@ -2,10 +2,11 @@ import path from 'path';
|
||||
|
||||
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: string) {
|
||||
export function formatShellAppDirectory({ sdkVersion }: IShellAppDirectoryConfig) {
|
||||
const majorSdkVersion = ExponentTools.parseSdkMajorVersion(sdkVersion);
|
||||
return config.builder.useLocalWorkingDir
|
||||
? path.join(config.directories.workingDir, 'local')
|
||||
|
||||
@@ -18,15 +18,15 @@ export async function resizeIconWithSharpAsync(iconSizePx: number, iconFilename:
|
||||
await fs.writeFile(filename, buffer);
|
||||
}
|
||||
|
||||
export async function getImageDimensionsWithSharpAsync(basename: string, dirname: string) {
|
||||
const filename = path.join(dirname, basename);
|
||||
export async function getImageDimensionsWithSharpAsync(dirname: string, filename: string) {
|
||||
const filepath = path.join(dirname, filename);
|
||||
|
||||
// PLEASE DON'T REMOVE THE FOLLOWING LINE (sharp caches files taking path as the cache key)
|
||||
sharp.cache(false);
|
||||
|
||||
try {
|
||||
const meta = await sharp(filename).metadata();
|
||||
return [meta.width, meta.height];
|
||||
const { width, height } = await sharp(filepath).metadata();
|
||||
return { width, height };
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import path from 'path';
|
||||
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import fs from 'fs-extra';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { IosCodeSigning } from 'xdl';
|
||||
|
||||
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 } = job.credentials;
|
||||
|
||||
const certSerialNumber = IosCodeSigning.findP12CertSerialNumber(certP12, certPassword);
|
||||
const args = [
|
||||
teamId,
|
||||
udids!.join(','),
|
||||
bundleIdentifier,
|
||||
certSerialNumber || '__last__',
|
||||
];
|
||||
|
||||
try {
|
||||
const credentials = await runFastlaneAction(
|
||||
travelingFastlane.manageAdHocProvisioningProfile,
|
||||
args,
|
||||
{ env: { FASTLANE_SESSION: appleSession } },
|
||||
);
|
||||
if (isEmpty(credentials)) {
|
||||
if (!job.credentials.provisioningProfile) {
|
||||
// a user has generated the provisioning profile for his app, but he probably deleted it from Expo servers, ...
|
||||
throw new BuildError(
|
||||
BuildErrorReason.PROVISIONING_PROFILE_MISSING,
|
||||
`Provisioning profile is missing on Expo servers.`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('New ad hoc provisioning profile successfully created');
|
||||
job.credentials.provisioningProfile = credentials.provisioningProfile;
|
||||
|
||||
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;
|
||||
@@ -16,7 +16,7 @@ export async function create(ctx: IContext): Promise<IKeychain> {
|
||||
l.info('done creating keychain');
|
||||
return keychainInfo;
|
||||
} catch (err) {
|
||||
l.error('unable to create keychain');
|
||||
l.error({ err }, 'unable to create keychain');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export async function remove(ctx: IContext, keychainPath: string) {
|
||||
l.info('done deleting keychain');
|
||||
return keychainInfo;
|
||||
} catch (err) {
|
||||
l.error('unable to delete keychain');
|
||||
l.error({ err }, 'unable to delete keychain');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -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('unable to import distribution certificate into keychain');
|
||||
l.error({ err }, 'unable to import distribution certificate into keychain');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ 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 { PLATFORMS } from 'turtle/constants';
|
||||
import { IOS } from 'turtle/constants/index';
|
||||
import { IJob } from 'turtle/job';
|
||||
import logger from 'turtle/logger/index';
|
||||
|
||||
@@ -18,14 +18,8 @@ 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);
|
||||
const applicationFilesSrc = path.join(
|
||||
ctx.workingDir,
|
||||
'shellAppBase-builds',
|
||||
buildType as string,
|
||||
'**',
|
||||
'*',
|
||||
);
|
||||
await copyAsync(applicationFilesSrc, ctx.baseArchiveDir);
|
||||
|
||||
await copyAsync(ctx.applicationFilesSrc, ctx.baseArchiveDir);
|
||||
|
||||
logger.info(
|
||||
{ buildPhase: 'icons setup' },
|
||||
@@ -34,18 +28,26 @@ 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,
|
||||
verbose: true,
|
||||
output: ctx.outputPath,
|
||||
expoSourcePath: path.join(ctx.workingDir, PLATFORMS.IOS),
|
||||
expoSourcePath: path.join(ctx.workingDir, 'ios'),
|
||||
manifest,
|
||||
output: ctx.outputPath,
|
||||
verbose: true,
|
||||
};
|
||||
if (buildType === IOS.BUILD_TYPES.CLIENT) {
|
||||
Object.assign(shellAppParams, {
|
||||
workspacePath: path.join(ctx.workingDir, 'ios'),
|
||||
});
|
||||
} else {
|
||||
Object.assign(shellAppParams, {
|
||||
url: commonUtils.getExperienceUrl(job),
|
||||
releaseChannel,
|
||||
sdkVersion,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info({ buildPhase: 'configuring NSBundle' }, 'configuring NSBundle...');
|
||||
return await IosShellApp.configureAndCopyArchiveAsync(shellAppParams);
|
||||
|
||||
@@ -2,12 +2,18 @@ import path from 'path';
|
||||
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
|
||||
import config from 'turtle/config';
|
||||
import { PLATFORMS } from 'turtle/constants';
|
||||
import { IOS, PLATFORMS } from 'turtle/constants';
|
||||
|
||||
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}`);
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { ExponentTools } from 'xdl';
|
||||
|
||||
import config from 'turtle/config';
|
||||
import { IJob } from 'turtle/job';
|
||||
|
||||
let SUPPORTED_SDK_VERSIONS: number[];
|
||||
|
||||
export async function ensureCanBuildSdkVersion(job: IJob) {
|
||||
if (config.builder.useLocalWorkingDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SUPPORTED_SDK_VERSIONS) {
|
||||
SUPPORTED_SDK_VERSIONS = await findSupportedSdkVersions(job.platform);
|
||||
}
|
||||
|
||||
const targetMajorSdkVersion = ExponentTools.parseSdkMajorVersion(job.sdkVersion || job.manifest.sdkVersion);
|
||||
if (!SUPPORTED_SDK_VERSIONS.includes(targetMajorSdkVersion)) {
|
||||
throw new Error(`Unsupported SDK Version!`);
|
||||
}
|
||||
}
|
||||
|
||||
async function findSupportedSdkVersions(platform: 'android' | 'ios'): Promise<number[]> {
|
||||
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));
|
||||
return sdks.map((sdk) => parseInt(sdk.substr(SDK_DIR_PREFIX.length), 10));
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { IOS } from 'turtle/constants';
|
||||
|
||||
export interface IShellAppDirectoryConfig {
|
||||
sdkVersion: string;
|
||||
buildType?: IOS.BUILD_TYPES;
|
||||
}
|
||||
+4
-6
@@ -8,7 +8,7 @@ export default {
|
||||
env: env('NODE_ENV'),
|
||||
hostname: env('HOSTNAME', os.hostname()),
|
||||
deploymentEnv: env('ENVIRONMENT'),
|
||||
platform: env('PLATFORM', PLATFORMS.IOS),
|
||||
platform: env('PLATFORM', PLATFORMS.IOS) as PLATFORMS,
|
||||
aws: {
|
||||
accessKeyId: env('AWS_ACCESS_KEY_ID'),
|
||||
secretAccessKey: env('AWS_SECRET_ACCESS_KEY'),
|
||||
@@ -47,11 +47,9 @@ export default {
|
||||
client: {
|
||||
level: env('CLIENT_LOGGER_LEVEL', 'info'),
|
||||
},
|
||||
loggly: {
|
||||
token: env('LOGGLY_TOKEN', ''),
|
||||
subdomain: env('LOGGLY_SUBDOMAIN', ''),
|
||||
buffer: envNum('LOGGLY_BUFFER', 100),
|
||||
},
|
||||
},
|
||||
google: {
|
||||
credentials: env('GOOGLE_APPLICATION_CREDENTIALS', ''),
|
||||
},
|
||||
sentry: {
|
||||
dsn: env('SENTRY_DSN', ''),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as BUILD from './build';
|
||||
import * as IOS from './ios';
|
||||
|
||||
export const PLATFORMS = {
|
||||
IOS: 'ios',
|
||||
ANDROID: 'android',
|
||||
};
|
||||
export enum PLATFORMS {
|
||||
IOS = 'ios',
|
||||
ANDROID = 'android',
|
||||
}
|
||||
|
||||
export { IOS, BUILD };
|
||||
@@ -1,4 +1,5 @@
|
||||
export const BUILD_TYPES = {
|
||||
ARCHIVE: 'archive',
|
||||
SIMULATOR: 'simulator',
|
||||
};
|
||||
export enum BUILD_TYPES {
|
||||
ARCHIVE = 'archive',
|
||||
CLIENT = 'client',
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
+6
-3
@@ -1,3 +1,5 @@
|
||||
import { IOS } from 'turtle/constants';
|
||||
|
||||
export interface IJob {
|
||||
platform: 'android' | 'ios';
|
||||
experienceName: string;
|
||||
@@ -8,7 +10,7 @@ export interface IJob {
|
||||
releaseChannel: string;
|
||||
publicUrl?: string;
|
||||
// ios
|
||||
buildType?: string;
|
||||
buildType?: IOS.BUILD_TYPES;
|
||||
bundleIdentifier?: string;
|
||||
};
|
||||
credentials: {
|
||||
@@ -16,13 +18,14 @@ export interface IJob {
|
||||
keystore?: string;
|
||||
keystoreAlias?: string;
|
||||
keystorePassword?: string;
|
||||
// ios
|
||||
keyPassword?: string;
|
||||
// ios
|
||||
certP12?: string;
|
||||
certPassword?: string;
|
||||
provisioningProfile?: string;
|
||||
password?: string;
|
||||
teamId?: string;
|
||||
appleSession?: string;
|
||||
udids?: string[];
|
||||
};
|
||||
projectDir: string;
|
||||
fakeUploadDir?: string;
|
||||
|
||||
+12
-3
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
||||
|
||||
import * as sqs from 'turtle/aws/sqs';
|
||||
import builders from 'turtle/builders';
|
||||
import BuildError from 'turtle/builders/BuildError';
|
||||
import { logErrorOnce } from 'turtle/builders/utils/common';
|
||||
import config from 'turtle/config';
|
||||
import { BUILD } from 'turtle/constants/index';
|
||||
@@ -46,7 +47,7 @@ export async function getJob() {
|
||||
return job;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error at receiving messages', err);
|
||||
logger.error({ err }, 'Error at receiving messages');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +140,15 @@ async function build(job: any) {
|
||||
} catch (err) {
|
||||
logErrorOnce(err);
|
||||
const buildDuration = calculateBuildDuration();
|
||||
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, { turtleVersion, buildDuration });
|
||||
let reason;
|
||||
if (err instanceof BuildError) {
|
||||
reason = err.reason;
|
||||
}
|
||||
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, {
|
||||
turtleVersion,
|
||||
buildDuration,
|
||||
...reason && { reason },
|
||||
});
|
||||
return false;
|
||||
} finally {
|
||||
await logger.cleanup(job);
|
||||
@@ -151,6 +160,6 @@ async function deleteMessage(priority: string, receiptHandle: string) {
|
||||
setCurrentJobId(null);
|
||||
await sqs.deleteMessage(priority, receiptHandle);
|
||||
} catch (err) {
|
||||
logger.error('Error at deleting msg', err);
|
||||
logger.error({ err }, 'Error at deleting msg');
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,13 @@ export default baseJobSchema.concat(
|
||||
}),
|
||||
credentials: Joi.object()
|
||||
.keys({
|
||||
certP12: Joi.string(),
|
||||
certPassword: Joi.string(),
|
||||
teamId: Joi.string(),
|
||||
certP12: Joi.string().required(),
|
||||
certPassword: Joi.string().required(),
|
||||
teamId: Joi.string().required(),
|
||||
// it's not required for adhoc Expo Client builds
|
||||
provisioningProfile: Joi.string(),
|
||||
password: Joi.string(),
|
||||
appleSession: Joi.string(),
|
||||
udids: Joi.array().items(Joi.string()),
|
||||
})
|
||||
.when('config.buildType', {
|
||||
is: IOS.BUILD_TYPES.SIMULATOR,
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import BunyanLoggly from 'bunyan-loggly';
|
||||
|
||||
import config from 'turtle/config';
|
||||
import * as constants from 'turtle/constants/logger';
|
||||
import { IJob } from 'turtle/job';
|
||||
|
||||
export interface ILogglyConfig {
|
||||
token: string;
|
||||
subdomain: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface ILogglyExtraFields {
|
||||
jobID: string;
|
||||
experienceName: string;
|
||||
}
|
||||
|
||||
export default class HackyLogglyStream extends EventEmitter {
|
||||
private logglyStream: BunyanLoggly;
|
||||
private extraFields: ILogglyExtraFields | null;
|
||||
|
||||
constructor(logglyConfig: ILogglyConfig) {
|
||||
super();
|
||||
this.logglyStream = new BunyanLoggly(logglyConfig, config.logger.loggly.buffer);
|
||||
this.extraFields = null;
|
||||
}
|
||||
public init(job: IJob) {
|
||||
this.extraFields = {
|
||||
jobID: job.id,
|
||||
experienceName: job.experienceName,
|
||||
};
|
||||
}
|
||||
public cleanup() {
|
||||
this.extraFields = null;
|
||||
}
|
||||
public write(rec: any) {
|
||||
const { msg: message, hostname: host, level, ...other } = rec;
|
||||
this.logglyStream.write({
|
||||
message,
|
||||
level: constants.LEVELS_MAPPING[level],
|
||||
host,
|
||||
...other,
|
||||
data: this.extraFields || {},
|
||||
});
|
||||
}
|
||||
}
|
||||
+16
-23
@@ -6,10 +6,14 @@ import { Client as RavenClient } from 'raven';
|
||||
import config from 'turtle/config';
|
||||
import * as constants from 'turtle/constants/logger';
|
||||
import { IJob } from 'turtle/job';
|
||||
import HackyLogglyStream from 'turtle/logger/hackyLogglyStream';
|
||||
import S3Stream from 'turtle/logger/s3Stream';
|
||||
import { isOffline } from 'turtle/turtleContext';
|
||||
|
||||
// type error when using import
|
||||
// https://github.com/googleapis/nodejs-logging-bunyan/issues/241
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const { LoggingBunyan } = require('@google-cloud/logging-bunyan');
|
||||
|
||||
interface IStream {
|
||||
stream: any;
|
||||
type?: string;
|
||||
@@ -18,7 +22,6 @@ interface IStream {
|
||||
}
|
||||
|
||||
export const s3logger = new S3Stream();
|
||||
let logglyStream: HackyLogglyStream;
|
||||
|
||||
const streams: IStream[] = [];
|
||||
|
||||
@@ -47,45 +50,35 @@ if (config.sentry.dsn && config.deploymentEnv !== 'development') {
|
||||
});
|
||||
}
|
||||
|
||||
if (config.logger.loggly.token) {
|
||||
const logglyConfig = {
|
||||
token: config.logger.loggly.token,
|
||||
subdomain: config.logger.loggly.subdomain,
|
||||
// prettier-ignore
|
||||
tags: [
|
||||
'app-shell-apps',
|
||||
`platform.${config.platform}`,
|
||||
`environment.${config.deploymentEnv}`,
|
||||
],
|
||||
if (config.google.credentials) {
|
||||
const resource = {
|
||||
type: 'generic_node',
|
||||
labels: {
|
||||
node_id: config.hostname,
|
||||
location: '', // default value
|
||||
namespace: '', // default value
|
||||
},
|
||||
};
|
||||
logglyStream = new HackyLogglyStream(logglyConfig);
|
||||
streams.push({
|
||||
type: 'raw',
|
||||
stream: logglyStream,
|
||||
});
|
||||
streams.push(new LoggingBunyan({ name: 'turtle', resource }).stream('info'));
|
||||
}
|
||||
|
||||
const logger = bunyan.createLogger({
|
||||
name: 'turtle',
|
||||
level: config.logger.level,
|
||||
platform: config.platform,
|
||||
environment: config.deploymentEnv,
|
||||
streams,
|
||||
});
|
||||
|
||||
logger.withFields = (extraFields: any) => withFields(logger, extraFields);
|
||||
|
||||
logger.init = async (job: IJob) => {
|
||||
if (logglyStream) {
|
||||
logglyStream.init(job);
|
||||
}
|
||||
return await s3logger.init(job);
|
||||
};
|
||||
|
||||
logger.cleanup = async () => {
|
||||
// a little hacky, but works
|
||||
logger.info({ lastBuildLog: true }, 'this is the last log from this build');
|
||||
if (logglyStream) {
|
||||
logglyStream.cleanup();
|
||||
}
|
||||
await s3logger.waitForLogger();
|
||||
};
|
||||
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
export = BunyanLoggly;
|
||||
|
||||
declare class BunyanLoggly {
|
||||
constructor(options: BunyanLoggly.IOptions, bufferLength?: number, bufferTimeout?: number, logglyCallback?: any);
|
||||
public write(data: any): void;
|
||||
}
|
||||
|
||||
declare namespace BunyanLoggly {
|
||||
interface IOptions {
|
||||
token: string;
|
||||
subdomain: string;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -9,7 +9,7 @@ import { getRedisClient, RedisClient } from 'turtle/utils/redis';
|
||||
|
||||
const REDIS_TURTLE_VERSION_KEY = 'turtle:version';
|
||||
|
||||
process.on('unhandledRejection', (err) => logger.error('Unhandled promise rejection:', err));
|
||||
process.on('unhandledRejection', (err) => logger.error({ err }, 'Unhandled promise rejection'));
|
||||
|
||||
function handleExit() {
|
||||
if (checkShouldExit()) {
|
||||
@@ -39,19 +39,19 @@ async function main() {
|
||||
await redis.set(REDIS_TURTLE_VERSION_KEY, turtleVersion);
|
||||
logger.info(`Register Turtle version ${turtleVersion} in Redis`);
|
||||
} catch (err) {
|
||||
logger.error('Failed to register Turtle version in Redis', err);
|
||||
logger.error({ err }, 'Failed to register Turtle version in Redis');
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
await doJob();
|
||||
} catch (err) {
|
||||
logger.error('Failed to do a job', err);
|
||||
logger.error({ err }, 'Failed to do a job');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => logger.error('This should never happen...'))
|
||||
.catch((err) => logger.error('Something went terribly wrong :(', err))
|
||||
.catch((err) => logger.error({ err }, 'Something went terribly wrong'))
|
||||
.then(() => process.exit(1));
|
||||
@@ -41,6 +41,10 @@ export function createDefaultConfigurationKey(platform = config.platform) {
|
||||
}
|
||||
|
||||
export async function getPriorities() {
|
||||
if (config.env === 'test') {
|
||||
return NORMAL_CONFIGURATION;
|
||||
}
|
||||
|
||||
try {
|
||||
const redis = await getRedisClient(RedisClient.Configuration);
|
||||
if (redis.getConfig === undefined) {
|
||||
@@ -56,24 +60,23 @@ export async function getPriorities() {
|
||||
logger.debug(`Using configuration at index ${configurationsIndex} pulled from redis`);
|
||||
const { error, value: configValue } = configSchema.validate(configurations[configurationsIndex]);
|
||||
if (error) {
|
||||
logger.warn(error);
|
||||
logger.warn({ err: error });
|
||||
throw new Error('Received configuration is not valid');
|
||||
}
|
||||
return configValue;
|
||||
} catch (err) {
|
||||
logger.warn(err);
|
||||
logger.warn({ err });
|
||||
const defaultConfig = JSON.parse(await redis.get(createDefaultConfigurationKey()));
|
||||
const { error, value: defaultConfigValue } = configSchema.validate(defaultConfig);
|
||||
if (error) {
|
||||
logger.warn(error);
|
||||
logger.warn({ err: error });
|
||||
throw new Error('Received default configuration is not valid');
|
||||
}
|
||||
logger.warn('Using default configuration pulled from redis');
|
||||
return defaultConfigValue;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(err);
|
||||
logger.warn('Using configuration chosen locally');
|
||||
logger.warn({ err }, 'Using configuration chosen locally');
|
||||
if (Math.random() < 0.7) {
|
||||
return HIGH_CONFIGURATION;
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ export async function getRedisClient(type = RedisClient.Default) {
|
||||
try {
|
||||
redisClients[type] = await connect(MILLIS_CONNECTION_TIMEOUT, type);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
logger.error({ err });
|
||||
}
|
||||
}
|
||||
return redisClients[type];
|
||||
@@ -66,7 +66,7 @@ export async function checkIfCancelled(jobId: string) {
|
||||
if (config.deploymentEnv === 'development') {
|
||||
logger.warn('Did you turn on redis server? Run `yarn start-docker` in server/www');
|
||||
}
|
||||
logger.error(err);
|
||||
logger.error({ err });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" awscli --command aws "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" awscli --command aws_bash_completer "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" awscli --command aws_completer "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" git-crypt --command git-crypt "$@"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#! /usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
scriptDir=$(dirname "$0")
|
||||
nixDir="$scriptDir/../../nix"
|
||||
|
||||
nix build -f $nixDir --no-link "$@"
|
||||
|
||||
for package in "$@"; do
|
||||
for b in $(ls $(nix path-info -f $nixDir $package)/bin); do
|
||||
cat << EOF > "$scriptDir/$b"
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by $(basename "$0")
|
||||
exec nix run -f "\$(dirname "\$0")/../../nix" $package --command $b "\$@"
|
||||
EOF
|
||||
chmod +x "$scriptDir/$b"
|
||||
done
|
||||
done
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" nodejs-8_x --command node "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" nodejs-8_x --command npm "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" nodejs-8_x --command npx "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" yarn --command yarn "$@"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
# This file was generated by nix-binstubs
|
||||
exec nix run -f "$(dirname "$0")/../../nix" yarn --command yarnpkg "$@"
|
||||
+4
-1
@@ -5,7 +5,10 @@
|
||||
"rules": {
|
||||
"quotemark": [true, "single", "jsx-double"],
|
||||
"object-literal-sort-keys": false,
|
||||
"object-curly-spacing": true,
|
||||
"no-namespace": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
"rulesDirectory": [
|
||||
"node_modules/tslint-eslint-rules/dist/rules"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user