Compare commits

..
84 changed files with 978 additions and 1620 deletions

No files matched your search

+8 -4
View File
@@ -1,21 +1,25 @@
version: 2
machine:
node:
# Circle will use node v4.0.0
version: 4.0.0
jobs:
build:
docker:
- image: circleci/node:8.12
- image: circleci/node:10.15.0
steps:
- checkout
- run: yarn
- run: yarn lint
- run: yarn build
test:
docker:
- image: circleci/node:8.12
- image: circleci/node:10.15.0
steps:
- checkout
- run: yarn
- run: ./scripts/testUnitCI.sh
- run: ./test-ci-unit.sh
workflows:
version: 2
+21
View File
@@ -0,0 +1,21 @@
{
"plugins": [
"import"
],
"extends": ["universe", "plugin:import/errors"],
"rules":{
"import/order": [
"error",
{
"groups": ["builtin", "external", "parent", "sibling", "index"],
"newlines-between": "always"
}
],
"import/no-unresolved": "off"
},
"settings": {
"import/resolver": {
"babel-module": {}
}
}
}
-3
View File
@@ -6,6 +6,3 @@
shell.apk
node_modules
.secrets*
*.swp
*.swo
-9
View File
@@ -1,9 +0,0 @@
{
"git": {
"commit": false,
"tag": true,
"push": true,
"requireUpstream": false,
"tagName": "v${version}"
}
}
-12
View File
@@ -1,12 +0,0 @@
{
"npm": {
"tag": "latest"
},
"git": {
"requireUpstream": false,
"tagName": "v${version}"
},
"github": {
"release": true
}
}
-25
View File
@@ -1,25 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
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]
## [0.5.1] - 2019-01-16
### Changed
- 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 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
- Update iOS shell app for SDK32.
- Update Android shell app for SDK32.
-7
View File
@@ -1,7 +0,0 @@
# Community Code of Conduct
The Expo team is committed to fostering an open and welcoming environment.
**Our Community Guidelines can be found here:**
https://expo.io/guidelines
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1 -13
View File
@@ -1,11 +1,6 @@
# Turtle CLI
[![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/expo/turtle/blob/master/LICENSE)
[![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.
Turtle CLI is a command line interface for building Expo standalone apps. You can use it both on your CI and your private computer.
### Glossary
@@ -16,10 +11,3 @@ You can use it both on your CI and your private computer.
## 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.
## 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.
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2017",
"allowSyntheticDefaultImports": false,
"baseUrl": "./",
"paths": {
"turtle/*": ["src/*"],
}
},
"exclude": ["node_modules", "dist", "build"]
}
+14 -14
View File
@@ -1,22 +1,23 @@
{
"name": "turtle-cli",
"version": "0.5.1",
"version": "0.4.3",
"description": "The command-line tool for building standalone Expo apps",
"preferGlobal": true,
"scripts": {
"start": "EXPENV_OVERRIDES=local-common,local,local-queue expenv gulp",
"start": "EXPENV_OVERRIDES=local-common,local 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",
"watch": "EXPENV_OVERRIDES=local-common,local expenv gulp watch:source",
"bin:turtle": "./bin/turtle.js",
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"lint": "eslint src",
"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",
"init-workingdir:local": "./scripts/initLocalWorkingdir.sh",
"init-workingdir:remote": "yarn init-workingdir:remote-android && yarn init-workingdir:remote-ios",
"init-workingdir:remote-android": "./scripts/android/fetchRemoteAndroidTarball.sh",
"init-workingdir:remote-ios": "./scripts/ios/fetchRemoteIosTarball.sh",
"docker:pull": "retry5 gcloud docker -- pull gcr.io/exponentjs/turtle-android:latest",
"docker:pull:tag": "retry5 gcloud docker -- pull gcr.io/exponentjs/turtle-android:${TAG}",
"docker:build": "yarn build:production android && gcloud docker -- build -t gcr.io/exponentjs/turtle-android:latest -t gcr.io/exponentjs/turtle-android:${TAG} .",
@@ -28,13 +29,12 @@
"test:smoke": "yarn test --config jest/smoke-config.json --forceExit",
"test:smoke:android": "yarn test:smoke --testNamePattern=android",
"test:smoke:ios": "PLATFORM=ios yarn test:smoke --testNamePattern=iOS",
"release": "release-it",
"release:beta": "yarn release -c .release-it.beta.json --preRelease=beta",
"secrets:init-private": "./scripts/initPrivateSecrets.sh",
"ssh:android:pods-names": "./scripts/ssh/findAndroidPods.sh",
"ssh:android:connect": "./scripts/ssh/connectToAndroidPod.sh",
"ssh:ios:ips": "./scripts/ssh/findIosIps.sh",
"set-priorities": "yarn set-priorities:local",
"set-priorities:local": "EXPENV_OVERRIDES=local-common,local,local-queue expenv babel-node scripts/setConfigurations.js",
"set-priorities:local": "EXPENV_OVERRIDES=local-common,local 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"
},
@@ -60,9 +60,6 @@
"url": "https://github.com/expo/turtle/issues"
},
"homepage": "https://github.com/expo/turtle#readme",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"devDependencies": {
"@types/decompress": "^4.2.3",
"@types/fs-extra": "^5.0.4",
@@ -78,9 +75,13 @@
"@types/which": "^1.3.1",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-jest": "^23.6.0",
"babel-plugin-module-resolver": "^3.1.1",
"babel-preset-env": "^1.6.1",
"eslint": "^4.19.1",
"eslint-config-universe": "^1.0.7",
"eslint-import-resolver-babel-module": "^4.0.0",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-babel": "^7.0.1",
"gulp-changed": "^3.2.0",
@@ -91,7 +92,6 @@
"jest": "^23.6.0",
"nock": "^10.0.1",
"nodemon": "^1.18.7",
"release-it": "^10.0.0",
"rimraf": "^2.6.2",
"source-map-loader": "^0.2.3",
"ts-jest": "^23.10.4",
+1 -7
View File
@@ -12,13 +12,7 @@ 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
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
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 -2
View File
@@ -7,11 +7,11 @@ if [ -z "$1" ]; then
exit 1
fi
export PLATFORM=$1
PLATFORM=$1
yarn
yarn run build
yarn set-workingdir:remote
yarn init-workingdir:remote-$PLATFORM
if [[ -z "${TARBALL}" ]]; then
echo "TARBALL is not set, building tarball skipped"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
ensure_link_exists() {
if [ ! -L "$2" ]; then
ln -s $1 $2
echo "$2 link created"
fi
}
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
WORKINGDIR="$ROOT_DIR/workingdir/local"
if [ ! -d "$WORKINGDIR" ]; then
mkdir -p $WORKINGDIR
echo "$WORKINGDIR directory created"
fi
LINKS=( "template-files" "package.json" "android" "expokit-npm-package" "packages" "client-builds" "cpp" "exponent-view-template" "ios" "shellAppBase-builds" "shellAppWorkspaces")
for LINKNAME in ${LINKS[@]}; do
ensure_link_exists "$EXPO_UNIVERSE_DIR/exponent/$LINKNAME" "$WORKINGDIR/$LINKNAME"
done
+5 -7
View File
@@ -1,10 +1,5 @@
#!/usr/bin/env bash
if [ -z "$EXPO_UNIVERSE_DIR" ]; then
echo "You need to specify EXPO_UNIVERSE_DIR to use this script. Alternatively you can create .secrets.local-queue file yourself with AWS_SQS_ANDROID_QUEUE_URL, AWS_SQS_IOS_QUEUE_URL and AWS_SQS_OUT_QUEUE_URL variables."
exit 1
fi
if [ -z "$1" ]; then
echo "Please specify a nickname you used to create AWS SQS queues"
exit 1
@@ -17,9 +12,12 @@ ANDROID=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.a
IOS=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_ios_in | head -1 | cut -d"=" -f2 | xargs`
OUT=`cd $TERRAFORM_DIR; terraform state show module.turtle_queues_local_$1.aws_sqs_queue.jobs_out | head -1 | cut -d"=" -f2 | xargs`
cat << EOF > $TURTLE_DIR/.secrets.local-queue
cat << EOF > $TURTLE_DIR/.secrets.local
AWS_SQS_ANDROID_QUEUE_URL="$ANDROID"
AWS_SQS_IOS_QUEUE_URL="$IOS"
AWS_SQS_OUT_QUEUE_URL="$OUT"
TURTLE_FAKE_UPLOAD="1"
TURTLE_FAKE_UPLOAD_DIR="/Users/$USER/Downloads"
TURTLE_WORKING_DIR_PATH="$EXPO_UNIVERSE_DIR/server/turtle/workingdir"
TURTLE_USE_LOCAL_WORKING_DIR="1"
EOF
+1 -7
View File
@@ -12,13 +12,7 @@ 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
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
tar zxf $ARTIFACTS_DIR/$SDK_VERSION.tar.gz -C $WORKING_DIR/$SDK_VERSION
rm -rf $TARBALL_URI $ARTIFACTS_DIR/$SDK_VERSION.tar.gz
done
-86
View File
@@ -1,86 +0,0 @@
#!/usr/bin/env bash
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
DOWNLOAD_DIR="${EXPO_DOWNLOAD_DIR:-$HOME/Downloads}"
mkdir -p $DOWNLOAD_DIR
if [ ! -f "$ROOT_DIR/.secrets.local-queue" ]; then
echo "You need to run \"yarn secrets:init-private [SQS_NAME]\" or create .secrets.local-queue file yourself with AWS_SQS_ANDROID_QUEUE_URL, AWS_SQS_IOS_QUEUE_URL and AWS_SQS_OUT_QUEUE_URL variables."
exit 1
fi
setup_local() {
ensure_link_exists() {
if [ ! -L "$2" ]; then
ln -s $1 $2
echo "$2 link created"
fi
}
WORKINGDIR="$ROOT_DIR/workingdir/local"
if [[ -d $WORKINGDIR ]]; then
echo "Local workingdir already prepared"
return
fi
if [[ -z "$EXPO_UNIVERSE_DIR" ]]; then
echo "EXPO_UNIVERSE_DIR needs to be set"
exit -1
fi
cd $EXPO_UNIVERSE_DIR/exponent && yarn
cd $EXPO_UNIVERSE_DIR/exponent/tools-public && yarn && ./generate-dynamic-macros-android.sh
mkdir -p $WORKINGDIR
echo "$WORKINGDIR directory created"
LINKS=("template-files" "package.json" "android" "expokit-npm-package" "packages" "client-builds" "cpp" "exponent-view-template" "ios" "shellAppBase-builds" "shellAppWorkspaces")
for LINKNAME in ${LINKS[@]}; do
ensure_link_exists "$EXPO_UNIVERSE_DIR/exponent/$LINKNAME" "$WORKINGDIR/$LINKNAME"
done
}
setup_remote() {
if [[ -d $ROOT_DIR/workingdir/android && -d $ROOT_DIR/workingdir/ios ]]; then
echo "Shell apps already fetched"
else
echo "Fetching shell apps"
if [ "$PLATFORM" != 'ios']; then
$ROOT_DIR/scripts/android/fetchRemoteAndroidTarball.sh
fi
if [ "$PLATFORM" != 'android']; then
$ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
fi
fi
}
switch_local() {
cat << EOF > $ROOT_DIR/.secrets.local
TURTLE_FAKE_UPLOAD="1"
TURTLE_FAKE_UPLOAD_DIR="$HOME/Downloads"
TURTLE_WORKING_DIR_PATH="$ROOT_DIR/workingdir"
TURTLE_USE_LOCAL_WORKING_DIR="1"
EOF
}
switch_remote() {
cat << EOF > $ROOT_DIR/.secrets.local
TURTLE_FAKE_UPLOAD="1"
TURTLE_FAKE_UPLOAD_DIR="$HOME/Downloads"
TURTLE_WORKING_DIR_PATH="$ROOT_DIR/workingdir"
EOF
}
if [ "$1" == "local" ]; then
setup_local
switch_local
elif [ "$1" == "remote" ]; then
setup_remote
switch_remote
else
echo "You need to pass remote or local as argument"
exit -1
fi
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-cb03fdc12dc74f699a6d3f22219cbcd30e01ac46.tar.gz
s3://exp-artifacts/android-shell-builder-55e2cc952dc8bae3ebba8aaaab6eb1199ac7b0d0.tar.gz
+5 -4
View File
@@ -3,13 +3,14 @@ import fs from 'fs-extra';
import nock from 'nock';
import config from 'turtle/config';
import { PLATFORMS } from 'turtle/constants';
import * as jobManager from 'turtle/jobManager';
import logger from 'turtle/logger';
import { PLATFORMS } from 'turtle/constants';
jest.mock('../builders/utils/uploader.ts', () => ({
uploadBuildToS3: jest.fn(() => {
logger.debug('Faking uploading build');
console.log('Faking uploading build');
return 'legit_path';
}),
}));
@@ -67,11 +68,11 @@ function mockFinish() {
.post('/', (body: any) => {
const keys = Object.keys(body);
if (keys.includes('MessageBody')) {
const mb = JSON.parse(body.MessageBody);
const mb = JSON.parse(body['MessageBody']);
if (mb.status === 'finished') {
return true;
} else if (mb.status === 'errored') {
fail('Build ended with error');
fail('Build ended with error')
}
}
return false;
+23 -29
View File
@@ -1,22 +1,16 @@
import aws from 'aws-sdk';
import _ from 'lodash';
import { EXTRA_DIMENSIONS, GROUP_HASH } from 'turtle/aws/cloudwatch/constants';
import calculateStatistics from 'turtle/aws/cloudwatch/statistics';
import {
IMetric,
IMetricConfiguration,
IMetricData,
IMetricsChunk,
MetricRegistrationObject,
} from 'turtle/aws/cloudwatch/types';
import config from 'turtle/config';
import logger from 'turtle/logger';
import calculateStatistics from 'turtle/aws/cloudwatch/statistics';
import { EXTRA_DIMENSIONS, GROUP_HASH } from 'turtle/aws/cloudwatch/constants';
import { Metric, MetricData, MetricConfiguration, MetricRegistrationObject, MetricsChunk } from 'turtle/aws/cloudwatch/types';
interface IState {
metrics: IMetric[];
interface State {
metrics: Array<Metric>;
registeredMetrics: {
[key: string]: IMetricConfiguration;
[key: string]: MetricConfiguration;
};
intervalId: NodeJS.Timer | null;
}
@@ -25,7 +19,7 @@ const SUCCESS_SUFFIX = '.success';
const FAIL_SUFFIX = '.fail';
const CHUNK_SIZE = 20;
const state: IState = {
const state: State = {
metrics: [],
registeredMetrics: {},
intervalId: null,
@@ -52,7 +46,7 @@ export function addMetricData({
success = true,
explicitName = false,
extraDimensions = [],
}: IMetricData) {
}: MetricData) {
if (config.cloudwatch.disabled) {
return;
}
@@ -63,7 +57,7 @@ export function addMetricData({
return;
}
const { unit } = registeredMetric;
const metric: IMetric = {
const metric: Metric = {
MetricName: metricName,
Unit: unit,
Value: value,
@@ -120,7 +114,7 @@ function _dumpMetrics() {
logger.trace(
'[cloudwatch] lenghts for state.metrics=%d, state.registeredMetrics=%d',
state.metrics.length,
Object.keys(state.registeredMetrics).length,
Object.keys(state.registeredMetrics).length
);
_addEmptyMetrics(state.metrics);
@@ -130,7 +124,7 @@ function _dumpMetrics() {
state.metrics = [];
}
function _addEmptyMetrics(metrics: IMetric[]) {
function _addEmptyMetrics(metrics: Array<Metric>) {
const seenMetrics = metrics.reduce((acc, i) => {
if (!acc.has(i.MetricName)) {
acc.add(i.MetricName);
@@ -140,44 +134,44 @@ function _addEmptyMetrics(metrics: IMetric[]) {
const nonEmptyMetrics = _.omitBy(state.registeredMetrics, ({ addEmpty }) => !addEmpty);
const toAdd = _.difference(Object.keys(nonEmptyMetrics), Array.from(seenMetrics));
toAdd.forEach((name) => addMetricData({ name, value: 0, explicitName: true }));
toAdd.forEach(name => addMetricData({ name, value: 0, explicitName: true }));
}
function _formatMetrics(metrics: IMetric[]) {
function _formatMetrics(metrics: Array<Metric>) {
const reducedMetrics = _reduceMetrics(metrics);
const metricsWithDimensions = reducedMetrics.map((metric) => {
const metricsWithDimensions = reducedMetrics.map(metric => {
const { dimensions } = state.registeredMetrics[metric.MetricName];
const joinedDimensions = [...dimensions, ...metric[EXTRA_DIMENSIONS]];
return joinedDimensions.map((i) => Object.assign({}, metric, { Dimensions: i }));
return joinedDimensions.map(i => Object.assign({}, metric, { Dimensions: i }));
});
const metricData = _.flatten(metricsWithDimensions);
const chunks = _.chunk(metricData, CHUNK_SIZE);
return chunks.map((chunk) => ({
return chunks.map(chunk => ({
Namespace: config.cloudwatch.namespace,
MetricData: chunk,
}));
}
function _reduceMetrics(metrics: IMetric[]) {
function _reduceMetrics(metrics: Array<Metric>) {
const grouped = _.groupBy(metrics, GROUP_HASH);
return _.reduce(
grouped,
(acc: IMetric[], val: any) => {
const { MetricName: key } = val[0] as IMetric;
(acc: Array<Metric>, val: any) => {
const { MetricName: key } = val[0] as Metric;
const { reducer, statistics } = state.registeredMetrics[key];
const reduced = reducer ? reducer(val) : val;
const withStats = statistics ? calculateStatistics(reduced) : reduced;
acc.push(...withStats);
return acc;
},
[],
[]
);
}
function _pushMetrics(metrics: IMetricsChunk[]) {
function _pushMetrics(metrics: Array<MetricsChunk>) {
logger.trace('[cloudwatch] pushing metrics', JSON.stringify(metrics, null, '\t'));
metrics.forEach((metricsChunk) => {
cloudWatch.putMetricData(metricsChunk, (err) => {
metrics.forEach(metricsChunk => {
cloudWatch.putMetricData(metricsChunk, err => {
if (err) {
logger.warn('[cloudwatch]', err);
}
+5 -5
View File
@@ -1,10 +1,10 @@
import { IMetric } from 'turtle/aws/cloudwatch/types';
import { Metric } from 'turtle/aws/cloudwatch/types';
export function sum(metrics: IMetric[]) {
export function sum(metrics: Array<Metric>) {
if (metrics.length === 0) {
return [];
}
const reduced = metrics.reduce((acc: IMetric | null, i: IMetric) => {
const reduced = metrics.reduce((acc: Metric | null, i: Metric) => {
let result = acc;
if (!result) {
result = {...i};
@@ -16,12 +16,12 @@ export function sum(metrics: IMetric[]) {
return [reduced];
}
export function avg(metrics: IMetric[]) {
export function avg(metrics: Array<Metric>) {
if (!metrics.length) {
return [];
}
const metric = sum(metrics)[0] as IMetric;
const metric = sum(metrics)[0] as Metric;
metric.Value = metric.Value / metrics.length;
return [metric];
}
+3 -3
View File
@@ -1,11 +1,11 @@
import { IMetric, MetricWithStats } from 'turtle/aws/cloudwatch/types';
import { Metric, MetricWithStats } from 'turtle/aws/cloudwatch/types';
export default function calculateStatistics(metrics: IMetric[]) {
export default function calculateStatistics(metrics: Array<Metric>) {
if (!metrics.length) {
return [];
}
const reduced = metrics.reduce((acc: MetricWithStats | null, i: IMetric) => {
const reduced = metrics.reduce((acc: MetricWithStats | null, i: Metric) => {
let result = acc;
if (!result) {
result = Object.assign({}, i, {
+12 -12
View File
@@ -1,6 +1,6 @@
import { EXTRA_DIMENSIONS, GROUP_HASH } from 'turtle/aws/cloudwatch/constants';
export interface IMetric {
export interface Metric {
MetricName: string;
Unit: string;
Value: number;
@@ -11,8 +11,8 @@ export interface IMetric {
SampleCount: number;
Sum: number;
};
[EXTRA_DIMENSIONS]: any;
[GROUP_HASH]: string;
[EXTRA_DIMENSIONS]: any,
[GROUP_HASH]: string,
}
export type MetricWithStats = {
@@ -21,10 +21,10 @@ export type MetricWithStats = {
Minimum: number;
SampleCount: number;
Sum: number;
},
} & IMetric;
}
} & Metric;
export interface IMetricData {
export interface MetricData {
name: string;
value: number;
success?: boolean;
@@ -32,17 +32,17 @@ export interface IMetricData {
extraDimensions?: any;
}
export interface IMetricConfiguration {
export interface MetricConfiguration {
unit: string;
statistics?: boolean;
reducer?: any;
addEmpty?: boolean;
dimensions?: any;
}
};
export type MetricRegistrationObject = { metricName: string, explicitName?: boolean } & IMetricConfiguration;
export type MetricRegistrationObject = { metricName: string, explicitName?: boolean } & MetricConfiguration;
export interface IMetricsChunk {
export interface MetricsChunk {
Namespace: string;
MetricData: IMetric[];
}
MetricData: Array<Metric>;
};
+3 -3
View File
@@ -1,10 +1,10 @@
import AWS from 'aws-sdk';
import fs from 'fs-extra';
import _ from 'lodash';
import fs from 'fs-extra';
import config from 'turtle/config';
interface IFileUploadParams {
interface FileUploadParams {
bucketName?: string;
key: string;
srcPath: string;
@@ -17,7 +17,7 @@ const s3 = new AWS.S3({
export default s3;
export async function uploadFile({ bucketName = config.s3.bucket, key, srcPath }: IFileUploadParams) {
export async function uploadFile({ bucketName = config.s3.bucket, key, srcPath }: FileUploadParams) {
return await s3
.upload({
Bucket: bucketName,
+4 -4
View File
@@ -2,11 +2,11 @@ import retry from 'async-retry';
import AWS from 'aws-sdk';
import _ from 'lodash';
import { OUTPUT_QUEUE_URL, QUEUE_URL } from 'turtle/aws/utils';
import config from 'turtle/config';
import { JOB_UPDATE_TYPE } from 'turtle/constants/build';
import logger from 'turtle/logger';
import { getCurrentJobId } from 'turtle/turtleContext';
import { QUEUE_URL, OUTPUT_QUEUE_URL } from 'turtle/aws/utils';
const sqs = new AWS.SQS({
apiVersion: '2012-11-05',
@@ -26,7 +26,7 @@ export async function receiveMessage(priority: string) {
return sqs
.receiveMessage(params)
.promise()
.then((data) => {
.then(data => {
if (data.Messages) {
logger.info(`Received a message`);
return data.Messages[0];
@@ -35,7 +35,7 @@ export async function receiveMessage(priority: string) {
return null;
}
})
.catch((err) => {
.catch(err => {
logger.error('Receive error', err);
throw err;
});
@@ -67,7 +67,7 @@ export async function changeMessageVisibility(priority: string, receiptHandle: s
export function changeMessageVisibilityRecurring(priority: string, receiptHandle: string, jobId: string) {
return setInterval(() => {
if (getCurrentJobId() === jobId) {
changeMessageVisibility(priority, receiptHandle).catch((err) => {
changeMessageVisibility(priority, receiptHandle).catch(err => {
logger.warn('Error at change msg visibility', err);
});
}
-2
View File
@@ -1,2 +0,0 @@
export class ErrorWithCommandHelp extends Error {
}
-2
View File
@@ -1,2 +0,0 @@
export class ErrorWithProgramHelp extends Error {
}
+7 -8
View File
@@ -1,11 +1,11 @@
import fs from 'fs-extra';
import _ from 'lodash';
import fs from 'fs-extra';
import uuid from 'uuid';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { createBuilderAction } from 'turtle/bin/utils/builder';
import builder from 'turtle/builders/android';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/errors';
import { PLATFORMS } from 'turtle/constants';
import { createBuilderAction } from 'turtle/bin/utils/builder';
export default (program: any, setCommonCommandOptions: any) => {
const command = program.command('build:android [project-dir]');
@@ -14,12 +14,11 @@ export default (program: any, setCommonCommandOptions: any) => {
.alias('ba')
.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)',
'path to your Keystore (please provide Keystore password and Key password as EXPO_ANDROID_KEYSTORE_PASSWORD and EXPO_ANDROID_KEY_PASSWORD env variables)'
)
.option('--keystore-alias <alias>', 'keystore Alias')
.description(
'Build a standalone APK 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({
@@ -30,7 +29,7 @@ export default (program: any, setCommonCommandOptions: any) => {
builder,
platform: PLATFORMS.ANDROID,
os: ['darwin', 'linux'],
}),
})
);
};
@@ -58,7 +57,7 @@ const prepareCredentials = async (cmd: any) => {
if (!credentialsExist) {
if (someCredentialsExist) {
throw new ErrorWithCommandHelp(
'Please provide all required credentials - Keystore (with password), Keystore alias and Key password',
'Please provide all required credentials - Keystore (with password), Keystore alias and Key password'
);
} else {
return null;
+4 -4
View File
@@ -1,5 +1,5 @@
import { default as buildAndroid } from 'turtle/bin/commands/build/android';
import { default as buildIOS } from 'turtle/bin/commands/build/ios';
import { default as buildAndroid } from 'turtle/bin/commands/build/android';
function createCommand(builder: any) {
return (program: any) => {
@@ -15,15 +15,15 @@ function setCommonCommandOptions(cmd: any) {
.option('-o --output <output-file-path>', 'output file path')
.option(
'--public-url <url>',
'the URL of an externally hosted manifest (for self-hosted apps).',
'the URL of an externally hosted manifest (for self-hosted apps).'
)
.option(
'--release-channel <channel-name>',
'pull from specified release channel (default: default)',
'pull from specified release channel (default: default)'
);
}
export default {
ios: createCommand(buildIOS),
android: createCommand(buildAndroid),
};
}
+8 -9
View File
@@ -1,11 +1,11 @@
import fs from 'fs-extra';
import _ from 'lodash';
import uuid from 'uuid';
import fs from 'fs-extra';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { createBuilderAction } from 'turtle/bin/utils/builder';
import builder from 'turtle/builders/ios';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/errors';
import { IOS, PLATFORMS } from 'turtle/constants';
import { createBuilderAction } from 'turtle/bin/utils/builder';
export default (program: any, setCommonCommandOptions: any) => {
const command = program.command('build:ios [project-dir]');
@@ -16,16 +16,16 @@ export default (program: any, setCommonCommandOptions: any) => {
'-t --type <build>',
'type of build: archive|simulator',
/^(archive|simulator)$/i,
'archive',
'archive'
)
.option('--team-id <apple-teamId>', 'Apple Team ID')
.option(
'--dist-p12-path <dist.p12>',
'path to your Distribution Certificate P12 (please provide password as EXPO_IOS_DIST_P12_PASSWORD env variable)',
'path to your Distribution Certificate P12 (please provide password as EXPO_IOS_DIST_P12_PASSWORD env variable)'
)
.option('--provisioning-profile-path <.mobileprovision>', 'path to your Provisioning Profile')
.description(
'Build a standalone IPA for your project, signed and ready for submission to the Apple App Store.',
'Build a standalone IPA for your project, signed and ready for submission to the Apple App Store.'
)
.asyncAction(
createBuilderAction({
@@ -36,7 +36,7 @@ export default (program: any, setCommonCommandOptions: any) => {
builder,
platform: PLATFORMS.IOS,
os: 'darwin',
}),
})
);
};
@@ -68,8 +68,7 @@ const prepareCredentials = async (cmd: any) => {
if (!credentialsExist) {
throw new ErrorWithCommandHelp(
'Please provide all required credentials'
+ '- Apple Team ID, Distribution Certificate P12 (with password) and Provisioning Profile',
'Please provide all required credentials - Apple Team ID, Distribution Certificate P12 (with password) and Provisioning Profile'
);
}
+2
View File
@@ -0,0 +1,2 @@
export class ErrorWithProgramHelp extends Error {}
export class ErrorWithCommandHelp extends Error {}
+5 -6
View File
@@ -1,10 +1,9 @@
import _ from 'lodash';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
import setup from 'turtle/bin/setup/setup';
import { PLATFORMS } from 'turtle/constants';
import logger from 'turtle/logger';
import setup from 'turtle/bin/setup/setup';
import { ErrorWithCommandHelp, ErrorWithProgramHelp } from 'turtle/bin/commands/errors';
import { PLATFORMS } from 'turtle/constants';
const l = logger.withFields({ buildPhase: 'setting up environment' });
@@ -17,7 +16,7 @@ function createSetupCommand(platform: string, os?: string) {
.description(`Setup environment for building ${_.capitalize(platform)} standalone apps.`)
.option(
'--sdk-version <sdk-version>',
'version of Expo SDK shell app to download (optional)',
'version of Expo SDK shell app to download (optional)'
)
.asyncAction(async (cmd: any) => await setupAction(program, cmd, platform, os));
};
@@ -31,7 +30,7 @@ async function setupAction(program: any, cmd: any, platform: string, os?: string
const { sdkVersion } = cmd;
await setup(platform, sdkVersion);
l.info('it\'s all set!');
l.info('it\'s all set!')
} catch (err) {
logger.error(`Failed to setup environment for ${platform} builds`);
logger.error(err.stack);
+3 -3
View File
@@ -1,5 +1,5 @@
import os from 'os';
import path from 'path';
import os from 'os';
import _ from 'lodash';
@@ -16,8 +16,8 @@ const resolveEnv = () => {
};
const currentEnv = resolveEnv();
const isLocal = (envName: string) => envName === 'local';
const isStaging = (envName: string) => envName === 'staging';
const isLocal = (env: string) => env === 'local';
const isStaging = (env: string) => env === 'staging';
const apiConfig = {
protocol: 'https',
+14 -18
View File
@@ -1,13 +1,11 @@
import path from 'path';
import program, { Command } from 'commander';
import fs from 'fs-extra';
import { LoggerDetach, ModuleVersion } from 'xdl';
import program, { Command } from 'commander';
import _ from 'lodash';
import * as commands from 'turtle/bin/commands';
import logger from 'turtle/logger';
import * as commands from 'turtle/bin/commands';
const { name, version } = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
const { name, version } = require('../../package.json');
const ModuleVersionChecker = ModuleVersion.createModuleVersionChecker(name, version);
@@ -17,15 +15,13 @@ Command.prototype.asyncAction = function asyncAction(asyncFn: (...asyncFnArgs: a
return this.action(async (...args) => {
try {
await checkForUpdateAsync();
} catch (e) {
logger.warn('Failed to check for turtle-cli update.');
}
} catch (e) {}
return await asyncFn(...args);
});
};
}
export function run(programName: string) {
runAsync(programName).catch((e) => {
runAsync(programName).catch(e => {
logger.error('Uncaught Error', e);
process.exit(1);
});
@@ -33,22 +29,22 @@ export function run(programName: string) {
async function runAsync(programName: string) {
program.version(version);
Object.values(commands).forEach((command) => registerCommand(program, command));
Object.values(commands).forEach(command => registerCommand(program, command));
program.parse(process.argv);
const subCommand = process.argv[2];
if (subCommand) {
const commandNames = program.commands.reduce((acc: string[], command: any) => {
acc.push(command._name);
const alias = command._alias;
const commands = program.commands.reduce((acc: Array<string>, command: any) => {
acc.push(command['_name']);
const alias = command['_alias'];
if (alias) {
acc.push(alias);
}
return acc;
}, []);
if (!commandNames.includes(subCommand)) {
if (!_.includes(commands, subCommand)) {
logger.error(
`"${subCommand}" is not an ${programName} command. See "${programName} --help" for the full list of commands.`,
`"${subCommand}" is not an ${programName} command. See "${programName} --help" for the full list of commands.`
);
}
} else {
@@ -64,7 +60,7 @@ async function checkForUpdateAsync() {
logger.warn(
`There is a new version of ${name} available (${latest}).
You are currently using ${name} ${current}
Run \`npm install -g ${name}\` to get the latest version`,
Run \`npm install -g ${name}\` to get the latest version`
);
}
}
+13 -13
View File
@@ -2,20 +2,20 @@ import path from 'path';
import util from 'util';
import fs from 'fs-extra';
import _which from 'which';
import { ExponentTools } from 'xdl';
import _which from 'which';
import ensureAndroidNDKIsPresent from 'turtle/bin/setup/android/ndk';
import ensureAndroidSDKIsPresent from 'turtle/bin/setup/android/sdk';
import { checkSystem, ensureShellAppIsPresent } from 'turtle/bin/setup/utils/common';
import { IToolDefinition } from 'turtle/bin/setup/utils/toolsDetector';
import { formatShellAppDirectory } from 'turtle/builders/utils/android/workingdir';
import config from 'turtle/config';
import { PLATFORMS } from 'turtle/constants';
import logger from 'turtle/logger';
import config from 'turtle/config';
import { IToolDefinition } from 'turtle/bin/setup/utils/toolsDetector';
import { checkSystem, ensureShellAppIsPresent } from 'turtle/bin/setup/utils/common';
import { formatShellAppDirectory } from 'turtle/builders/utils/android/workingdir';
import ensureAndroidSDKIsPresent from 'turtle/bin/setup/android/sdk';
import ensureAndroidNDKIsPresent from 'turtle/bin/setup/android/ndk';
import { PLATFORMS } from 'turtle/constants';
const which = util.promisify(_which);
const REQUIRED_TOOLS: IToolDefinition[] = [
const REQUIRED_TOOLS: Array<IToolDefinition> = [
{
command: 'bash',
missingDescription: 'Please install bash',
@@ -31,13 +31,13 @@ const REQUIRED_TOOLS: IToolDefinition[] = [
const { status, stdout } = await ExponentTools.spawnAsyncThrowError(
'javac',
['-version'],
{ stdio: 'pipe' },
{ stdio: 'pipe' }
);
if (stdout.match(/no java runtime present/i)) {
return false;
}
return status === 0;
},
}
},
];
const LOGGER_FIELDS = { buildPhase: 'setting up environment' };
@@ -50,7 +50,7 @@ export default async function setup(sdkVersion?: string) {
await ensureShellAppIsPresent(
sdkVersion,
{ formatShellAppDirectory, formatShellAppTarballUriPath },
_shellAppPostDownloadAction,
_shellAppPostDownloadAction
);
}
}
@@ -107,7 +107,7 @@ function _setEnvVars(envVars: object) {
.forEach(([key, value]) => process.env[key] = value);
}
function _alterPath(newPaths: string[]) {
function _alterPath(newPaths: Array<string>) {
const currentPaths = process.env.PATH ? process.env.PATH.split(':') : [];
const paths = [...newPaths, ...currentPaths];
process.env.PATH = paths.join(':');
+4 -4
View File
@@ -4,10 +4,10 @@ import path from 'path';
import decompress from 'decompress';
import fs from 'fs-extra';
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';
import download from 'turtle/bin/setup/utils/downloader';
import { formatArtifactDownloadPath } from 'turtle/bin/setup/utils/common';
const ANDROID_NDK_URL = `https://dl.google.com/android/repository/android-ndk-r10e-${os.platform()}-x86_64.zip`;
@@ -24,7 +24,7 @@ export default async function ensureAndroidNDKIsPresent() {
l.info('Downloading Android NDK');
await fs.ensureDir(config.directories.artifactsDir);
await download(ANDROID_NDK_URL, androidNdkDownloadPath);
l.info('Decompressing Android NDK');
l.info('Decompressing Android NDK')
await decompress(androidNdkDownloadPath, androidNdkDir, { strip: 1 });
l.info('Android NDK installed successfully');
} catch (err) {
@@ -47,5 +47,5 @@ function _createEnvVars(androidNdkDir: string) {
}
function _createPaths(androidNdkDir: string) {
return [androidNdkDir];
return [androidNdkDir]
}
+3 -3
View File
@@ -5,10 +5,10 @@ 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';
import download from 'turtle/bin/setup/utils/downloader';
import { formatArtifactDownloadPath } from 'turtle/bin/setup/utils/common';
const ANDROID_SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip';
@@ -26,7 +26,7 @@ export default async function ensureAndroidSDKIsPresent() {
await fs.ensureDir(config.directories.artifactsDir);
await download(ANDROID_SDK_URL, androidSdkDownloadPath);
await fs.ensureDir(androidSdkDir);
l.info('Decompressing Android SDK');
l.info('Decompressing Android SDK')
await decompress(androidSdkDownloadPath, androidSdkDir);
await fs.remove(androidSdkDownloadPath);
l.info('Configuring Android SDK, this may take a while');
+4 -4
View File
@@ -2,13 +2,13 @@ import path from 'path';
import { ExponentTools } from 'xdl';
import config from 'turtle/config';
import { checkSystem, ensureShellAppIsPresent } from 'turtle/bin/setup/utils/common';
import { IToolDefinition } from 'turtle/bin/setup/utils/toolsDetector';
import { formatShellAppDirectory } from 'turtle/builders/utils/ios/workingdir';
import config from 'turtle/config';
import { PLATFORMS } from 'turtle/constants';
const REQUIRED_TOOLS: IToolDefinition[] = [
const REQUIRED_TOOLS: Array<IToolDefinition> = [
{
command: 'bash',
missingDescription: 'Please install bash',
@@ -24,13 +24,13 @@ const REQUIRED_TOOLS: IToolDefinition[] = [
const { status, stdout } = await ExponentTools.spawnAsyncThrowError(
'xcodebuild',
['-version'],
{ stdio: 'pipe' },
{ stdio: 'pipe' }
);
if (stdout.match(/requires xcode/i)) {
return false;
}
return status === 0;
},
}
},
];
+1 -1
View File
@@ -1,7 +1,7 @@
import isSemver from 'is-semver';
import setupAndroid from 'turtle/bin/setup/android';
import setupIos from 'turtle/bin/setup/ios';
import setupAndroid from 'turtle/bin/setup/android';
import { PLATFORMS } from 'turtle/constants';
export default async function setup(platform: string, sdkVersion?: string) {
+8 -10
View File
@@ -1,13 +1,14 @@
import path from 'path';
import { ExponentTools } from '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 config from 'turtle/config';
import logger from 'turtle/logger';
import { IToolDefinition, ensureToolsAreInstalled } from 'turtle/bin/setup/utils/toolsDetector';
import download from 'turtle/bin/setup/utils/downloader';
const l = logger.withFields({ buildPhase: 'setting up environment' });
@@ -18,15 +19,11 @@ interface IShellAppFormaters {
type PostDownloadAction = (workingdir: string) => void;
export async function checkSystem(requiredTools: IToolDefinition[]) {
export async function checkSystem(requiredTools: Array<IToolDefinition>) {
await ensureToolsAreInstalled(requiredTools);
}
export async function ensureShellAppIsPresent(
sdkVersion: string,
formatters: IShellAppFormaters,
postDownloadAction?: PostDownloadAction,
) {
export async function ensureShellAppIsPresent(sdkVersion: string, formatters: IShellAppFormaters, postDownloadAction?: PostDownloadAction) {
const workingdir = formatters.formatShellAppDirectory(sdkVersion);
if (await fs.pathExists(workingdir)) {
return;
@@ -48,11 +45,12 @@ async function _downloadShellApp(sdkVersion: string, targetDirectory: string, fo
l.info('extracting shell app (this may take a while)...');
await tar.x({
file: tarballDownloadTargetPath,
C: targetDirectory,
C: targetDirectory
});
l.info('shell app extracted');
}
export function formatArtifactDownloadPath(uri: string) {
const { base } = path.parse(uri);
return path.join(config.directories.artifactsDir, base);
+3 -2
View File
@@ -1,9 +1,9 @@
import fs from 'fs';
import { URL } from 'url';
import fs from 'fs';
import ProgressBar from 'progress';
import request from 'request';
import requestProgress from 'request-progress';
import ProgressBar from 'progress';
const S3_PROTOCOL = 's3:';
const PROGRESS_BAR_CONFIG = {
@@ -38,6 +38,7 @@ export default async function download(url: string, destPath: string) {
});
}
function convertS3ToHttpsUrl(s3UrlRaw: string) {
const { host: s3Bucket, pathname: s3Path, protocol } = new URL(s3UrlRaw);
if (protocol !== S3_PROTOCOL) {
+1 -1
View File
@@ -12,7 +12,7 @@ export interface IToolDefinition {
testFn?: () => Promise<boolean>;
}
export async function ensureToolsAreInstalled(tools: IToolDefinition[]) {
export async function ensureToolsAreInstalled(tools: Array<IToolDefinition>) {
let isAnyToolMissing = false;
for (const { command, missingDescription, testFn } of tools) {
try {
+7 -11
View File
@@ -1,12 +1,11 @@
import _ from 'lodash';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
import setup from 'turtle/bin/setup/setup';
import * as ProjectUtils from 'turtle/bin/utils/project';
import * as UserUtils from 'turtle/bin/utils/user';
import logger from 'turtle/logger';
import { sanitizeJob } from 'turtle/validator';
import * as UserUtils from 'turtle/bin/utils/user';
import * as ProjectUtils from 'turtle/bin/utils/project';
import { ErrorWithCommandHelp, ErrorWithProgramHelp } from 'turtle/bin/commands/errors';
import setup from 'turtle/bin/setup/setup';
export function createBuilderAction({
program,
@@ -30,9 +29,7 @@ export function createBuilderAction({
}
if (cmd.buildDir && cmd.output) {
throw new ErrorWithCommandHelp(
'You can\'t provide both --build-dir and --output parameters, please choose one',
);
throw new ErrorWithCommandHelp('You can\'t provide both --build-dir and --output parameters, please choose one');
}
const userData = {
@@ -47,11 +44,10 @@ export function createBuilderAction({
throw new ErrorWithCommandHelp('You must provide both Expo username and password');
}
} else if (!cmd.publicUrl) {
throw new ErrorWithCommandHelp(
'You must provide your Expo username and password unless you specify --public-url to your project manifest.',
);
throw new ErrorWithCommandHelp('You must provide your Expo username and password unless you specify --public-url to your project manifest.');
}
const args = {
releaseChannel: cmd.releaseChannel || 'default',
buildType: cmd.type,
+1 -1
View File
@@ -1,5 +1,5 @@
import fs from 'fs-extra';
import path from 'path';
import fs from 'fs-extra';
export async function loadAppJSON(projectDirArg: string) {
const projectDir = resolveAbsoluteDir(projectDirArg);
+1 -1
View File
@@ -1,6 +1,6 @@
import { User as UserManager } from 'xdl';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/errors';
interface IUserData {
username?: string;
+4 -7
View File
@@ -2,17 +2,17 @@ import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import uuidv4 from 'uuid/v4';
import { AndroidShellApp, ImageUtils } from 'xdl';
import uuidv4 from 'uuid/v4';
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 { uploadBuildToS3 } from 'turtle/builders/utils/uploader';
import config from 'turtle/config';
import { IAndroidCredentials, IJob, IJobResult } from 'turtle/job';
import logger from 'turtle/logger';
import { IAndroidCredentials, IJob, IJobResult } from 'turtle/job';
import { formatShellAppDirectory } from 'turtle/builders/utils/android/workingdir';
export default async function buildAndroid(jobData: IJob): Promise<IJobResult> {
const credentials = await getOrCreateCredentials(jobData);
@@ -25,10 +25,7 @@ export default async function buildAndroid(jobData: IJob): Promise<IJobResult> {
uploadPath: apkFilePath,
s3FileKey,
...config.builder.fakeUpload && {
fakeUploadBuildPath:
jobData.fakeUploadBuildPath
? jobData.fakeUploadBuildPath
: path.join(jobData.fakeUploadDir || config.directories.fakeUploadDir, fakeUploadFilename),
fakeUploadBuildPath: jobData.fakeUploadBuildPath ? jobData.fakeUploadBuildPath : path.join(jobData.fakeUploadDir || config.directories.fakeUploadDir, fakeUploadFilename)
},
});
+1 -1
View File
@@ -1,5 +1,5 @@
import buildAndroid from './android';
import buildIOS from './ios';
import buildAndroid from './android';
export default {
ios: buildIOS,
+6 -6
View File
@@ -1,13 +1,13 @@
import _ from 'lodash';
import { IosIPABuilder as createIPABuilder } from 'xdl';
import { IContext } from 'turtle/builders/ios/context';
import runShellAppBuilder from 'turtle/builders/utils/ios/shellAppBuilder';
import { logErrorOnce } from 'turtle/builders/utils/common';
import * as fileUtils from 'turtle/builders/utils/file';
import * as keychain from 'turtle/builders/utils/ios/keychain';
import runShellAppBuilder from 'turtle/builders/utils/ios/shellAppBuilder';
import { IJob } from 'turtle/job';
import logger from 'turtle/logger/index';
import { IContext } from 'turtle/builders/ios/context';
import { IJob } from 'turtle/job';
export default async function buildArchive(ctx: IContext, job: IJob) {
let keychainInfo;
@@ -34,11 +34,11 @@ async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, m
const {
credentials: { provisioningProfile, certPassword, teamId, password },
config: { bundleIdentifier: bundleIdentifierFromConfig },
config: { bundleIdentifier: _bundleIdentifierFromConfig },
} = job;
const bundleIdentifierFromManifest = _.get(manifest, 'ios.bundleIdentifier');
const bundleIdentifier = bundleIdentifierFromConfig || bundleIdentifierFromManifest;
const _bundleIdentifierFromManifest = _.get(manifest, 'ios.bundleIdentifier');
const bundleIdentifier = _bundleIdentifierFromConfig || _bundleIdentifierFromManifest;
const { provisioningProfilePath } = ctx;
await fileUtils.writeBase64ToBinaryFile(provisioningProfilePath, provisioningProfile as string);
+7 -15
View File
@@ -3,10 +3,10 @@ import * as path from 'path';
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, PLATFORMS } from 'turtle/constants/index';
import { IJob } from 'turtle/job';
import { formatShellAppDirectory } from 'turtle/builders/utils/ios/workingdir';
export interface IContext {
appDir: string;
@@ -30,15 +30,9 @@ const { BUILD_TYPES } = IOS;
export function createBuilderContext(job: IJob): IContext {
const { join } = path;
const appUUID = uuidv4();
const {
manifest: {
sdkVersion: sdkVersionFromManifest = null,
} = {},
sdkVersion: sdkVersionFromJob,
config: { buildType },
} = job;
const { manifest: { sdkVersion: _sdkVersionFromManifest = null } = {}, sdkVersion: _sdkVersionFromJob, config: { buildType } } = job;
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
const sdkVersion = _sdkVersionFromJob || _sdkVersionFromManifest;
const workingDir = formatShellAppDirectory(sdkVersion);
const context: any = {
@@ -50,7 +44,7 @@ export function createBuilderContext(job: IJob): IContext {
context.provisioningProfileDir = join(context.appDir, 'provisioning');
context.provisioningProfilePath = join(
context.provisioningProfileDir,
`${appUUID}.mobileprovision`,
`${appUUID}.mobileprovision`
);
context.tempCertPath = join(context.appDir, 'cert.p12');
context.baseArchiveDir = join(context.appDir, 'archive');
@@ -61,7 +55,7 @@ export function createBuilderContext(job: IJob): IContext {
context.archiveDir = join(
context.baseArchiveDir,
'Release',
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`,
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`
);
} else if (buildType === BUILD_TYPES.ARCHIVE) {
context.outputPath = join(context.appDir, 'archive.xcarchive');
@@ -72,7 +66,7 @@ export function createBuilderContext(job: IJob): IContext {
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.xcarchive`,
'Products',
'Applications',
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`,
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`
);
}
@@ -80,9 +74,7 @@ export function createBuilderContext(job: IJob): IContext {
const s3Filename = `${job.experienceName}-${appUUID}-${buildType}.${s3FileExtension}`;
if (config.builder.fakeUpload) {
const fakeUploadFilename = s3Filename.replace('/', '\\');
context.fakeUploadBuildPath = job.fakeUploadBuildPath
? job.fakeUploadBuildPath
: join(job.fakeUploadDir || config.directories.fakeUploadDir, fakeUploadFilename);
context.fakeUploadBuildPath = job.fakeUploadBuildPath ? job.fakeUploadBuildPath : join(job.fakeUploadDir || config.directories.fakeUploadDir, fakeUploadFilename);
} else {
context.s3FileKey = join(PLATFORMS.IOS, s3Filename);
}
+4 -4
View File
@@ -15,7 +15,7 @@ import { IJob, IJobResult } from 'turtle/job';
const { BUILD_TYPES } = IOS;
let SUPPORTED_SDK_VERSIONS: number[];
let SUPPORTED_SDK_VERSIONS: Array<number>;
export default async function iosBuilder(job: IJob): Promise<IJobResult> {
await ensureCanBuildSdkVersion(job);
@@ -76,9 +76,9 @@ async function ensureCanBuildSdkVersion(job: IJob) {
}
}
async function findSupportedSdkVersions(): Promise<number[]> {
async function findSupportedSdkVersions(): Promise<Array<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));
const sdks = files.filter(file => file.startsWith(SDK_DIR_PREFIX));
return sdks.map(sdk => parseInt(sdk.substr(SDK_DIR_PREFIX.length)));
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { IContext } from 'turtle/builders/ios/context';
import runShellAppBuilder from 'turtle/builders/utils/ios/shellAppBuilder';
import { IJob } from 'turtle/job';
import logger from 'turtle/logger';
import { IContext } from 'turtle/builders/ios/context';
import { IJob } from 'turtle/job';
export default async function buildSimulator(ctx: IContext, job: IJob) {
const l = logger.withFields({ buildPhase: 'running simulator builder' });
+3 -2
View File
@@ -1,14 +1,14 @@
import path from 'path';
import fs from 'fs-extra';
import uuidv4 from 'uuid/v4';
import fs from 'fs-extra';
import { AndroidKeystore } from 'xdl';
import * as sqs from 'turtle/aws/sqs';
import { UPDATE_CREDENTIALS } from 'turtle/constants/build';
import { IAndroidCredentials, IJob } from 'turtle/job';
import logger from 'turtle/logger';
import { isOffline } from 'turtle/turtleContext';
import { IAndroidCredentials, IJob } from 'turtle/job';
async function getOrCreateCredentials(jobData: IJob): Promise<IAndroidCredentials> {
if (!jobData.credentials) {
@@ -42,6 +42,7 @@ async function getOrCreateCredentials(jobData: IJob): Promise<IAndroidCredential
} else {
await sqs.sendMessage(jobData.id, UPDATE_CREDENTIALS, {
...credentials,
turtleVersion: jobData.config.turtleVersion,
});
l.info('Keystore sent to storage successfully');
}
+1 -3
View File
@@ -1,8 +1,6 @@
import path from 'path';
import { ExponentTools } from 'xdl';
import config from 'turtle/config';
import { ExponentTools } from 'xdl';
import { PLATFORMS } from 'turtle/constants';
export function formatShellAppDirectory(sdkVersion: string) {
+1 -1
View File
@@ -1,8 +1,8 @@
import * as url from 'url';
import config from 'turtle/config';
import { IJob } from 'turtle/job';
import logger from 'turtle/logger';
import { IJob } from 'turtle/job';
export function getExperienceUrl(job: IJob) {
const { experienceName, config: jobConfig } = job;
+2 -1
View File
@@ -1,13 +1,14 @@
import { IosKeychain } from 'xdl';
import { IContext } from 'turtle/builders/ios/context';
import * as fileUtils from 'turtle/builders/utils/file';
import logger from 'turtle/logger';
import { IContext } from 'turtle/builders/ios/context';
interface IKeychain {
path: string;
}
export async function create(ctx: IContext): Promise<IKeychain> {
const l = logger.withFields({ buildPhase: 'creating keychain' });
try {
+6 -6
View File
@@ -2,22 +2,22 @@ import path from 'path';
import util from 'util';
import copy from 'copy';
import _ from 'lodash';
import { ImageUtils, IosShellApp } from 'xdl';
import _ from 'lodash';
import { IContext } from 'turtle/builders/ios/context';
import * as commonUtils from 'turtle/builders/utils/common';
import * as imageHelpers from 'turtle/builders/utils/image';
import { PLATFORMS } from 'turtle/constants';
import { IJob } from 'turtle/job';
import logger from 'turtle/logger/index';
import { IContext } from 'turtle/builders/ios/context';
import { IJob } from 'turtle/job';
import { PLATFORMS } from 'turtle/constants';
const copyAsync = util.promisify(copy);
export default async function runShellAppBuilder(ctx: IContext, job: IJob): Promise<any> {
const { config: jobConfig, manifest, sdkVersion: sdkVersionFromJob } = job;
const { config: jobConfig, manifest, sdkVersion: _sdkVersionFromJob } = job;
const { buildType, releaseChannel } = jobConfig;
const sdkVersion = _.get(manifest, 'sdkVersion', sdkVersionFromJob);
const sdkVersion = _.get(manifest, 'sdkVersion', _sdkVersionFromJob);
const applicationFilesSrc = path.join(
ctx.workingDir,
'shellAppBase-builds',
-2
View File
@@ -1,7 +1,5 @@
import path from 'path';
import { ExponentTools } from 'xdl';
import config from 'turtle/config';
import { PLATFORMS } from 'turtle/constants';
+4 -10
View File
@@ -1,8 +1,8 @@
import os from 'os';
import path from 'path';
import { PLATFORMS } from 'turtle/constants';
import { env, envNum, envOptional, envTransform } from 'turtle/utils/env';
import { PLATFORMS } from 'turtle/constants';
export default {
env: env('NODE_ENV'),
@@ -66,7 +66,7 @@ export default {
skipCleanup: envTransform(
'TURTLE_SKIP_CLEANUP',
'0',
(val: string) => env('NODE_ENV') === 'development' && val === '1',
(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', 60 * 60 * 1000),
@@ -77,15 +77,9 @@ export default {
workingDir: env('TURTLE_WORKING_DIR_PATH', path.join(__dirname, '../workingdir')),
shellTarballsDir: env('TURTLE_SHELL_TARBALLS_DIR', path.join(__dirname, '../shellTarballs')),
artifactsDir: env('TURTLE_ARTIFACTS_DIR', path.join(__dirname, '../artifacts')),
androidDependenciesDir: env(
'TURTLE_ANDROID_DEPENDENCIES_DIR',
path.join(os.homedir(), '.turtle/androidDependencies'),
),
androidDependenciesDir: env('TURTLE_ANDROID_DEPENDENCIES_DIR', path.join(os.homedir(), '.turtle/androidDependencies')),
tempS3LogsDir: env('TURTLE_TEMP_S3_LOGS_DIR', '/tmp/logs'),
fakeUploadDir: envOptional('TURTLE_FAKE_UPLOAD_DIR'),
temporaryFilesRoot: env(
'TURTLE_TEMPORARY_FILES_DIR',
path.join(os.platform() === 'darwin' ? '/private' : '/', 'tmp', 'turtle'),
),
temporaryFilesRoot: env('TURTLE_TEMPORARY_FILES_DIR', path.join(os.platform() === 'darwin' ? '/private' : '/', 'tmp', 'turtle')),
},
};
+1 -1
View File
@@ -5,7 +5,7 @@ export enum JOB_STATES {
FINISHED = 'finished',
ERRORED = 'errored',
SENT_TO_QUEUE = 'sent-to-queue',
}
};
export const UPDATE_CREDENTIALS = 'update-credentials';
+6
View File
@@ -7,3 +7,9 @@ export const PLATFORMS = {
};
export { IOS, BUILD };
export const TURTLE_VERSIONS = {
NEW: 'new',
OLD: 'old',
BOTH: 'both',
};
+1
View File
@@ -6,6 +6,7 @@ export interface IJob {
sdkVersion: string;
config: {
releaseChannel: string;
turtleVersion?: string;
publicUrl?: string;
// ios
buildType?: string;
+15 -15
View File
@@ -1,22 +1,17 @@
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import { logErrorOnce } from 'turtle/builders/utils/common';
import logger from 'turtle/logger';
import * as sqs from 'turtle/aws/sqs';
import builders from 'turtle/builders';
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 } 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'));
import builders from 'turtle/builders';
import config from 'turtle/config';
import * as redis from 'turtle/utils/redis';
import { checkShouldExit, setCurrentJobId } from 'turtle/turtleContext';
import * as buildStatusMetric from 'turtle/metrics/buildStatus';
import * as buildDurationMetric from 'turtle/metrics/buildDuration';
import { getPriorities } from 'turtle/utils/priorities';
function _maybeExit() {
if (checkShouldExit()) {
@@ -34,6 +29,7 @@ export async function doJob() {
export async function getJob() {
_maybeExit();
logger.info('Fetching job');
// eslint-disable-next-line no-constant-condition
while (true) {
try {
let job = null;
@@ -113,7 +109,9 @@ async function processJob(jobData: any) {
function failAfterMaxJobTime(priority: string, receiptHandle: string, job: any) {
return setTimeout(async () => {
try {
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, { turtleVersion });
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, {
turtleVersion: job.config.turtleVersion,
});
await deleteMessage(priority, receiptHandle);
} finally {
logger.error('Going to terminate turtle agent, just in case...');
@@ -124,11 +122,13 @@ function failAfterMaxJobTime(priority: string, receiptHandle: string, job: any)
async function build(job: any) {
const s3Url = await logger.init(job);
const { turtleVersion } = job.config;
try {
await sqs.sendMessage(job.id, BUILD.JOB_STATES.IN_PROGRESS, {
logUrl: s3Url,
logFormat: 'json',
turtleVersion,
});
const result = await (builders as any)[job.platform](job);
sqs.sendMessage(job.id, BUILD.JOB_STATES.FINISHED, { ...result, turtleVersion });
+1 -1
View File
@@ -12,5 +12,5 @@ export default baseJobSchema.concat(
keystoreAlias: Joi.string(),
keystore: Joi.string(),
}),
}),
})
);
+2 -1
View File
@@ -1,6 +1,6 @@
import Joi from 'joi';
import { PLATFORMS } from 'turtle/constants';
import { PLATFORMS, TURTLE_VERSIONS } from 'turtle/constants';
export default Joi.object().keys({
platform: Joi.string().valid(Object.values(PLATFORMS)),
@@ -10,6 +10,7 @@ export default Joi.object().keys({
config: Joi.object()
.keys({
releaseChannel: Joi.string().regex(/[a-z\d][a-z\d._-]*/),
turtleVersion: Joi.string().valid(Object.values(TURTLE_VERSIONS)),
})
.unknown(true),
projectDir: Joi.string(),
+1 -1
View File
@@ -23,5 +23,5 @@ export default baseJobSchema.concat(
then: Joi.optional(),
otherwise: Joi.required(),
}),
}),
})
);
+11 -11
View File
@@ -6,36 +6,36 @@ import config from 'turtle/config';
import * as constants from 'turtle/constants/logger';
import { IJob } from 'turtle/job';
export interface ILogglyConfig {
export interface LogglyConfig {
token: string;
subdomain: string;
tags: string[];
tags: Array<string>;
}
interface ILogglyExtraFields {
jobID: string;
interface LogglyExtraFields {
jobID: string,
experienceName: string;
}
export default class HackyLogglyStream extends EventEmitter {
private logglyStream: BunyanLoggly;
private extraFields: ILogglyExtraFields | null;
logglyStream: BunyanLoggly;
extraFields: LogglyExtraFields | null;
constructor(logglyConfig: ILogglyConfig) {
constructor(logglyConfig: LogglyConfig) {
super();
this.logglyStream = new BunyanLoggly(logglyConfig, config.logger.loggly.buffer);
this.extraFields = null;
}
public init(job: IJob) {
init(job: IJob) {
this.extraFields = {
jobID: job.id,
experienceName: job.experienceName,
experienceName: job.experienceName
};
}
public cleanup() {
cleanup() {
this.extraFields = null;
}
public write(rec: any) {
write(rec: any) {
const { msg: message, hostname: host, level, ...other } = rec;
this.logglyStream.write({
message,
+11 -11
View File
@@ -1,16 +1,16 @@
import bunyan from 'bunyan';
import bunyanDebugStream from 'bunyan-debug-stream';
import { SentryStream } from 'bunyan-sentry-stream';
import { Client as RavenClient } from 'raven';
import { SentryStream } from 'bunyan-sentry-stream';
import bunyanDebugStream from 'bunyan-debug-stream';
import config from 'turtle/config';
import * as constants from 'turtle/constants/logger';
import { IJob } from 'turtle/job';
import HackyLogglyStream from 'turtle/logger/hackyLogglyStream';
import S3Stream from 'turtle/logger/s3Stream';
import HackyLogglyStream from 'turtle/logger/hackyLogglyStream';
import * as constants from 'turtle/constants/logger';
import { isOffline } from 'turtle/turtleContext';
import { IJob } from 'turtle/job';
interface IStream {
interface Stream {
stream: any;
type?: string;
level?: string;
@@ -20,7 +20,7 @@ interface IStream {
export const s3logger = new S3Stream();
let logglyStream: HackyLogglyStream;
const streams: IStream[] = [];
const streams: Stream[] = [];
if (isOffline()) {
const prettyStdOut = new bunyanDebugStream({ forceColor: true });
@@ -33,7 +33,7 @@ if (isOffline()) {
stream: s3logger,
reemitErrorEvents: true,
level: config.logger.client.level,
},
}
);
}
@@ -56,7 +56,7 @@ if (config.logger.loggly.token) {
'app-shell-apps',
`platform.${config.platform}`,
`environment.${config.deploymentEnv}`,
],
]
};
logglyStream = new HackyLogglyStream(logglyConfig);
streams.push({
@@ -91,9 +91,9 @@ logger.cleanup = async () => {
export default logger;
export function withFields(loggerObj: any, extraFields: any) {
export function withFields(_logger: any, extraFields: any) {
return constants.LEVELS.reduce((obj, level) => {
obj[level] = (...args: any[]) => loggerObj[level](extraFields, ...args);
obj[level] = (...args: any[]) => _logger[level](extraFields, ...args);
return obj;
}, {} as any);
}
+24 -24
View File
@@ -1,6 +1,6 @@
import EventEmitter from 'events';
import path from 'path';
import EventEmitter from 'events';
import fs from 'fs-extra';
import _ from 'lodash';
@@ -11,13 +11,13 @@ import * as constants from 'turtle/constants/logger';
import { IJob } from 'turtle/job';
export default class S3Stream extends EventEmitter {
private s3Url: string | null;
private lastFlush: number | null;
private filePath: string | null;
private fileHandle: number | null;
private finishedPromise: Promise<any> | null;
private finishedPromiseResolveFn: any;
private secrets: any;
s3Url: string | null;
lastFlush: number | null;
filePath: string | null;
fileHandle: number | null;
finishedPromise: Promise<any> | null;
finishedPromiseResolveFn: any;
secrets: any;
constructor() {
super();
@@ -29,7 +29,7 @@ export default class S3Stream extends EventEmitter {
this.finishedPromiseResolveFn = null;
}
public async init(job: IJob) {
async init(job: IJob) {
this.s3Url = `logs/${job.experienceName}/${job.id}`;
const dir = config.directories.tempS3LogsDir;
const exists = await fs.pathExists(dir);
@@ -39,12 +39,12 @@ export default class S3Stream extends EventEmitter {
this.filePath = path.join(dir, job.id);
this.fileHandle = await fs.open(this.filePath, 'w+', 0o660);
this.secrets = {};
this.finishedPromise = new Promise((resolveFn) => (this.finishedPromiseResolveFn = resolveFn));
this.finishedPromise = new Promise(res => (this.finishedPromiseResolveFn = res));
const res = await this.flush() as any;
return res.Location;
}
public async cleanup() {
async cleanup() {
const res = this.finishedPromiseResolveFn;
try {
await this.flush(true);
@@ -59,7 +59,7 @@ export default class S3Stream extends EventEmitter {
}
}
public flush(force = false) {
flush(force = false) {
if (force || !this.lastFlush) {
return this._flush();
}
@@ -69,7 +69,15 @@ export default class S3Stream extends EventEmitter {
}
}
public write(recRaw: any) {
_flush() {
this.lastFlush = Date.now();
return uploadFile({
srcPath: this.filePath as string,
key: this.s3Url as string,
});
}
write(recRaw: any) {
if (this.fileHandle) {
const rec = JSON.stringify({
...recRaw,
@@ -80,7 +88,7 @@ export default class S3Stream extends EventEmitter {
}
const recSanitized = Object.keys(this.secrets).reduce(
(acc, secret) => acc.replace(new RegExp(secret, 'g'), this.secrets[secret]),
rec,
rec
);
fs
.write(this.fileHandle as number, `${recSanitized}\n`)
@@ -90,22 +98,14 @@ export default class S3Stream extends EventEmitter {
}
}
public addSecretsToFilter(secrets: any) {
addSecretsToFilter(secrets: any) {
const filteredSecrets = secrets.filter((i: any) => i !== undefined && i !== null);
const replacements = filteredSecrets.map((i: any) => 'X'.repeat(i.length));
const toAdd = _.zipObject(filteredSecrets, replacements);
this.secrets = Object.assign({}, this.secrets, toAdd);
}
public waitForLogger() {
waitForLogger() {
return this.finishedPromise;
}
private _flush() {
this.lastFlush = Date.now();
return uploadFile({
srcPath: this.filePath as string,
key: this.s3Url as string,
});
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import logger from 'turtle/logger';
import * as cloudwatch from 'turtle/aws/cloudwatch';
import { TYPE_DIMENSIONS } from 'turtle/aws/cloudwatch/dimensions';
import logger from 'turtle/logger';
const TURTLE_BUILD_DURATION_METRIC_NAME = 'turtleBuildDuration';
const TOTAL_BUILD_DURATION_METRIC_NAME = 'totalBuildDuration';
+3 -3
View File
@@ -1,8 +1,8 @@
import * as cloudwatch from 'turtle/aws/cloudwatch';
import { TYPE_DIMENSIONS } from 'turtle/aws/cloudwatch/dimensions';
import * as reducers from 'turtle/aws/cloudwatch/reducers';
import config from 'turtle/config';
import logger from 'turtle/logger';
import * as cloudwatch from 'turtle/aws/cloudwatch';
import * as reducers from 'turtle/aws/cloudwatch/reducers';
import { TYPE_DIMENSIONS } from 'turtle/aws/cloudwatch/dimensions';
const BUILD_METRIC_NAME = 'build';
+3 -3
View File
@@ -1,12 +1,12 @@
import * as cloudwatch from 'turtle/aws/cloudwatch';
import logger from 'turtle/logger';
import * as buildDuration from 'turtle/metrics/buildDuration';
import * as cloudwatch from 'turtle/aws/cloudwatch';
import * as buildStatus from 'turtle/metrics/buildStatus';
import * as buildDuration from 'turtle/metrics/buildDuration';
const metricsToRegister = [buildStatus, buildDuration];
export async function init() {
logger.info('Initializing Cloudwatch metrics');
await cloudwatch.init();
metricsToRegister.forEach((metric) => metric.register());
metricsToRegister.forEach(metric => metric.register());
}
+5 -5
View File
@@ -1,13 +1,13 @@
export = BunyanLoggly;
export = BunyanLoggly
declare class BunyanLoggly {
constructor(options: BunyanLoggly.IOptions, bufferLength?: number, bufferTimeout?: number, logglyCallback?: any);
public write(data: any): void;
constructor(options: BunyanLoggly.IOptions, bufferLength?: number, bufferTimeout?: number, logglyCallback?: Function);
write(data: any): void;
}
declare namespace BunyanLoggly {
interface IOptions {
token: string;
subdomain: string;
token: string
subdomain: string
}
}
+7 -7
View File
@@ -1,12 +1,12 @@
import { LoggerDetach } from 'xdl';
import config from 'turtle/config';
import { doJob } from 'turtle/jobManager';
import logger from 'turtle/logger';
import setup from 'turtle/setup';
import { checkShouldExit, setShouldExit } from 'turtle/turtleContext';
import { doJob } from 'turtle/jobManager';
import { setShouldExit, checkShouldExit } from 'turtle/turtleContext';
process.on('unhandledRejection', (err) => logger.error('Unhandled promise rejection:', err));
process.on('unhandledRejection', err => logger.error('Unhandled promise rejection:', err));
function handleExit() {
if (checkShouldExit()) {
@@ -14,7 +14,7 @@ function handleExit() {
process.exit(1);
}
logger.warn(
`Received termination signal. Will exit after current build. To force exit press Ctrl-C again`,
`Received termination signal. Will exit after current build. To force exit press Ctrl-C again`
);
setShouldExit();
}
@@ -23,13 +23,13 @@ process.on('SIGINT', handleExit);
async function main() {
logger.info(
'Starting Turtle... '
+ `NODE_ENV=${config.env}, PLATFORM=${config.platform}, DEPLOYMENT_ENVIRONMENT=${config.deploymentEnv}`,
`Starting Turtle... (NODE_ENV=${config.env}, PLATFORM=${config.platform}, DEPLOYMENT_ENVIRONMENT=${config.deploymentEnv})`
);
LoggerDetach.configure(logger);
if (setup[config.platform]) {
await setup[config.platform]();
}
// eslint-disable-next-line no-constant-condition
while (true) {
try {
await doJob();
@@ -41,5 +41,5 @@ async function main() {
main()
.then(() => logger.error('This should never happen...'))
.catch((err) => logger.error('Something went terribly wrong :(', err))
.catch(err => logger.error('Something went terribly wrong :(', err))
.then(() => process.exit(1));
+1 -1
View File
@@ -1,6 +1,6 @@
import { PLATFORMS } from 'turtle/constants';
import android from 'turtle/setup/android';
import ios from 'turtle/setup/ios';
import android from 'turtle/setup/android';
export default {
[PLATFORMS.IOS]: ios,
+3 -3
View File
@@ -1,6 +1,6 @@
import fs from 'fs-extra';
import os from 'os';
import path from 'path';
import fs from 'fs-extra';
import * as keychain from 'turtle/builders/utils/ios/keychain';
import logger from 'turtle/logger';
@@ -11,9 +11,9 @@ async function deleteProvisioningProfilesFromHomedir() {
const exists = await fs.pathExists(provisioningProfilesDir);
if (exists) {
const provisioningProfiles = (await fs.readdir(provisioningProfilesDir)).filter(
(filename) => path.extname(filename) === '.mobileprovision',
filename => path.extname(filename) === '.mobileprovision',
);
await Promise.all(provisioningProfiles.map((file) => fs.remove(path.join(provisioningProfilesDir, file))));
await Promise.all(provisioningProfiles.map(file => fs.remove(path.join(provisioningProfilesDir, file))));
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
import config from 'turtle/config';
let shouldExit = false;
let currentJobId: string | null = null;
let _currentJobId: string | null = null;
export const setShouldExit = () => {
shouldExit = true;
@@ -12,11 +12,11 @@ export const checkShouldExit = () => {
};
export const getCurrentJobId = () => {
return currentJobId;
return _currentJobId;
};
export const setCurrentJobId = (id: string | null) => {
currentJobId = id;
_currentJobId = id;
};
export const isOffline = () => config.builder.mode === 'offline';
+2 -3
View File
@@ -1,8 +1,7 @@
import Joi from 'joi';
import os from 'os';
import config from 'turtle/config';
import os from 'os';
import logger from 'turtle/logger';
import Joi from 'joi';
import { getRedisClient, RedisClient } from 'turtle/utils/redis';
export const NORMAL = 'normalPriority';
+9 -8
View File
@@ -1,20 +1,21 @@
import Redis from 'ioredis';
import config from 'turtle/config';
import logger from 'turtle/logger';
import config from 'turtle/config';
const MILLIS_TO_UPLOAD_LOGS = 3000;
export const MILLIS_CONNECTION_TIMEOUT = 10000;
export enum RedisClient {
Subscriber = 'REDIS_CLIENT_SUBSCRIBER',
Default = 'REDIS_CLIENT_DEFAULT',
Configuration = 'REDIS_CLIENT_CONFIGURATION',
}
interface IClientConfig { [key: string]: string; }
type ClientConfig = { [key: string]: string; };
const clientToUrl: IClientConfig = {
const clientToUrl: ClientConfig = {
[RedisClient.Subscriber]: config.redis.url,
[RedisClient.Default]: config.redis.url,
[RedisClient.Configuration]: config.redis.configUrl,
@@ -41,21 +42,21 @@ interface IRedisClients {
[key: string]: any;
}
const redisClients: IRedisClients = {
const _redisClients: IRedisClients = {
[RedisClient.Subscriber]: null,
[RedisClient.Default]: null,
[RedisClient.Configuration]: null,
};
export async function getRedisClient(type = RedisClient.Default) {
if (!redisClients[type]) {
if (!_redisClients[type]) {
try {
redisClients[type] = await connect(MILLIS_CONNECTION_TIMEOUT, type);
_redisClients[type] = await connect(MILLIS_CONNECTION_TIMEOUT, type);
} catch (err) {
logger.error(err);
}
}
return redisClients[type];
return _redisClients[type];
}
export async function checkIfCancelled(jobId: string) {
@@ -75,7 +76,7 @@ export async function registerListener(jobId: string, deleteMessage: any) {
try {
const redis = await getRedisClient(RedisClient.Subscriber);
redis.subscribe('jobs:cancelled');
redis.on('message', async (_: any, message: any) => {
redis.on('message', async function(_: any, message: any) {
if (message === jobId) {
logger.info({ lastBuildLog: true }, 'Job cancelled - killing process');
await deleteMessage();
@@ -20,8 +20,6 @@ export API_PORT="3000"
export REDIS_URL="noop"
export REDIS_CONFIG_URL="noop"
PLATFORM=android yarn set-workingdir:remote
yarn test:smoke:android
smoke_result=$?
+1 -1
View File
@@ -18,7 +18,7 @@ export API_PORT="443"
export REDIS_URL="noop"
export REDIS_CONFIG_URL="noop"
PLATFORM=ios yarn set-workingdir:remote
yarn init-workingdir:remote-ios
yarn test:smoke:ios
smoke_result=$?
File renamed without changes.
-5
View File
@@ -8,9 +8,4 @@ if [ ! -f "$KEYFILE" ]; then
update-local-secrets --keyfile-only
fi
if ! command -v git-crypt >/dev/null 2>&1 ; then
echo "You need to have git-crypt installed."
exit 1
fi
git-crypt unlock "$KEYFILE"
+1 -2
View File
@@ -4,8 +4,7 @@
"jsRules": {},
"rules": {
"quotemark": [true, "single", "jsx-double"],
"object-literal-sort-keys": false,
"no-namespace": false
"object-literal-sort-keys": false
},
"rulesDirectory": []
}
+594 -1067
View File
File diff suppressed because it is too large. Load diff