Compare commits

..
Author SHA1 Message Date
Dominik Sokal 4ebf6c1d2c use release-it to handle releases 2019-01-18 11:38:20 +01:00
132 changed files with 5825 additions and 8822 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
{
"presets": [
["env", {
"targets": {
"node": "8.9"
}
}]
],
"plugins": [
["module-resolver", {
"alias": {
"turtle": "./build"
}
}]
]
}
+12 -201
View File
@@ -1,214 +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 >>"
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
- run: ./scripts/testUnitCI.sh
workflows:
checks:
version: 2
build_and_test:
jobs:
- build
tests:
jobs:
- unit_tests
- ios_smoke_tests
- android_smoke_tests
- test
+2 -7
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
source_local() {
file=./.envrc.local
@@ -15,9 +15,4 @@ export NODE_ENV=development
export EXPO_TURTLE_TOOLS_DIR="$(pwd)/tools"
if [ -z "$EXPO_UNIVERSE_DIR" ]; then
PATH_add $EXPO_TURTLE_TOOLS_DIR/bin
if [ "$CI" = "true" ]; then
PATH_add $EXPO_TURTLE_TOOLS_DIR/ci/bin
fi
fi
PATH_add $EXPO_TURTLE_TOOLS_DIR/bin
+1 -4
View File
@@ -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
-1
View File
@@ -1 +0,0 @@
fixtures/
-1
View File
@@ -1 +0,0 @@
v8.15.1
+1
View File
@@ -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 -138
View File
@@ -5,154 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### 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.
### 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
- 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
- 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
-33
View File
@@ -1,33 +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.
## Setting up the repository for development
*TODO*
## 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!
+26 -28
View File
@@ -20,7 +20,6 @@ RUN dpkg --add-architecture i386 && \
libc6:i386\
libncurses5:i386\
libstdc++6:i386\
rsync\
unzip\
yarn\
zlib1g:i386\
@@ -37,34 +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}
RUN yes | sdkmanager --licenses > /dev/null
RUN yes | sdkmanager \
# Install Android SDK components
RUN sdkmanager \
"platform-tools" \
"platforms;android-23" \
"platforms;android-24" \
"platforms;android-25" \
"platforms;android-26" \
"platforms;android-27" \
"platforms;android-28" | grep -v '=' || echo 'no output'
RUN yes | sdkmanager "platform-tools" | grep -v '=' || echo 'no output'
RUN yes | sdkmanager "build-tools;28.0.3" | grep -v '=' || echo 'no output'
RUN yes | sdkmanager \
"build-tools;25.0.0" \
"extras;android;m2repository" \
"extras;google;m2repository" \
"extras;google;google_play_services" | grep -v '=' || echo 'no output'
"extras;google;m2repository"
# Install Android NDK
ENV ANDROID_NDK_VERSION android-ndk-r10e
@@ -100,27 +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/
WORKDIR /app/turtle
ENV TURTLE_WORKING_DIR_PATH /app/workingdir/
CMD ["node", "./build/server.js"]
+5 -23
View File
@@ -1,43 +1,25 @@
<h3 align=center>
<img src="logo/horizontal.png" width=50%>
</h3>
# Turtle CLI
[![CircleCI](https://img.shields.io/circleci/project/github/expo/turtle/master.svg)](https://circleci.com/gh/expo/turtle)
[![NPM](https://img.shields.io/npm/v/turtle-cli/latest.svg)](https://npmjs.com/package/turtle-cli)
[![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/expo/turtle/blob/master/LICENSE)
[![Expo Developers Slack](https://img.shields.io/badge/Expo%20Developers-e01563.svg?logo=slack)](http://slack.expo.io)
[![Expo Forums](https://img.shields.io/badge/Expo%20Forums-blue.svg)](https://forums.expo.io/)
<hr />
[![NPM](https://img.shields.io/npm/v/turtle-cli/latest.svg)](https://rubygems.org/gems/fastlane)
[![CircleCI](https://img.shields.io/circleci/project/github/expo/turtle/master.svg)](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 to Turtle CLI](CONTRIBUTING.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
### Glossary
## 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.
-9
View File
@@ -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
View File
@@ -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'));
+1 -1
View File
@@ -9,6 +9,6 @@
"^.+\\.ts$": "ts-jest"
},
"testRegex": "(test|spec)\\.ts$",
"moduleFileExtensions": ["ts", "js", "json"],
"moduleFileExtensions": ["ts", "js"],
"verbose": true
}
+1 -1
View File
@@ -9,6 +9,6 @@
"^.+\\.ts$": "ts-jest"
},
"testMatch": [ "**/__smoke__/*.test.ts" ],
"moduleFileExtensions": ["ts", "js", "json"],
"moduleFileExtensions": ["ts", "js"],
"verbose": true
}
+1 -1
View File
@@ -9,6 +9,6 @@
"^.+\\.ts$": "ts-jest"
},
"testMatch": [ "**/__tests__/*.ts" ],
"moduleFileExtensions": ["ts", "js", "json"],
"moduleFileExtensions": ["ts", "js"],
"verbose": true
}
+8 -11
View File
@@ -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

-228
View File
@@ -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

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

-5
View File
@@ -1,5 +0,0 @@
self: super:
{
nodejs = super.nodejs-8_x;
}
-50
View File
@@ -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
''}
'';
}
-19
View File
@@ -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) ];
})
-14
View File
@@ -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"
-7
View File
@@ -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
}
-6
View File
@@ -1,6 +0,0 @@
{
"watch": ["src"],
"ext": "ts",
"ignore": ["src/**/*.test.ts"],
"exec": "ts-node -r tsconfig-paths/register --files src/server.ts"
}
+32 -48
View File
@@ -1,18 +1,19 @@
{
"name": "turtle-cli",
"version": "0.8.1",
"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",
"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",
@@ -25,33 +26,17 @@
"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",
"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:default": "expenv ts-node -r tsconfig-paths/register --files src/scripts/setConfigurations.ts",
"set-priorities:local": "EXPENV_OVERRIDES=local-common,local,local-queue yarn set-priorities:default",
"set-priorities:staging": "yarn set-priorities:default",
"set-priorities:production": "EXPENV_OVERRIDES=prod yarn set-priorities:default",
"set-replica-count:default": "expenv ts-node -r tsconfig-paths/register --files src/scripts/setReplicaCount.ts | bunyan -o short -l debug",
"set-replica-count:local": "EXPENV_OVERRIDES=local-common,local,local-queue yarn set-replica-count:default",
"set-replica-count:staging": "yarn set-replica-count:default",
"set-replica-count:production": "EXPENV_OVERRIDES=prod yarn set-replica-count:default"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.ts": [
"yarn lint:fix",
"git add"
]
"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"
@@ -83,61 +68,60 @@
"@types/fs-extra": "^5.0.4",
"@types/jest": "^23.3.9",
"@types/joi": "^13.4.3",
"@types/lodash": "^4.14.129",
"@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": "54.1.4",
"@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.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.4"
"which": "^1.3.1",
"xdl": "52.0.14-alpha.6+371be041"
}
}
+32
View File
@@ -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
+21 -1
View File
@@ -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
+2 -9
View File
@@ -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"
+6 -9
View File
@@ -1,12 +1,9 @@
#!/bin/bash
SDKMANAGER=$ANDROID_HOME/tools/bin/sdkmanager
yes | $SDKMANAGER --licenses > /dev/null
yes | $SDKMANAGER "platforms;android-28"
yes | $SDKMANAGER "platform-tools"
yes | $SDKMANAGER "build-tools;28.0.3"
yes | $SDKMANAGER \
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" \
"extras;google;google_play_services"
"extras;google;m2repository"
-38
View File
@@ -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
+21 -1
View File
@@ -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
-11
View File
@@ -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 "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
-7
View File
@@ -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
@@ -1,13 +1,12 @@
import { env } from 'turtle/utils/env';
import { connect, RedisClient } from 'turtle/utils/redis';
import { PLATFORMS } from 'turtle/constants';
import logger from 'turtle/logger';
import {
createConfigurationsKey,
createDefaultConfigurationKey,
HIGH_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
NORMAL_CONFIGURATION,
} from 'turtle/utils/priorities';
import { connect, RedisClient } from 'turtle/utils/redis';
const TIMEOUT = 3000;
@@ -26,40 +25,36 @@ const TIMEOUT = 3000;
// 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,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
// there are 5 iOS builders at the moment
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,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
HIGH_ONLY_CONFIGURATION,
NORMAL_CONFIGURATION,
NORMAL_CONFIGURATION,
HIGH_CONFIGURATION,
HIGH_CONFIGURATION,
NORMAL_CONFIGURATION,
NORMAL_CONFIGURATION,
HIGH_CONFIGURATION,
HIGH_CONFIGURATION,
// there are 15 Android builders at the moment
HIGH_CONFIGURATION,
HIGH_CONFIGURATION,
HIGH_CONFIGURATION,
NORMAL_CONFIGURATION,
NORMAL_CONFIGURATION,
];
const DEFAULT_CONFIGURATION = HIGH_CONFIGURATION;
async function run() {
@@ -73,8 +68,6 @@ async function run() {
redis.disconnect();
}
if (require.main === module) {
run()
.then(() => logger.info('All done'))
.catch((err) => logger.error('Error:', err));
}
run()
.then(() => console.log('All done'))
.catch(err => console.error('Error:', err));
+3 -15
View File
@@ -36,7 +36,7 @@ setup_local() {
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")
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"
@@ -48,25 +48,13 @@ setup_remote() {
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() {
@@ -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
+1
View File
@@ -0,0 +1 @@
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-c19ce0d88a153f481fd790e23bdcd6f2d5ea7fd6.tar.gz
s3://exp-artifacts/android-shell-builder-67bd02667e29221707f8c1dd11e739eada25ef55.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-89b2d24c86737d5a489c6a18467e31b2f9ce9005.tar.gz
s3://exp-artifacts/android-shell-builder-5c339ff7bab6f2161d378cc70e1bf04d32ad98dd.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-f796aaffa0b5715079b2950faf26c2ff4e53e4c0.tar.gz
s3://exp-artifacts/android-shell-builder-cb03fdc12dc74f699a6d3f22219cbcd30e01ac46.tar.gz
-1
View File
@@ -1 +0,0 @@
s3://exp-artifacts/android-shell-builder-e244e865ed8cb8e20d29ea00b6373845d1c61068.tar.gz
-1
View File
@@ -1 +0,0 @@
s3://exp-artifacts/ios-expo-client-a2b1e16f330b36fa0dc627a76caabe4d2ff45f77.tar.gz
+1
View File
@@ -0,0 +1 @@
s3://exp-artifacts/ios-shell-builder-sdk-26-db356a09877642d64023af98e4e2e1e632fbe4df.tar.gz
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
-25
View File
@@ -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 },
],
];
+1 -1
View File
@@ -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);
}
});
});
+1 -1
View File
@@ -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
View File
@@ -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);
}
}
+3 -16
View File
@@ -15,18 +15,11 @@ 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',
)
.description(
'Build a standalone APK or App Bundle for your project, signed and ready for submission to the Google Play'
+ ' Store.',
'Build a standalone APK for your project, signed and ready for submission to the Google Play Store.',
)
.asyncAction(
createBuilderAction({
@@ -41,17 +34,11 @@ export default (program: any, setCommonCommandOptions: any) => {
);
};
const buildJobObject = (
appJSON: any,
{ releaseChannel, username, projectDir, publicUrl, buildType }: any,
credentials: any,
) => ({
const buildJobObject = (appJSON: any, { releaseChannel, username, projectDir, publicUrl }: any, credentials: any) => ({
config: {
..._.get(appJSON, 'expo.android.config', {}),
releaseChannel,
androidPackage: _.get(appJSON, 'expo.android.package'),
publicUrl,
buildType,
},
id: uuid.v4(),
platform: PLATFORMS.ANDROID,
+1 -2
View File
@@ -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 {
+2 -2
View File
@@ -5,7 +5,7 @@ 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]');
@@ -56,7 +56,7 @@ const buildJobObject = (appJSON: any, { releaseChannel, buildType, username, pub
});
const prepareCredentials = async (cmd: any) => {
if (cmd.type !== IOS_BUILD_TYPES.ARCHIVE) {
if (cmd.type !== IOS.BUILD_TYPES.ARCHIVE) {
return null;
}
+3 -2
View File
@@ -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
View File
@@ -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);
});
}
+11 -19
View File
@@ -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';
@@ -41,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);
@@ -67,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) {
+1 -1
View File
@@ -12,7 +12,7 @@ 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');
+4 -4
View File
@@ -1,19 +1,19 @@
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 { 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');
@@ -73,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
View File
@@ -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;
}
+6 -7
View File
@@ -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,14 +27,14 @@ export async function ensureShellAppIsPresent(
formatters: IShellAppFormaters,
postDownloadAction?: PostDownloadAction,
) {
const workingdir = formatters.formatShellAppDirectory({ sdkVersion });
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);
}
}
+4 -13
View File
@@ -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) {
+3 -2
View File
@@ -60,7 +60,7 @@ export function createBuilderAction({
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);
@@ -72,7 +72,8 @@ export function createBuilderAction({
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) {
+4 -9
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import { User as UserManager } from '@expo/xdl';
import { User as UserManager } from 'xdl';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
-17
View File
@@ -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 };
+10 -27
View File
@@ -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,33 +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, manifest && 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),
sdkVersion,
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,
});
} catch (err) {
commonUtils.logErrorOnce(err);
+5 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
+1 -1
View File
@@ -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');
+9 -19
View File
@@ -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');
+2 -3
View File
@@ -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')
+4 -4
View File
@@ -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;
}
-99
View File
@@ -1,99 +0,0 @@
import path from 'path';
import spawnAsync from '@expo/spawn-async';
import { IosCodeSigning } from '@expo/xdl';
import fs from 'fs-extra';
import isEmpty from 'lodash/isEmpty';
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;
+7 -7
View File
@@ -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;
}
}
+16 -18
View File
@@ -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),
releaseChannel,
sdkVersion,
});
}
logger.info({ buildPhase: 'configuring NSBundle' }, 'configuring NSBundle...');
return await IosShellApp.configureAndCopyArchiveAsync(shellAppParams);
+7 -13
View File
@@ -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}`);
}
-169
View File
@@ -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;
}
+2 -2
View File
@@ -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,
-30
View File
@@ -1,30 +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';
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));
}
-6
View File
@@ -1,6 +0,0 @@
import { ANDROID_BUILD_TYPES, IOS_BUILD_TYPES } from 'turtle/constants';
export interface IShellAppDirectoryConfig {
sdkVersion: string;
buildType?: IOS_BUILD_TYPES & ANDROID_BUILD_TYPES;
}
+12 -13
View File
@@ -2,13 +2,13 @@ import os from 'os';
import path from 'path';
import { PLATFORMS } from 'turtle/constants';
import { env, envBoolean, envNum, envOptional, envTransform } from 'turtle/utils/env';
import { env, envNum, envOptional, envTransform } from 'turtle/utils/env';
export default {
env: env('NODE_ENV'),
hostname: env('HOSTNAME', os.hostname()),
deploymentEnv: env('ENVIRONMENT'),
platform: env('PLATFORM', PLATFORMS.IOS) as PLATFORMS,
platform: env('PLATFORM', PLATFORMS.IOS),
aws: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_SECRET_ACCESS_KEY'),
@@ -37,22 +37,21 @@ export default {
intervalMs: envNum('AWS_CLOUDWATCH_INTERVAL_MS', 30000),
namespace: env('AWS_CLOUDWATCH_NAMESPACE', 'Turtle'),
},
datadog: {
disabled: envBoolean('DATADOG_DISABLED', false),
apiKey: envOptional('DATADOG_API_KEY'),
appKey: envOptional('DATADOG_APP_KEY'),
intervalMs: env('DATADOG_INTERVAL_MS', 5 * 60 * 1000),
},
redis: {
url: env('REDIS_URL'),
configUrl: env('REDIS_CONFIG_URL'),
},
logger: {
level: env('LOGGER_LEVEL', 'info'),
intervalMs: envNum('LOGGER_INTERVAL_MS', 5000),
},
google: {
credentials: env('GOOGLE_APPLICATION_CREDENTIALS', ''),
interval: envNum('LOGGER_INTERVAL', 5),
client: {
level: env('CLIENT_LOGGER_LEVEL', 'info'),
},
loggly: {
token: env('LOGGLY_TOKEN', ''),
subdomain: env('LOGGLY_SUBDOMAIN', ''),
buffer: envNum('LOGGLY_BUFFER', 100),
},
},
sentry: {
dsn: env('SENTRY_DSN', ''),
@@ -70,7 +69,7 @@ export default {
(val: string) => env('NODE_ENV') === 'development' && val === '1',
),
fakeUpload: envTransform('TURTLE_FAKE_UPLOAD', '0', (val: string) => val === '1'),
maxJobTimeMs: envNum('TURTLE_MAX_JOB_TIME_MS', 15 * 60 * 1000),
maxJobTimeMs: envNum('TURTLE_MAX_JOB_TIME_MS', 60 * 60 * 1000),
useLocalWorkingDir: envTransform('TURTLE_USE_LOCAL_WORKING_DIR', '0', (val: string) => val === '1'),
},
directories: {
+6 -16
View File
@@ -1,19 +1,9 @@
import * as BUILD from './build';
import * as IOS from './ios';
export enum PLATFORMS {
IOS = 'ios',
ANDROID = 'android',
}
export const PLATFORMS = {
IOS: 'ios',
ANDROID: 'android',
};
export enum IOS_BUILD_TYPES {
ARCHIVE = 'archive',
CLIENT = 'client',
SIMULATOR = 'simulator',
}
export enum ANDROID_BUILD_TYPES {
APK = 'apk',
APP_BUNDLE = 'app-bundle',
}
export { BUILD };
export { IOS, BUILD };
+4
View File
@@ -0,0 +1,4 @@
export const BUILD_TYPES = {
ARCHIVE: 'archive',
SIMULATOR: 'simulator',
};
+3 -8
View File
@@ -1,5 +1,3 @@
import { ANDROID_BUILD_TYPES, IOS_BUILD_TYPES } from 'turtle/constants';
export interface IJob {
platform: 'android' | 'ios';
experienceName: string;
@@ -9,25 +7,22 @@ export interface IJob {
config: {
releaseChannel: string;
publicUrl?: string;
buildType: IOS_BUILD_TYPES & ANDROID_BUILD_TYPES;
// ios
buildType?: string;
bundleIdentifier?: string;
// android
androidPackage?: string;
};
credentials: {
// android
keystore?: string;
keystoreAlias?: string;
keystorePassword?: string;
keyPassword?: string;
// ios
keyPassword?: string;
certP12?: string;
certPassword?: string;
provisioningProfile?: string;
password?: string;
teamId?: string;
appleSession?: string;
udids?: string[];
};
projectDir: string;
fakeUploadDir?: string;
+19 -33
View File
@@ -1,19 +1,23 @@
import path from 'path';
import fs from 'fs-extra';
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';
import logger from 'turtle/logger';
import * as buildDurationMetric from 'turtle/metrics/buildDuration';
import * as buildStatusMetric from 'turtle/metrics/buildStatus';
import { checkShouldExit, setCurrentJobId, turtleVersion } from 'turtle/turtleContext';
import { checkShouldExit, setCurrentJobId } from 'turtle/turtleContext';
import { getPriorities } from 'turtle/utils/priorities';
import * as redis from 'turtle/utils/redis';
import { sanitizeJob } from 'turtle/validator';
const { version: turtleVersion } = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
function _maybeExit() {
if (checkShouldExit()) {
logger.warn('Exiting due to previously received termination signal.');
@@ -47,7 +51,7 @@ export async function getJob() {
return job;
}
} catch (err) {
logger.error({ err }, 'Error at receiving messages');
logger.error('Error at receiving messages', err);
}
}
}
@@ -71,21 +75,20 @@ async function processJob(jobData: any) {
logger.info('The job has been cancelled');
} else {
redis.registerListener(job.id, () => deleteMessage(priority, receiptHandle));
const startTimestamp = Date.now();
const startTimestamp = +new Date();
let buildFailed = false;
const buildType = _.get(job, 'config.buildType', 'default');
try {
const status = await build(job);
buildStatusMetric.add(buildType, priority, status);
buildFailed = !status;
const success = await build(job);
buildStatusMetric.add(buildType, success);
buildFailed = !success;
} catch (err) {
buildStatusMetric.add(buildType, priority, false);
buildStatusMetric.add(buildType, false);
buildFailed = true;
throw err;
} finally {
const endTimestamp = Date.now();
const endTimestamp = +new Date();
const turtleBuildDurationSecs = Math.ceil((endTimestamp - startTimestamp) / 1000);
logger.info(`BuildID=${job.id} Build duration=${turtleBuildDurationSecs}`);
buildDurationMetric.addTurtleDuration(buildType, turtleBuildDurationSecs, !buildFailed);
if (job.messageCreatedTimestamp) {
const totalBuildDurationSecs = Math.ceil(
@@ -113,17 +116,14 @@ function failAfterMaxJobTime(priority: string, receiptHandle: string, job: any)
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, { turtleVersion });
await deleteMessage(priority, receiptHandle);
} finally {
logger.error('Build timed out. Going to terminate turtle agent.');
await logger.cleanup();
logger.error('Going to terminate turtle agent, just in case...');
process.exit(1);
}
}, config.builder.maxJobTimeMs);
}
async function build(job: any) {
const startTimestamp = Date.now();
const s3Url = await logger.initForJob(job);
const calculateBuildDuration = () => Math.ceil((Date.now() - startTimestamp) / 1000);
const s3Url = await logger.init(job);
try {
await sqs.sendMessage(job.id, BUILD.JOB_STATES.IN_PROGRESS, {
@@ -131,28 +131,14 @@ async function build(job: any) {
logFormat: 'json',
});
const result = await (builders as any)[job.platform](job);
const buildDuration = calculateBuildDuration();
sqs.sendMessage(job.id, BUILD.JOB_STATES.FINISHED, {
...result,
turtleVersion,
buildDuration,
});
sqs.sendMessage(job.id, BUILD.JOB_STATES.FINISHED, { ...result, turtleVersion });
return true;
} catch (err) {
logErrorOnce(err);
const buildDuration = calculateBuildDuration();
let reason;
if (err instanceof BuildError) {
reason = err.reason;
}
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, {
turtleVersion,
buildDuration,
...reason && { reason },
});
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, { turtleVersion });
return false;
} finally {
await logger.cleanup();
await logger.cleanup(job);
}
}
@@ -161,6 +147,6 @@ async function deleteMessage(priority: string, receiptHandle: string) {
setCurrentJobId(null);
await sqs.deleteMessage(priority, receiptHandle);
} catch (err) {
logger.error({ err }, 'Error at deleting msg');
logger.error('Error at deleting msg', err);
}
}
+7 -9
View File
@@ -1,27 +1,25 @@
import Joi from 'joi';
import { IOS_BUILD_TYPES, PLATFORMS } from 'turtle/constants/index';
import { IOS, PLATFORMS } from 'turtle/constants/index';
import baseJobSchema from 'turtle/jobsSchemas/base';
export default baseJobSchema.concat(
Joi.object().keys({
platform: Joi.string().valid(PLATFORMS.IOS),
config: Joi.object().keys({
buildType: Joi.string().default(IOS_BUILD_TYPES.ARCHIVE),
buildType: Joi.string().default(IOS.BUILD_TYPES.ARCHIVE),
bundleIdentifier: Joi.string().regex(/^[a-zA-Z][a-zA-Z0-9\-.]+$/),
}),
credentials: Joi.object()
.keys({
certP12: Joi.string().required(),
certPassword: Joi.string().required(),
teamId: Joi.string().required(),
// it's not required for adhoc Expo Client builds
certP12: Joi.string(),
certPassword: Joi.string(),
teamId: Joi.string(),
provisioningProfile: Joi.string(),
appleSession: Joi.string(),
udids: Joi.array().items(Joi.string()),
password: Joi.string(),
})
.when('config.buildType', {
is: IOS_BUILD_TYPES.SIMULATOR,
is: IOS.BUILD_TYPES.SIMULATOR,
then: Joi.optional(),
otherwise: Joi.required(),
}),
+48
View File
@@ -0,0 +1,48 @@
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 || {},
});
}
}
+88 -64
View File
@@ -1,75 +1,99 @@
import bunyan from 'bunyan';
import bunyanDebugStream from 'bunyan-debug-stream';
import { SentryStream } from 'bunyan-sentry-stream';
import { Client as RavenClient } from 'raven';
import config from 'turtle/config';
import * as constants from 'turtle/constants/logger';
import { IJob } from 'turtle/job';
import streams from 'turtle/logger/streams';
import HackyLogglyStream from 'turtle/logger/hackyLogglyStream';
import S3Stream from 'turtle/logger/s3Stream';
import { isOffline } from 'turtle/turtleContext';
interface ILoggerFields {
[key: string]: string | number;
interface IStream {
stream: any;
type?: string;
level?: string;
reemitErrorEvents?: boolean;
}
class Logger {
private parentLogger: any;
private currentLogger: any;
export const s3logger = new S3Stream();
let logglyStream: HackyLogglyStream;
constructor(parentLogger?: any) {
this.parentLogger = parentLogger || bunyan.createLogger({
name: 'turtle',
level: config.logger.level,
platform: config.platform,
...config.deploymentEnv && { environment: config.deploymentEnv },
streams: Object.values(streams),
});
this.currentLogger = this.parentLogger;
}
const streams: IStream[] = [];
public async initForJob(job: IJob) {
this.currentLogger = this.parentLogger.child({
jobID: job.id,
experienceName: job.experienceName,
});
const s3Url = await streams.s3.stream.init(job);
return s3Url;
}
public async cleanup() {
this.currentLogger = this.parentLogger;
await streams.s3.stream.cleanup();
}
public trace(...all: any[]) {
this.currentLogger.trace(...all);
}
public debug(...all: any[]) {
this.currentLogger.debug(...all);
}
public info(...all: any[]) {
this.currentLogger.info(...all);
}
public warn(...all: any[]) {
this.currentLogger.warn(...all);
}
public error(...all: any[]) {
this.currentLogger.error(...all);
}
public fatal(...all: any[]) {
this.currentLogger.fatal(...all);
}
public child(fields: ILoggerFields) {
const newLogger = this.currentLogger.child(fields);
return new Logger(newLogger);
}
// only for backward compatibility
public withFields(fields: ILoggerFields) {
return this.child(fields);
}
if (isOffline()) {
const prettyStdOut = new bunyanDebugStream({ forceColor: true });
streams.push({ stream: prettyStdOut, type: 'raw', level: config.logger.client.level });
} else {
streams.push(
{ stream: process.stdout },
{
type: 'raw',
stream: s3logger,
reemitErrorEvents: true,
level: config.logger.client.level,
},
);
}
export default new Logger();
if (config.sentry.dsn && config.deploymentEnv !== 'development') {
const raven = new RavenClient();
raven.config(config.sentry.dsn, { environment: config.deploymentEnv });
streams.push({
level: 'error',
type: 'raw', // Mandatory type for SentryStream
stream: new SentryStream(raven),
});
}
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}`,
],
};
logglyStream = new HackyLogglyStream(logglyConfig);
streams.push({
type: 'raw',
stream: logglyStream,
});
}
const logger = bunyan.createLogger({
name: 'turtle',
level: config.logger.level,
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();
};
export default logger;
export function withFields(loggerObj: any, extraFields: any) {
return constants.LEVELS.reduce((obj, level) => {
obj[level] = (...args: any[]) => loggerObj[level](extraFields, ...args);
return obj;
}, {} as any);
}
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff. Show more