Compare commits

...
Author SHA1 Message Date
Dominik Sokal 70605254dd update CHANGELOG 2019-03-13 14:46:00 +01:00
Dominik Sokal 0f3fdde934 add config param 2019-03-13 14:43:35 +01:00
Dominik Sokal 33b2a569aa Release 0.5.13 2019-03-12 15:10:40 +01:00
Dominik Sokal 2febf6bcdd upgrade xdl to 53.1.6-alpha.2 2019-03-12 15:08:41 +01:00
Wojciech Kozyra e2c9ee0e39 [android-builder] check if sdk is supported (#25) 2019-03-06 15:56:31 +01:00
Dominik Sokal f017540571 don't fetch android shell apps when running tests on ci (they are already there) 2019-02-28 11:55:26 +01:00
Dominik Sokal 5b65eb3d6e add json to moduleFileExtensions (jest) 2019-02-28 11:37:53 +01:00
Dominik Sokal 60874f31c9 Release 0.5.12 2019-02-26 09:51:28 +01:00
Dominik Sokal 78beb3c0b3 fix yarn.lock 2019-02-26 09:51:01 +01:00
Dominik Sokal 03633d94e5 update CHANGELOG 2019-02-26 09:46:17 +01:00
Nick Novitski 392e4487b7 Add google cloud logger (#21) 2019-02-25 13:10:11 -08:00
sierrakn f316cb0e0c Sending manifest when building Android shell app (#22)
# Why

Please describe the motivation for this PR, and link to relevant GitHub issues, forums posts, or feature requests.

# How

How did you build this feature or fix this bug and why?

# Test Plan

Please describe how you tested this change and how a reviewer could reproduce your test, especially if this PR does not include automated tests! If possible, please also provide terminal output and/or screenshots demonstrating your test/reproduction.
2019-02-25 11:11:21 +01:00
Wojciech Kozyra 968ce7d8bb update traveling-fastlane 2019-02-22 14:56:10 +01:00
Wojciech Kozyra 5876a11568 Update CHANGELOG.md 2019-02-22 13:48:02 +01:00
Wojciech Kozyra 7448e1f4e4 Release 0.5.11 2019-02-22 13:45:28 +01:00
Dominik Sokal 4338e71005 [ios] add support for expo client builds (#19)
* [ios] add support for expo client builds

* change client shell app tarball url

* add creating an ad hoc provisioning profile

* cleanup logs

* update xdl and traveling-fastlane

* fix for breaking changes in xdl
2019-02-22 13:18:08 +01:00
Eric Samelson 7323412095 update CHANGELOG for 0.5.10 2019-02-19 14:07:32 -08:00
Eric Samelson c1d5a17e45 Release 0.5.10 2019-02-19 14:04:44 -08:00
Eric Samelson 2771ad0829 Revert "Android splash screen standalone app adjustments (#3)"
This reverts commit 31c17a4d72.
2019-02-19 11:29:00 -08:00
Eric Samelson 32cc988072 update android sdk 32 shell app tarball to 2.10.7 2019-02-19 11:28:01 -08:00
Wojciech Kozyra 0b3ce2c8e0 fix setup script 2019-02-14 10:26:11 +01:00
Wojciech Kozyra 9c46ee143e register turtle version in redis (#20)
register turtle version in redis
2019-02-13 15:53:03 +01:00
Bartłomiej Bukowski 31c17a4d72 Android splash screen standalone app adjustments (#3)
* Android splash screen standalone adjustments

* Update xdl
2019-02-06 14:49:43 +01:00
Stanisław Chmiela ee3f265294 Release 0.5.9 2019-02-06 14:02:31 +01:00
Stanisław Chmiela c1cca91e68 [changelog] Add changelog entry for 0.5.8 2019-02-06 14:01:45 +01:00
Stanisław Chmiela 038bafe20b [ios] Fix bare notifications in standalone mode on SDK32 (#18)
See https://github.com/expo/expo/pull/3431.
2019-02-06 13:58:01 +01:00
Eric Samelson f2fef264d7 update CHANGELOG for 0.5.7 2019-01-31 17:40:08 -08:00
Eric Samelson fae77b4455 Release 0.5.7 2019-01-31 17:36:57 -08:00
Eric Samelson a3ffa0411e update android sdk 32 shell app with bundled assets fix 2019-01-31 15:52:31 -08:00
Dominik Sokal 27dd680cbd update CHANGELOG 2019-01-25 13:35:12 +01:00
Dominik Sokal 2b5a370f48 Release 0.5.6 2019-01-25 13:30:00 +01:00
Dominik Sokal 21933f5a6a update shell apps for sdk 30, 31 and 32 (built with xcode 10.1) 2019-01-25 13:27:45 +01:00
Dominik Sokal a5bd4f0c97 Update CHANGELOG 2019-01-24 09:35:16 +01:00
Dominik Sokal 8e10aacf59 Release 0.5.5 2019-01-24 09:33:54 +01:00
Dominik Sokal 3dd74ef406 fix releaseBeta.sh 2019-01-23 17:00:49 +01:00
Dominik Sokal 87f7085fc4 print a reminder about updating CHANGELOG when releasing turtle-cli 2019-01-23 16:58:51 +01:00
Dominik Sokal 0b0b5a7c72 update CONTRIBUTING.md 2019-01-23 16:45:16 +01:00
Dominik Sokal d6dee79bcf update CHANGELOG 2019-01-23 16:45:09 +01:00
Dominik Sokal db05eeeed3 send build duration to www (#15) 2019-01-23 16:29:17 +01:00
Stanisław Chmiela cc317a6572 Release 0.5.4 2019-01-22 12:07:20 +01:00
Stanisław Chmiela 149073466d [ios] Update iOS shell tarball for SDK32 (#13)
Updated iOS shell tarball to https://github.com/expo/expo/commit/0847c1073d38c366866f3d9279ddd662da47dc9b
2019-01-22 11:50:53 +01:00
Dominik Sokal 776aec445b Update CHANGELOG.md 2019-01-22 09:56:41 +01:00
Dominik Sokal d6432b6455 Release 0.5.3 2019-01-21 16:08:51 +01:00
Dominik Sokal 81f3a0e300 update CHANGELOG for 0.5.3 2019-01-21 16:05:28 +01:00
Dominik Sokal 167766519e Update Android build tools (#12)
* update android build-tools

* update sdkmanager

* pipe yes to sdkmanager

* accepty licenses prior to using sdkmanager

* accept android 28 license

* alter PATH

* do we need android ndk?

* revert installing Android NDK

* remove redundant backslash

* fix license issue when building with turtle-cli

* change path to build-tools
2019-01-21 16:00:44 +01:00
Dominik Sokal b0d7156691 Release 0.5.2 2019-01-18 14:27:02 +01:00
Dominik Sokal 0fdf722a48 Update changelog 2019-01-18 14:25:47 +01:00
Dominik Sokal 0382e6bc18 add CONTRIBUTING.md and update README (#11)
* add CONTRIBUTING.md and update README

* address pr review requests
2019-01-18 14:21:39 +01:00
Wojciech Kozyra 07a72ea5cb fix ci tests 2019-01-18 12:45:08 +01:00
Dominik Sokal 1d3647db98 fix link to turtle-cli npm package 2019-01-18 11:54:05 +01:00
Dominik Sokal 53b96d399f update CHANGELOG 2019-01-18 11:50:56 +01:00
Dominik Sokal 78067fc8a6 use release-it to handle releases (#10) 2019-01-18 11:39:26 +01:00
Wojciech Kozyra 4b29da8f5b fixes for expo outside universe (#8)
* fixes for expo outside universe

* move test scripts + fix typos
2019-01-18 11:36:08 +01:00
Dominik Sokal 74d6b37b00 remove jsconfig.json 2019-01-17 16:09:39 +01:00
Dominik Sokal d8049d8b83 Get rid of eslint and fix tslint errors (#9)
* get rid of eslint

* fix tslint errors

* run lint before building turtle
2019-01-17 16:00:33 +01:00
99 changed files with 5661 additions and 1517 deletions

No files matched your search

+4 -8
View File
@@ -1,25 +1,21 @@
version: 2
machine:
node:
# Circle will use node v4.0.0
version: 4.0.0
jobs:
build:
docker:
- image: circleci/node:10.15.0
- image: circleci/node:8.12
steps:
- checkout
- run: yarn
- run: yarn lint
- run: yarn build
test:
docker:
- image: circleci/node:10.15.0
- image: circleci/node:8.12
steps:
- checkout
- run: yarn
- run: ./test-ci-unit.sh
- run: ./scripts/testUnitCI.sh
workflows:
version: 2
-21
View File
@@ -1,21 +0,0 @@
{
"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,3 +6,6 @@
shell.apk
node_modules
.secrets*
*.swp
*.swo
+9
View File
@@ -0,0 +1,9 @@
{
"git": {
"commit": false,
"tag": true,
"push": true,
"requireUpstream": false,
"tagName": "v${version}"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"npm": {
"tag": "latest"
},
"git": {
"requireUpstream": false,
"tagName": "v${version}"
},
"github": {
"release": true
}
}
+60 -3
View File
@@ -5,19 +5,76 @@ 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]
### Added
- `--config` parameter for turtle-cli commands, which allows to specify a custom path to app.json file
## [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 react-native-screens@1.0.0-alpha.22
- Update Android shell app for SDK32 that includes `[email protected]`.
## [0.5.0] - 2019-01-16
### Added
- Turtle version is added to onFinish and onError build messages.
- 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.
### 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
@@ -0,0 +1,33 @@
# 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!
+13 -9
View File
@@ -36,30 +36,34 @@ 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-3859397.zip
ENV ANDROID_SDK_URL https://dl.google.com/android/repository/sdk-tools-linux-4333796.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/25.0.0/:${PATH}
ENV PATH ${ANDROID_HOME}/build-tools/28.0.3/:${PATH}
RUN yes | sdkmanager --licenses > /dev/null
# Install Android SDK components
RUN sdkmanager \
"platform-tools" \
RUN yes | sdkmanager \
"platforms;android-23" \
"build-tools;25.0.0" \
"platforms;android-24" \
"platforms;android-25" \
"platforms;android-26" \
"platforms;android-27" \
"platforms;android-28"
RUN yes | sdkmanager "platform-tools"
RUN yes | sdkmanager "build-tools;28.0.3"
RUN yes | sdkmanager \
"extras;android;m2repository" \
"extras;google;m2repository"
"extras;google;m2repository" \
"extras;google;google_play_services"
# Install Android NDK
ENV ANDROID_NDK_VERSION android-ndk-r10e
+18 -4
View File
@@ -1,25 +1,39 @@
# 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)
[![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/)
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
- [Changelog](CHANGELOG.md)
- [Contributing to Turtle CLI](CONTRIBUTING.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
## Glossary
- **Turtle** - script which takes a shell app, fills it with user's code and produces a standalone app
- **Shell app** — precompiled (in case of iOS builds) Expo app which doesn't have user's code
- **Standalone app** - .ipa/.apk file, a user uploads standalone apps to the Google Play Store/Apple App Store
## Installation
[Installation instructions and documentation here.](https://docs.expo.io/versions/latest/distribution/turtle-cli)
## Usage example
See the [expo/turtle-cli-example](https://github.com/expo/turtle-cli-example) repository to learn how to use Turtle CLI in order to build Expo standalone apps on your CI.
## Do you need help?
- Make sure you've read [the Expo Docs](https://docs.expo.io/), especially [Building Standalone Apps on Your CI
](https://docs.expo.io/versions/latest/distribution/turtle-cli) and [Building Standalone Apps](https://docs.expo.io/versions/latest/distribution/building-standalone-apps) guides.
- Search for existing GitHub issues [in this repository](https://github.com/expo/turtle/issues) and in [expo/expo](https://github.com/expo/expo/issues) repository.
- Ask for help in **one** of the following ways:
* create [a GitHub issue](https://github.com/expo/turtle/issues/new).
* create a new topic on [Expo Forums](https://forums.expo.io/).
* ask a question on [Expo Developers Slack](http://slack.expo.io).
## License
The Turtle CLI source code is made available under the [MIT license](LICENSE).
Some of the dependencies are licensed differently, with the BSD license, for example.
+1 -1
View File
@@ -9,6 +9,6 @@
"^.+\\.ts$": "ts-jest"
},
"testRegex": "(test|spec)\\.ts$",
"moduleFileExtensions": ["ts", "js"],
"moduleFileExtensions": ["ts", "js", "json"],
"verbose": true
}
+1 -1
View File
@@ -9,6 +9,6 @@
"^.+\\.ts$": "ts-jest"
},
"testMatch": [ "**/__smoke__/*.test.ts" ],
"moduleFileExtensions": ["ts", "js"],
"moduleFileExtensions": ["ts", "js", "json"],
"verbose": true
}
+1 -1
View File
@@ -9,6 +9,6 @@
"^.+\\.ts$": "ts-jest"
},
"testMatch": [ "**/__tests__/*.ts" ],
"moduleFileExtensions": ["ts", "js"],
"moduleFileExtensions": ["ts", "js", "json"],
"verbose": true
}
-11
View File
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "es2017",
"allowSyntheticDefaultImports": false,
"baseUrl": "./",
"paths": {
"turtle/*": ["src/*"],
}
},
"exclude": ["node_modules", "dist", "build"]
}
+21 -17
View File
@@ -1,23 +1,22 @@
{
"name": "turtle-cli",
"version": "0.5.1",
"version": "0.5.13",
"description": "The command-line tool for building standalone Expo apps",
"preferGlobal": true,
"scripts": {
"start": "EXPENV_OVERRIDES=local-common,local expenv gulp",
"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 expenv gulp watch:source",
"watch": "EXPENV_OVERRIDES=local-common,local,local-queue expenv gulp watch:source",
"bin:turtle": "./bin/turtle.js",
"lint": "eslint src",
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"prepare": "yarn build",
"build": "gulp build:server",
"build:production": "./scripts/build.sh",
"build:android-tarball:local": "./scripts/android/buildAndroidTarballLocally.sh",
"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",
"secrets:init-private": "./scripts/initPrivateSecrets.sh",
"set-workingdir:remote": "./scripts/setWorkingdirMode.sh remote",
"set-workingdir:local": "./scripts/setWorkingdirMode.sh local",
"docker:pull": "retry5 gcloud docker -- pull gcr.io/exponentjs/turtle-android:latest",
"docker:pull:tag": "retry5 gcloud docker -- pull gcr.io/exponentjs/turtle-android:${TAG}",
"docker:build": "yarn build:production android && gcloud docker -- build -t gcr.io/exponentjs/turtle-android:latest -t gcr.io/exponentjs/turtle-android:${TAG} .",
@@ -29,12 +28,13 @@
"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",
"secrets:init-private": "./scripts/initPrivateSecrets.sh",
"release": "./scripts/release.sh",
"release:beta": "./scripts/releaseBeta.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 expenv babel-node scripts/setConfigurations.js",
"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"
},
@@ -60,6 +60,9 @@
"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",
@@ -75,13 +78,9 @@
"@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",
@@ -92,14 +91,16 @@
"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",
"tslint": "^5.10.0",
"typescript": "^3.0.1",
"typescript-eslint-parser": "^21.0.2"
"typescript": "^3.0.1"
},
"dependencies": {
"@expo/spawn-async": "^1.4.0",
"@google-cloud/logging-bunyan": "^0.9.5",
"async-retry": "^1.2.1",
"aws-sdk": "^2.226.1",
"bunyan": "^1.8.12",
@@ -123,6 +124,9 @@
"tar": "^4.4.6",
"uuid": "^3.2.1",
"which": "^1.3.1",
"xdl": "52.0.14-alpha.6+371be041"
"xdl": "53.1.6-alpha.2"
},
"optionalDependencies": {
"@expo/traveling-fastlane-darwin": "^1.8.1"
}
}
+7 -1
View File
@@ -12,7 +12,13 @@ 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)
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
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
+8 -1
View File
@@ -2,6 +2,8 @@
set -xeo pipefail
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
if [ -z "$1" ]; then
echo "Please specify a platform to build"
exit 1
@@ -11,7 +13,12 @@ PLATFORM=$1
yarn
yarn run build
yarn init-workingdir:remote-$PLATFORM
if [ "$PLATFORM" = "android" ]; then
$ROOT_DIR/scripts/android/fetchRemoteAndroidTarball.sh
elif [ "$PLATFORM" = "ios" ]; then
$ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
fi
if [[ -z "${TARBALL}" ]]; then
echo "TARBALL is not set, building tarball skipped"
+9 -6
View File
@@ -1,9 +1,12 @@
#!/bin/bash
yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses > /dev/null
$ANDROID_HOME/tools/bin/sdkmanager \
"platform-tools" \
"platforms;android-23" \
"build-tools;25.0.0" \
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 \
"extras;android;m2repository" \
"extras;google;m2repository"
"extras;google;m2repository" \
"extras;google;google_play_services"
-22
View File
@@ -1,22 +0,0 @@
#!/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
+7 -5
View File
@@ -1,5 +1,10 @@
#!/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
@@ -12,12 +17,9 @@ 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
cat << EOF > $TURTLE_DIR/.secrets.local-queue
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
+7 -1
View File
@@ -12,7 +12,13 @@ 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)
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
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
@@ -0,0 +1,11 @@
#!/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
@@ -0,0 +1,7 @@
#!/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
+86
View File
@@ -0,0 +1,86 @@
#!/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
File renamed without changes.
+2 -1
View File
@@ -18,7 +18,8 @@ export API_PORT="443"
export REDIS_URL="noop"
export REDIS_CONFIG_URL="noop"
yarn init-workingdir:remote-ios
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
$ROOT_DIR/scripts/ios/fetchRemoteIosTarball.sh
yarn test:smoke:ios
smoke_result=$?
File renamed without changes.
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/android-shell-builder-cb03fdc12dc74f699a6d3f22219cbcd30e01ac46.tar.gz
s3://exp-artifacts/android-shell-builder-a4bd3bd26b1b31bdfa66c39b6b4d0d1d138c32fc.tar.gz
+1
View File
@@ -0,0 +1 @@
s3://exp-artifacts/ios-expo-client-15bb883c22173d5b01df19421580170e776041d0.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/ios-shell-builder-sdk-30-7d017a96d00fbf18ee7e5581ea47528b9cc78afd.tar.gz
s3://exp-artifacts/ios-shell-builder-sdk-30-6863276e6b9605e31228c845a047387571959e9b.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/ios-shell-builder-sdk-latest-56824ecea6ba10f5a9d8991545a1c47ee3284f5f.tar.gz
s3://exp-artifacts/ios-shell-builder-sdk-latest-e1a8bd0ab2e1eba546792c0ffc282b5b202ccc39.tar.gz
+1 -1
View File
@@ -1 +1 @@
s3://exp-artifacts/ios-shell-builder-sdk-latest-e3855783ed0618f4dcf326dc353c6e2951e05c49.tar.gz
s3://exp-artifacts/ios-shell-builder-sdk-latest-f6e1921542b2e26b4b9221dd2eddb74ea5cfdf74.tar.gz
+4 -5
View File
@@ -3,14 +3,13 @@ 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(() => {
console.log('Faking uploading build');
logger.debug('Faking uploading build');
return 'legit_path';
}),
}));
@@ -68,11 +67,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;
+29 -23
View File
@@ -1,16 +1,22 @@
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 State {
metrics: Array<Metric>;
interface IState {
metrics: IMetric[];
registeredMetrics: {
[key: string]: MetricConfiguration;
[key: string]: IMetricConfiguration;
};
intervalId: NodeJS.Timer | null;
}
@@ -19,7 +25,7 @@ const SUCCESS_SUFFIX = '.success';
const FAIL_SUFFIX = '.fail';
const CHUNK_SIZE = 20;
const state: State = {
const state: IState = {
metrics: [],
registeredMetrics: {},
intervalId: null,
@@ -46,7 +52,7 @@ export function addMetricData({
success = true,
explicitName = false,
extraDimensions = [],
}: MetricData) {
}: IMetricData) {
if (config.cloudwatch.disabled) {
return;
}
@@ -57,7 +63,7 @@ export function addMetricData({
return;
}
const { unit } = registeredMetric;
const metric: Metric = {
const metric: IMetric = {
MetricName: metricName,
Unit: unit,
Value: value,
@@ -114,7 +120,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);
@@ -124,7 +130,7 @@ function _dumpMetrics() {
state.metrics = [];
}
function _addEmptyMetrics(metrics: Array<Metric>) {
function _addEmptyMetrics(metrics: IMetric[]) {
const seenMetrics = metrics.reduce((acc, i) => {
if (!acc.has(i.MetricName)) {
acc.add(i.MetricName);
@@ -134,44 +140,44 @@ function _addEmptyMetrics(metrics: Array<Metric>) {
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: Array<Metric>) {
function _formatMetrics(metrics: IMetric[]) {
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: Array<Metric>) {
function _reduceMetrics(metrics: IMetric[]) {
const grouped = _.groupBy(metrics, GROUP_HASH);
return _.reduce(
grouped,
(acc: Array<Metric>, val: any) => {
const { MetricName: key } = val[0] as Metric;
(acc: IMetric[], val: any) => {
const { MetricName: key } = val[0] as IMetric;
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: Array<MetricsChunk>) {
function _pushMetrics(metrics: IMetricsChunk[]) {
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 { Metric } from 'turtle/aws/cloudwatch/types';
import { IMetric } from 'turtle/aws/cloudwatch/types';
export function sum(metrics: Array<Metric>) {
export function sum(metrics: IMetric[]) {
if (metrics.length === 0) {
return [];
}
const reduced = metrics.reduce((acc: Metric | null, i: Metric) => {
const reduced = metrics.reduce((acc: IMetric | null, i: IMetric) => {
let result = acc;
if (!result) {
result = {...i};
@@ -16,12 +16,12 @@ export function sum(metrics: Array<Metric>) {
return [reduced];
}
export function avg(metrics: Array<Metric>) {
export function avg(metrics: IMetric[]) {
if (!metrics.length) {
return [];
}
const metric = sum(metrics)[0] as Metric;
const metric = sum(metrics)[0] as IMetric;
metric.Value = metric.Value / metrics.length;
return [metric];
}
+3 -3
View File
@@ -1,11 +1,11 @@
import { Metric, MetricWithStats } from 'turtle/aws/cloudwatch/types';
import { IMetric, MetricWithStats } from 'turtle/aws/cloudwatch/types';
export default function calculateStatistics(metrics: Array<Metric>) {
export default function calculateStatistics(metrics: IMetric[]) {
if (!metrics.length) {
return [];
}
const reduced = metrics.reduce((acc: MetricWithStats | null, i: Metric) => {
const reduced = metrics.reduce((acc: MetricWithStats | null, i: IMetric) => {
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 Metric {
export interface IMetric {
MetricName: string;
Unit: string;
Value: number;
@@ -11,8 +11,8 @@ export interface Metric {
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;
}
} & Metric;
},
} & IMetric;
export interface MetricData {
export interface IMetricData {
name: string;
value: number;
success?: boolean;
@@ -32,17 +32,17 @@ export interface MetricData {
extraDimensions?: any;
}
export interface MetricConfiguration {
export interface IMetricConfiguration {
unit: string;
statistics?: boolean;
reducer?: any;
addEmpty?: boolean;
dimensions?: any;
};
}
export type MetricRegistrationObject = { metricName: string, explicitName?: boolean } & MetricConfiguration;
export type MetricRegistrationObject = { metricName: string, explicitName?: boolean } & IMetricConfiguration;
export interface MetricsChunk {
export interface IMetricsChunk {
Namespace: string;
MetricData: Array<Metric>;
};
MetricData: IMetric[];
}
+3 -3
View File
@@ -1,10 +1,10 @@
import AWS from 'aws-sdk';
import _ from 'lodash';
import fs from 'fs-extra';
import _ from 'lodash';
import config from 'turtle/config';
interface FileUploadParams {
interface IFileUploadParams {
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 }: FileUploadParams) {
export async function uploadFile({ bucketName = config.s3.bucket, key, srcPath }: IFileUploadParams) {
return await s3
.upload({
Bucket: bucketName,
+5 -5
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;
});
@@ -62,12 +62,12 @@ export async function changeMessageVisibility(priority: string, receiptHandle: s
}
// Every VISIBILITY_TIMEOUT_SEC / 3 seconds we are telling AWS SQS
// that we're still proccessing the message, so it does not
// that we're still processing the message, so it does not
// send the build job to another turtle agent
export function changeMessageVisibilityRecurring(priority: string, receiptHandle: string, jobId: string) {
return setInterval(() => {
if (getCurrentJobId() === jobId) {
changeMessageVisibility(priority, receiptHandle).catch(err => {
changeMessageVisibility(priority, receiptHandle).catch((err) => {
logger.warn('Error at change msg visibility', err);
});
}
+2
View File
@@ -0,0 +1,2 @@
export class ErrorWithCommandHelp extends Error {
}
+2
View File
@@ -0,0 +1,2 @@
export class ErrorWithProgramHelp extends Error {
}
+9 -8
View File
@@ -1,11 +1,11 @@
import _ from 'lodash';
import fs from 'fs-extra';
import _ from 'lodash';
import uuid from 'uuid';
import builder from 'turtle/builders/android';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/errors';
import { PLATFORMS } from 'turtle/constants';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { createBuilderAction } from 'turtle/bin/utils/builder';
import builder from 'turtle/builders/android';
import { PLATFORMS } from 'turtle/constants';
export default (program: any, setCommonCommandOptions: any) => {
const command = program.command('build:android [project-dir]');
@@ -14,11 +14,12 @@ 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({
@@ -29,7 +30,7 @@ export default (program: any, setCommonCommandOptions: any) => {
builder,
platform: PLATFORMS.ANDROID,
os: ['darwin', 'linux'],
})
}),
);
};
@@ -57,7 +58,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;
+6 -5
View File
@@ -1,5 +1,5 @@
import { default as buildIOS } from 'turtle/bin/commands/build/ios';
import { default as buildAndroid } from 'turtle/bin/commands/build/android';
import { default as buildIOS } from 'turtle/bin/commands/build/ios';
function createCommand(builder: any) {
return (program: any) => {
@@ -15,15 +15,16 @@ 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)',
)
.option('-c --config <config-file>', 'specify a path to app.json');
}
export default {
ios: createCommand(buildIOS),
android: createCommand(buildAndroid),
}
};
+10 -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 builder from 'turtle/builders/ios';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/errors';
import { IOS, PLATFORMS } from 'turtle/constants';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { createBuilderAction } from 'turtle/bin/utils/builder';
import builder from 'turtle/builders/ios';
import { IOS, PLATFORMS } from 'turtle/constants';
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,7 +68,8 @@ 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
@@ -1,2 +0,0 @@
export class ErrorWithProgramHelp extends Error {}
export class ErrorWithCommandHelp extends Error {}
+5 -4
View File
@@ -1,9 +1,10 @@
import _ from 'lodash';
import logger from 'turtle/logger';
import { ErrorWithCommandHelp } from 'turtle/bin/commands/ErrorWithCommandHelp';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
import setup from 'turtle/bin/setup/setup';
import { ErrorWithCommandHelp, ErrorWithProgramHelp } from 'turtle/bin/commands/errors';
import { PLATFORMS } from 'turtle/constants';
import logger from 'turtle/logger';
const l = logger.withFields({ buildPhase: 'setting up environment' });
@@ -16,7 +17,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));
};
@@ -30,7 +31,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 path from 'path';
import os from 'os';
import path from 'path';
import _ from 'lodash';
@@ -16,8 +16,8 @@ const resolveEnv = () => {
};
const currentEnv = resolveEnv();
const isLocal = (env: string) => env === 'local';
const isStaging = (env: string) => env === 'staging';
const isLocal = (envName: string) => envName === 'local';
const isStaging = (envName: string) => envName === 'staging';
const apiConfig = {
protocol: 'https',
+18 -14
View File
@@ -1,11 +1,13 @@
import { LoggerDetach, ModuleVersion } from 'xdl';
import path from 'path';
import program, { Command } from 'commander';
import _ from 'lodash';
import fs from 'fs-extra';
import { LoggerDetach, ModuleVersion } from 'xdl';
import logger from 'turtle/logger';
import * as commands from 'turtle/bin/commands';
import logger from 'turtle/logger';
const { name, version } = require('../../package.json');
const { name, version } = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
const ModuleVersionChecker = ModuleVersion.createModuleVersionChecker(name, version);
@@ -15,13 +17,15 @@ Command.prototype.asyncAction = function asyncAction(asyncFn: (...asyncFnArgs: a
return this.action(async (...args) => {
try {
await checkForUpdateAsync();
} catch (e) {}
} catch (e) {
logger.warn('Failed to check for turtle-cli update.');
}
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);
});
@@ -29,22 +33,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 commands = program.commands.reduce((acc: Array<string>, command: any) => {
acc.push(command['_name']);
const alias = command['_alias'];
const commandNames = program.commands.reduce((acc: string[], command: any) => {
acc.push(command._name);
const alias = command._alias;
if (alias) {
acc.push(alias);
}
return acc;
}, []);
if (!_.includes(commands, subCommand)) {
if (!commandNames.includes(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 {
@@ -60,7 +64,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`,
);
}
}
+12 -12
View File
@@ -2,20 +2,20 @@ import path from 'path';
import util from 'util';
import fs from 'fs-extra';
import { ExponentTools } from 'xdl';
import _which from 'which';
import { ExponentTools } from 'xdl';
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 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';
const which = util.promisify(_which);
const REQUIRED_TOOLS: Array<IToolDefinition> = [
const REQUIRED_TOOLS: IToolDefinition[] = [
{
command: 'bash',
missingDescription: 'Please install bash',
@@ -31,13 +31,13 @@ const REQUIRED_TOOLS: Array<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: Array<string>) {
function _alterPath(newPaths: 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];
}
+5 -5
View File
@@ -5,12 +5,12 @@ 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';
const ANDROID_SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip';
const LOGGER_FIELDS = { buildPhase: 'setting up environment' };
const l = logger.withFields(LOGGER_FIELDS);
@@ -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');
@@ -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/25.0.0'),
path.join(androidSdkDir, 'build-tools/28.0.3'),
];
}
+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: Array<IToolDefinition> = [
const REQUIRED_TOOLS: IToolDefinition[] = [
{
command: 'bash',
missingDescription: 'Please install bash',
@@ -24,13 +24,13 @@ const REQUIRED_TOOLS: Array<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 setupIos from 'turtle/bin/setup/ios';
import setupAndroid from 'turtle/bin/setup/android';
import setupIos from 'turtle/bin/setup/ios';
import { PLATFORMS } from 'turtle/constants';
export default async function setup(platform: string, sdkVersion?: string) {
+14 -11
View File
@@ -1,30 +1,34 @@
import path from 'path';
import { ExponentTools } from 'xdl';
import fs from 'fs-extra';
import tar from 'tar';
import config from 'turtle/config';
import logger from 'turtle/logger';
import { IToolDefinition, ensureToolsAreInstalled } from 'turtle/bin/setup/utils/toolsDetector';
import { ExponentTools } from 'xdl';
import download from 'turtle/bin/setup/utils/downloader';
import { ensureToolsAreInstalled, IToolDefinition } from 'turtle/bin/setup/utils/toolsDetector';
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
import config from 'turtle/config';
import logger from 'turtle/logger';
const l = logger.withFields({ buildPhase: 'setting up environment' });
interface IShellAppFormaters {
formatShellAppDirectory: (sdkVersion: string) => string;
formatShellAppDirectory: (config: IShellAppDirectoryConfig) => string;
formatShellAppTarballUriPath: (sdkMajorVersion: string) => string;
}
type PostDownloadAction = (workingdir: string) => void;
export async function checkSystem(requiredTools: Array<IToolDefinition>) {
export async function checkSystem(requiredTools: IToolDefinition[]) {
await ensureToolsAreInstalled(requiredTools);
}
export async function ensureShellAppIsPresent(sdkVersion: string, formatters: IShellAppFormaters, postDownloadAction?: PostDownloadAction) {
const workingdir = formatters.formatShellAppDirectory(sdkVersion);
export async function ensureShellAppIsPresent(
sdkVersion: string,
formatters: IShellAppFormaters,
postDownloadAction?: PostDownloadAction,
) {
const workingdir = formatters.formatShellAppDirectory({ sdkVersion });
if (await fs.pathExists(workingdir)) {
return;
}
@@ -45,12 +49,11 @@ 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);
+2 -3
View File
@@ -1,9 +1,9 @@
import { URL } from 'url';
import fs from 'fs';
import { URL } from 'url';
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,7 +38,6 @@ 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: Array<IToolDefinition>) {
export async function ensureToolsAreInstalled(tools: IToolDefinition[]) {
let isAnyToolMissing = false;
for (const { command, missingDescription, testFn } of tools) {
try {
+12 -8
View File
@@ -1,11 +1,12 @@
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,
@@ -29,7 +30,9 @@ 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 = {
@@ -44,10 +47,11 @@ 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,
@@ -56,7 +60,7 @@ export function createBuilderAction({
publicUrl: cmd.publicUrl,
};
const appJSON = await ProjectUtils.loadAppJSON(projectDirArg);
const appJSON = await ProjectUtils.loadAppJSON(projectDirArg, cmd.config);
const sdkVersion = _.get(appJSON, 'expo.sdkVersion');
await setup(platform, sdkVersion);
const credentials = await prepareCredentials(cmd);
+10 -5
View File
@@ -1,12 +1,17 @@
import path from 'path';
import fs from 'fs-extra';
import path from 'path';
export async function loadAppJSON(projectDirArg: string) {
const projectDir = resolveAbsoluteDir(projectDirArg);
const appJSONPath = path.join(projectDir, 'app.json');
export async function loadAppJSON(projectDirArg: string, config: string | null) {
let appJSONPath;
if (config) {
appJSONPath = resolveAbsoluteDir(config);
} else {
const projectDir = resolveAbsoluteDir(projectDirArg);
appJSONPath = path.join(projectDir, 'app.json');
}
const appJSONExists = await fs.pathExists(appJSONPath);
if (!appJSONExists) {
throw new Error(`Couldn't find app.json in ${projectDir} directory.`);
throw new Error(`Couldn't find app.json.`);
} else {
return require(appJSONPath);
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { User as UserManager } from 'xdl';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/errors';
import { ErrorWithProgramHelp } from 'turtle/bin/commands/ErrorWithProgramHelp';
interface IUserData {
username?: string;
+17
View File
@@ -0,0 +1,17 @@
class BuildError extends Error {
public reason: BuildErrorReason;
constructor(reason: BuildErrorReason, message: string) {
super(message);
this.name = 'BuildError';
this.reason = reason;
}
}
enum BuildErrorReason {
SESSION_EXPIRED = 'session-expired',
PROVISIONING_PROFILE_MISSING = 'provisioning-profile-missing',
}
export default BuildError;
export { BuildErrorReason };
+15 -9
View File
@@ -2,19 +2,21 @@ import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import { AndroidShellApp, ImageUtils } from 'xdl';
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 { uploadBuildToS3 } from 'turtle/builders/utils/uploader';
import { ensureCanBuildSdkVersion } from 'turtle/builders/utils/version';
import config from 'turtle/config';
import logger from 'turtle/logger';
import { IAndroidCredentials, IJob, IJobResult } from 'turtle/job';
import { formatShellAppDirectory } from 'turtle/builders/utils/android/workingdir';
import logger from 'turtle/logger';
export default async function buildAndroid(jobData: IJob): Promise<IJobResult> {
await ensureCanBuildSdkVersion(jobData);
const credentials = await getOrCreateCredentials(jobData);
const apkFilePath = await runShellAppBuilder(jobData, credentials);
const randomHex = uuidv4().replace(/-/g, '');
@@ -25,7 +27,10 @@ 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),
},
});
@@ -57,20 +62,21 @@ async function runShellAppBuilder(
const outputFilePath = path.join(temporaryFilesRoot, `shell-signed-${jobData.id}.apk`);
const { manifest: { sdkVersion: sdkVersionFromManifest = null } = {}, sdkVersion: sdkVersionFromJob } = jobData;
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
const workingDir = formatShellAppDirectory(sdkVersion);
const { config: jobConfig, manifest, sdkVersion: sdkVersionFromJob } = jobData;
const sdkVersion = _.get(manifest, 'sdkVersion', sdkVersionFromJob);
const workingDir = formatShellAppDirectory({ sdkVersion });
try {
await AndroidShellApp.createAndroidShellAppAsync({
url: commonUtils.getExperienceUrl(jobData),
sdkVersion: _.get(jobData, 'manifest.sdkVersion') || jobData.sdkVersion,
sdkVersion,
keystore: tempKeystorePath,
manifest,
alias: credentials.keystoreAlias,
keystorePassword: credentials.keystorePassword,
keyPassword: credentials.keyPassword,
privateConfigFile: tempShellAppConfigPath,
releaseChannel: jobData.config.releaseChannel,
releaseChannel: jobConfig.releaseChannel,
workingDir,
outputFile: outputFilePath,
});
+1 -1
View File
@@ -1,5 +1,5 @@
import buildIOS from './ios';
import buildAndroid from './android';
import buildIOS from './ios';
export default {
ios: buildIOS,
+8 -9
View File
@@ -1,13 +1,13 @@
import _ from 'lodash';
import { IosIPABuilder as createIPABuilder } from 'xdl';
import runShellAppBuilder from 'turtle/builders/utils/ios/shellAppBuilder';
import { IContext } from 'turtle/builders/ios/context';
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 logger from 'turtle/logger/index';
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/index';
export default async function buildArchive(ctx: IContext, job: IJob) {
let keychainInfo;
@@ -33,12 +33,12 @@ async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, m
l.info('building and signing IPA');
const {
credentials: { provisioningProfile, certPassword, teamId, password },
config: { bundleIdentifier: _bundleIdentifierFromConfig },
credentials: { provisioningProfile, certPassword, teamId },
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);
@@ -51,9 +51,8 @@ async function buildAndSignIPA(ctx: IContext, job: IJob, keychainPath: string, m
certPath: ctx.tempCertPath,
certPassword,
teamID: teamId,
password,
bundleIdentifier,
workingDir: ctx.workingDir,
workspacePath: ctx.workspacePath,
manifest,
});
await ipaBuilder.build();
+66 -17
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;
@@ -22,18 +22,26 @@ export interface IContext {
tempCertPath: string;
uploadPath: string;
workingDir: string;
workspacePath: string;
applicationFilesSrc: string;
}
const { DEFAULT_EXPOKIT_WORKSPACE_NAME } = IosShellApp;
const { EXPOKIT_APP, EXPONENT_APP } = IosShellApp;
const { BUILD_TYPES } = IOS;
export function createBuilderContext(job: IJob): IContext {
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 workingDir = formatShellAppDirectory(sdkVersion);
const sdkVersion = sdkVersionFromJob || sdkVersionFromManifest;
const workingDir = formatShellAppDirectory({ sdkVersion, buildType: buildType! });
const context: any = {
appDir: join(config.directories.temporaryFilesRoot, appUUID),
@@ -44,29 +52,68 @@ 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');
if (buildType === BUILD_TYPES.SIMULATOR) {
context.outputPath = join(context.appDir, 'archive.tar.gz');
context.uploadPath = join(context.appDir, 'archive.tar.gz');
context.archiveDir = join(
context.baseArchiveDir,
'Release',
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`
if (buildType === BUILD_TYPES.CLIENT) {
context.applicationFilesSrc = join(
workingDir,
'expo-client-build',
'**',
'*',
);
} else if (buildType === BUILD_TYPES.ARCHIVE) {
} else {
context.applicationFilesSrc = join(
workingDir,
'shellAppBase-builds',
buildType as string,
'**',
'*',
);
}
if (buildType === BUILD_TYPES.ARCHIVE) {
context.outputPath = join(context.appDir, 'archive.xcarchive');
context.uploadPath = join(context.buildDir, 'archive.ipa');
context.archiveDir = join(
context.baseArchiveDir,
'Release',
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.xcarchive`,
`${EXPOKIT_APP}.xcarchive`,
'Products',
'Applications',
`${DEFAULT_EXPOKIT_WORKSPACE_NAME}.app`
`${EXPOKIT_APP}.app`,
);
context.workspacePath = path.join(
workingDir,
'shellAppWorkspaces',
'ios',
'default',
`${EXPOKIT_APP}.xcworkspace`,
);
} else if (buildType === BUILD_TYPES.CLIENT) {
context.outputPath = join(context.appDir, 'archive.xcarchive');
context.uploadPath = join(context.buildDir, 'archive.ipa');
context.archiveDir = join(
context.baseArchiveDir,
`${EXPONENT_APP}.xcarchive`,
'Products',
'Applications',
`${EXPONENT_APP}.app`,
);
context.workspacePath = path.join(
workingDir,
'ios',
`${EXPONENT_APP}.xcworkspace`,
);
} else if (buildType === BUILD_TYPES.SIMULATOR) {
context.outputPath = join(context.appDir, 'archive.tar.gz');
context.uploadPath = join(context.appDir, 'archive.tar.gz');
context.archiveDir = join(
context.baseArchiveDir,
'Release',
`${EXPOKIT_APP}.app`,
);
}
@@ -74,7 +121,9 @@ 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);
}
+14 -32
View File
@@ -1,24 +1,23 @@
import path from 'path';
import fs from 'fs-extra';
import pick from 'lodash/pick';
import { ExponentTools } from 'xdl';
import buildArchive from 'turtle/builders/ios/archive';
import { createBuilderContext, IContext } from 'turtle/builders/ios/context';
import buildSimulator from 'turtle/builders/ios/simulator';
import { logErrorOnce } from 'turtle/builders/utils/common';
import prepareAdHocBuildCredentials from 'turtle/builders/utils/ios/adhocBuild';
import { uploadBuildToS3 } from 'turtle/builders/utils/uploader';
import { ensureCanBuildSdkVersion } from 'turtle/builders/utils/version';
import config from 'turtle/config';
import { IOS, PLATFORMS } from 'turtle/constants/index';
import { IOS } from 'turtle/constants/index';
import { IJob, IJobResult } from 'turtle/job';
const { BUILD_TYPES } = IOS;
let SUPPORTED_SDK_VERSIONS: Array<number>;
export default async function iosBuilder(job: IJob): Promise<IJobResult> {
await ensureCanBuildSdkVersion(job);
if (job.config.buildType !== BUILD_TYPES.CLIENT) {
await ensureCanBuildSdkVersion(job);
}
const ctx = createBuilderContext(job);
@@ -26,10 +25,15 @@ export default async function iosBuilder(job: IJob): Promise<IJobResult> {
await initBuilder(ctx);
const { buildType } = job.config;
if (buildType === BUILD_TYPES.SIMULATOR) {
await buildSimulator(ctx, job);
} else if (buildType === BUILD_TYPES.ARCHIVE) {
if (buildType === BUILD_TYPES.CLIENT) {
await prepareAdHocBuildCredentials(job);
}
if ([BUILD_TYPES.ARCHIVE, BUILD_TYPES.CLIENT].includes(buildType!)) {
await buildArchive(ctx, job);
} else if (buildType === BUILD_TYPES.SIMULATOR) {
await buildSimulator(ctx, job);
} else {
throw new Error(`Unsupported iOS build type: ${buildType}`);
}
@@ -60,25 +64,3 @@ async function cleanup(ctx: IContext) {
const getTemporaryDirs = (ctx: IContext) =>
Object.values(pick(ctx, ['appDir', 'provisioningProfileDir']));
async function ensureCanBuildSdkVersion(job: IJob) {
if (config.builder.useLocalWorkingDir) {
return;
}
if (!SUPPORTED_SDK_VERSIONS) {
SUPPORTED_SDK_VERSIONS = await findSupportedSdkVersions();
}
const targetMajorSdkVersion = ExponentTools.parseSdkMajorVersion(job.sdkVersion || job.manifest.sdkVersion);
if (!SUPPORTED_SDK_VERSIONS.includes(targetMajorSdkVersion)) {
throw new Error(`Unsupported SDK Version!`);
}
}
async function findSupportedSdkVersions(): Promise<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)));
}
+2 -2
View File
@@ -1,7 +1,7 @@
import runShellAppBuilder from 'turtle/builders/utils/ios/shellAppBuilder';
import logger from 'turtle/logger';
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';
export default async function buildSimulator(ctx: IContext, job: IJob) {
const l = logger.withFields({ buildPhase: 'running simulator builder' });
+11 -9
View File
@@ -1,14 +1,14 @@
import path from 'path';
import uuidv4 from 'uuid/v4';
import fs from 'fs-extra';
import { AndroidKeystore } from 'xdl';
import uuidv4 from 'uuid/v4';
import { Credentials } 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) {
@@ -21,13 +21,15 @@ async function getOrCreateCredentials(jobData: IJob): Promise<IAndroidCredential
credentials.keystoreAlias = Buffer.from(jobData.experienceName).toString('base64');
const androidPackage = jobData.manifest.android.package;
await AndroidKeystore.createKeystore({
keystorePassword: credentials.keystorePassword,
keyPassword: credentials.keyPassword,
keystoreFilename,
keystoreAlias: credentials.keystoreAlias,
await Credentials.Android.createKeystore(
{
keystorePath: keystoreFilename,
keystorePassword: credentials.keystorePassword,
keyPassword: credentials.keyPassword,
keyAlias: credentials.keystoreAlias,
},
androidPackage,
});
);
credentials.keystore = (await fs.readFile(keystoreFilename)).toString('base64');
l.info('Keystore created successfully');
+5 -2
View File
@@ -1,9 +1,12 @@
import path from 'path';
import config from 'turtle/config';
import { ExponentTools } from 'xdl';
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
import config from 'turtle/config';
import { PLATFORMS } from 'turtle/constants';
export function formatShellAppDirectory(sdkVersion: string) {
export function formatShellAppDirectory({ sdkVersion }: IShellAppDirectoryConfig) {
const majorSdkVersion = ExponentTools.parseSdkMajorVersion(sdkVersion);
return config.builder.useLocalWorkingDir
? path.join(config.directories.workingDir, 'local')
+1 -1
View File
@@ -1,8 +1,8 @@
import * as url from 'url';
import config from 'turtle/config';
import logger from 'turtle/logger';
import { IJob } from 'turtle/job';
import logger from 'turtle/logger';
export function getExperienceUrl(job: IJob) {
const { experienceName, config: jobConfig } = job;
+4 -4
View File
@@ -18,15 +18,15 @@ export async function resizeIconWithSharpAsync(iconSizePx: number, iconFilename:
await fs.writeFile(filename, buffer);
}
export async function getImageDimensionsWithSharpAsync(basename: string, dirname: string) {
const filename = path.join(dirname, basename);
export async function getImageDimensionsWithSharpAsync(dirname: string, filename: string) {
const filepath = path.join(dirname, filename);
// PLEASE DON'T REMOVE THE FOLLOWING LINE (sharp caches files taking path as the cache key)
sharp.cache(false);
try {
const meta = await sharp(filename).metadata();
return [meta.width, meta.height];
const { width, height } = await sharp(filepath).metadata();
return { width, height };
} catch (e) {
return null;
}
+99
View File
@@ -0,0 +1,99 @@
import path from 'path';
import spawnAsync from '@expo/spawn-async';
import fs from 'fs-extra';
import isEmpty from 'lodash/isEmpty';
import { IosCodeSigning } from 'xdl';
import * as sqs from 'turtle/aws/sqs';
import BuildError, { BuildErrorReason } from 'turtle/builders/BuildError';
import { UPDATE_CREDENTIALS } from 'turtle/constants/build';
import { IJob } from 'turtle/job';
import logger from 'turtle/logger';
import { isOffline } from 'turtle/turtleContext';
// tslint:disable-next-line:no-var-requires
const travelingFastlane = process.platform === 'darwin' ? require('@expo/traveling-fastlane-darwin')() : null;
async function prepareAdHocBuildCredentials(job: IJob) {
if (process.platform !== 'darwin') {
throw new Error('This function should be called only on macOS!');
}
const { bundleIdentifier } = job.config;
const { certP12, certPassword, teamId, appleSession, udids } = job.credentials;
const certSerialNumber = IosCodeSigning.findP12CertSerialNumber(certP12, certPassword);
const args = [
teamId,
udids!.join(','),
bundleIdentifier,
certSerialNumber || '__last__',
];
try {
const credentials = await runFastlaneAction(
travelingFastlane.manageAdHocProvisioningProfile,
args,
{ env: { FASTLANE_SESSION: appleSession } },
);
if (isEmpty(credentials)) {
if (!job.credentials.provisioningProfile) {
// a user has generated the provisioning profile for his app, but he probably deleted it from Expo servers, ...
throw new BuildError(
BuildErrorReason.PROVISIONING_PROFILE_MISSING,
`Provisioning profile is missing on Expo servers.`,
);
}
return;
}
logger.info('New ad hoc provisioning profile successfully created');
job.credentials.provisioningProfile = credentials.provisioningProfile;
if (isOffline()) {
const provisioningProfilePath = path.join(job.projectDir, `adhoc.mobileprovision`);
await fs.writeFile(provisioningProfilePath, Buffer.from(credentials.provisioningProfile, 'base64'));
logger.info(`Saved provisioning profile to ${provisioningProfilePath}`);
} else {
await sqs.sendMessage(job.id, UPDATE_CREDENTIALS, {
provisioningProfileId: credentials.provisioningProfileId,
provisioningProfile: credentials.provisioningProfile,
});
logger.info('Ad Hoc provisioning profile sent to storage successfully');
}
} catch (e) {
if (e instanceof BuildError) {
logger.error('Apple Session expired! Please authenticate again using expo-cli.');
}
throw e;
}
}
async function runFastlaneAction(action: string, args: string[], { env }: any) {
const { stderr } = await spawnAsync(
action,
args,
{
stdio: ['inherit', 'pipe', 'pipe'],
env: { ...process.env, ...env },
},
);
const { result, ...rest } = JSON.parse(stderr.trim());
if (result === 'failure') {
const { reason, rawDump, type } = rest;
let errorMsg = `Reason: ${reason}`;
if (rawDump) {
errorMsg += `, raw: ${JSON.stringify(rawDump)}`;
}
if (type === 'session-expired') {
throw new BuildError(BuildErrorReason.SESSION_EXPIRED, errorMsg);
} else {
throw new Error(errorMsg);
}
} else {
return rest;
}
}
export default prepareAdHocBuildCredentials;
+1 -2
View File
@@ -1,14 +1,13 @@
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 {
+22 -20
View File
@@ -2,30 +2,24 @@ import path from 'path';
import util from 'util';
import copy from 'copy';
import { ImageUtils, IosShellApp } from 'xdl';
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 logger from 'turtle/logger/index';
import { IContext } from 'turtle/builders/ios/context';
import { IOS } from 'turtle/constants/index';
import { IJob } from 'turtle/job';
import { PLATFORMS } from 'turtle/constants';
import logger from 'turtle/logger/index';
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 applicationFilesSrc = path.join(
ctx.workingDir,
'shellAppBase-builds',
buildType as string,
'**',
'*',
);
await copyAsync(applicationFilesSrc, ctx.baseArchiveDir);
const sdkVersion = _.get(manifest, 'sdkVersion', sdkVersionFromJob);
await copyAsync(ctx.applicationFilesSrc, ctx.baseArchiveDir);
logger.info(
{ buildPhase: 'icons setup' },
@@ -34,18 +28,26 @@ export default async function runShellAppBuilder(ctx: IContext, job: IJob): Prom
ImageUtils.setResizeImageFunction(imageHelpers.resizeIconWithSharpAsync);
ImageUtils.setGetImageDimensionsFunction(imageHelpers.getImageDimensionsWithSharpAsync);
const shellAppParams = {
url: commonUtils.getExperienceUrl(job),
sdkVersion,
action: 'configure',
type: buildType,
releaseChannel,
archivePath: ctx.archiveDir,
privateConfigData: job.config,
verbose: true,
output: ctx.outputPath,
expoSourcePath: path.join(ctx.workingDir, PLATFORMS.IOS),
expoSourcePath: path.join(ctx.workingDir, 'ios'),
manifest,
output: ctx.outputPath,
verbose: true,
};
if (buildType === IOS.BUILD_TYPES.CLIENT) {
Object.assign(shellAppParams, {
workspacePath: path.join(ctx.workingDir, 'ios'),
});
} else {
Object.assign(shellAppParams, {
url: commonUtils.getExperienceUrl(job),
releaseChannel,
sdkVersion,
});
}
logger.info({ buildPhase: 'configuring NSBundle' }, 'configuring NSBundle...');
return await IosShellApp.configureAndCopyArchiveAsync(shellAppParams);
+16 -8
View File
@@ -1,11 +1,19 @@
import path from 'path';
import { ExponentTools } from 'xdl';
import config from 'turtle/config';
import { PLATFORMS } from 'turtle/constants';
export function formatShellAppDirectory(sdkVersion: string) {
const majorVersion = ExponentTools.parseSdkMajorVersion(sdkVersion);
return config.builder.useLocalWorkingDir
? path.join(config.directories.workingDir, 'local')
: path.join(config.directories.workingDir, PLATFORMS.IOS, `sdk${majorVersion}`);
import { ExponentTools } from 'xdl';
import { IShellAppDirectoryConfig } from 'turtle/builders/utils/workingdir';
import config from 'turtle/config';
import { IOS, 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}`);
}
}
+30
View File
@@ -0,0 +1,30 @@
import fs from 'fs-extra';
import path from 'path';
import { ExponentTools } from 'xdl';
import config from 'turtle/config';
import { IJob } from 'turtle/job';
let SUPPORTED_SDK_VERSIONS: number[];
export async function ensureCanBuildSdkVersion(job: IJob) {
if (config.builder.useLocalWorkingDir) {
return;
}
if (!SUPPORTED_SDK_VERSIONS) {
SUPPORTED_SDK_VERSIONS = await findSupportedSdkVersions(job.platform);
}
const targetMajorSdkVersion = ExponentTools.parseSdkMajorVersion(job.sdkVersion || job.manifest.sdkVersion);
if (!SUPPORTED_SDK_VERSIONS.includes(targetMajorSdkVersion)) {
throw new Error(`Unsupported SDK Version!`);
}
}
async function findSupportedSdkVersions(platform: 'android' | 'ios'): Promise<number[]> {
const SDK_DIR_PREFIX = 'sdk';
const files = await fs.readdir(path.join(config.directories.workingDir, platform));
const sdks = files.filter((file) => file.startsWith(SDK_DIR_PREFIX));
return sdks.map((sdk) => parseInt(sdk.substr(SDK_DIR_PREFIX.length), 10));
}
+6
View File
@@ -0,0 +1,6 @@
import { IOS } from 'turtle/constants';
export interface IShellAppDirectoryConfig {
sdkVersion: string;
buildType?: IOS.BUILD_TYPES;
}
+14 -5
View File
@@ -1,14 +1,14 @@
import os from 'os';
import path from 'path';
import { env, envNum, envOptional, envTransform } from 'turtle/utils/env';
import { PLATFORMS } from 'turtle/constants';
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),
platform: env('PLATFORM', PLATFORMS.IOS) as PLATFORMS,
aws: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_SECRET_ACCESS_KEY'),
@@ -53,6 +53,9 @@ export default {
buffer: envNum('LOGGLY_BUFFER', 100),
},
},
google: {
credentials: env('GOOGLE_APPLICATION_CREDENTIALS', ''),
},
sentry: {
dsn: env('SENTRY_DSN', ''),
},
@@ -66,7 +69,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,9 +80,15 @@ 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';
+4 -5
View File
@@ -1,10 +1,9 @@
import * as BUILD from './build';
import * as IOS from './ios';
export const PLATFORMS = {
IOS: 'ios',
ANDROID: 'android',
};
export enum PLATFORMS {
IOS = 'ios',
ANDROID = 'android',
}
export { IOS, BUILD };
+5 -4
View File
@@ -1,4 +1,5 @@
export const BUILD_TYPES = {
ARCHIVE: 'archive',
SIMULATOR: 'simulator',
};
export enum BUILD_TYPES {
ARCHIVE = 'archive',
CLIENT = 'client',
SIMULATOR = 'simulator',
}
+6 -3
View File
@@ -1,3 +1,5 @@
import { IOS } from 'turtle/constants';
export interface IJob {
platform: 'android' | 'ios';
experienceName: string;
@@ -8,7 +10,7 @@ export interface IJob {
releaseChannel: string;
publicUrl?: string;
// ios
buildType?: string;
buildType?: IOS.BUILD_TYPES;
bundleIdentifier?: string;
};
credentials: {
@@ -16,13 +18,14 @@ export interface IJob {
keystore?: string;
keystoreAlias?: string;
keystorePassword?: string;
// ios
keyPassword?: string;
// ios
certP12?: string;
certPassword?: string;
provisioningProfile?: string;
password?: string;
teamId?: string;
appleSession?: string;
udids?: string[];
};
projectDir: string;
fakeUploadDir?: string;
+32 -17
View File
@@ -1,19 +1,18 @@
import _ from 'lodash';
import { logErrorOnce } from 'turtle/builders/utils/common';
import logger from 'turtle/logger';
import * as sqs from 'turtle/aws/sqs';
import { BUILD } from 'turtle/constants/index';
import { sanitizeJob } from 'turtle/validator';
import builders from 'turtle/builders';
import BuildError from 'turtle/builders/BuildError';
import { logErrorOnce } from 'turtle/builders/utils/common';
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 { 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 { getPriorities } from 'turtle/utils/priorities';
const { version: turtleVersion } = require('../package.json');
import * as redis from 'turtle/utils/redis';
import { sanitizeJob } from 'turtle/validator';
function _maybeExit() {
if (checkShouldExit()) {
@@ -31,7 +30,6 @@ 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;
@@ -73,20 +71,21 @@ async function processJob(jobData: any) {
logger.info('The job has been cancelled');
} else {
redis.registerListener(job.id, () => deleteMessage(priority, receiptHandle));
const startTimestamp = +new Date();
const startTimestamp = Date.now();
let buildFailed = false;
const buildType = _.get(job, 'config.buildType', 'default');
try {
const success = await build(job);
buildStatusMetric.add(buildType, success);
buildFailed = !success;
const status = await build(job);
buildStatusMetric.add(buildType, status);
buildFailed = !status;
} catch (err) {
buildStatusMetric.add(buildType, false);
buildFailed = true;
throw err;
} finally {
const endTimestamp = +new Date();
const endTimestamp = Date.now();
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(
@@ -121,7 +120,9 @@ function failAfterMaxJobTime(priority: string, receiptHandle: string, job: any)
}
async function build(job: any) {
const startTimestamp = Date.now();
const s3Url = await logger.init(job);
const calculateBuildDuration = () => Math.ceil((Date.now() - startTimestamp) / 1000);
try {
await sqs.sendMessage(job.id, BUILD.JOB_STATES.IN_PROGRESS, {
@@ -129,11 +130,25 @@ async function build(job: any) {
logFormat: 'json',
});
const result = await (builders as any)[job.platform](job);
sqs.sendMessage(job.id, BUILD.JOB_STATES.FINISHED, { ...result, turtleVersion });
const buildDuration = calculateBuildDuration();
sqs.sendMessage(job.id, BUILD.JOB_STATES.FINISHED, {
...result,
turtleVersion,
buildDuration,
});
return true;
} catch (err) {
logErrorOnce(err);
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, { turtleVersion });
const buildDuration = calculateBuildDuration();
let reason;
if (err instanceof BuildError) {
reason = err.reason;
}
sqs.sendMessage(job.id, BUILD.JOB_STATES.ERRORED, {
turtleVersion,
buildDuration,
...reason && { reason },
});
return false;
} finally {
await logger.cleanup(job);
+1 -1
View File
@@ -12,5 +12,5 @@ export default baseJobSchema.concat(
keystoreAlias: Joi.string(),
keystore: Joi.string(),
}),
})
}),
);
+7 -5
View File
@@ -12,16 +12,18 @@ export default baseJobSchema.concat(
}),
credentials: Joi.object()
.keys({
certP12: Joi.string(),
certPassword: Joi.string(),
teamId: Joi.string(),
certP12: Joi.string().required(),
certPassword: Joi.string().required(),
teamId: Joi.string().required(),
// it's not required for adhoc Expo Client builds
provisioningProfile: Joi.string(),
password: Joi.string(),
appleSession: Joi.string(),
udids: Joi.array().items(Joi.string()),
})
.when('config.buildType', {
is: IOS.BUILD_TYPES.SIMULATOR,
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 LogglyConfig {
export interface ILogglyConfig {
token: string;
subdomain: string;
tags: Array<string>;
tags: string[];
}
interface LogglyExtraFields {
jobID: string,
interface ILogglyExtraFields {
jobID: string;
experienceName: string;
}
export default class HackyLogglyStream extends EventEmitter {
logglyStream: BunyanLoggly;
extraFields: LogglyExtraFields | null;
private logglyStream: BunyanLoggly;
private extraFields: ILogglyExtraFields | null;
constructor(logglyConfig: LogglyConfig) {
constructor(logglyConfig: ILogglyConfig) {
super();
this.logglyStream = new BunyanLoggly(logglyConfig, config.logger.loggly.buffer);
this.extraFields = null;
}
init(job: IJob) {
public init(job: IJob) {
this.extraFields = {
jobID: job.id,
experienceName: job.experienceName
experienceName: job.experienceName,
};
}
cleanup() {
public cleanup() {
this.extraFields = null;
}
write(rec: any) {
public write(rec: any) {
const { msg: message, hostname: host, level, ...other } = rec;
this.logglyStream.write({
message,
+30 -13
View File
@@ -1,16 +1,21 @@
import bunyan from 'bunyan';
import { Client as RavenClient } from 'raven';
import { SentryStream } from 'bunyan-sentry-stream';
import bunyanDebugStream from 'bunyan-debug-stream';
import { SentryStream } from 'bunyan-sentry-stream';
import { Client as RavenClient } from 'raven';
import config from 'turtle/config';
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';
import HackyLogglyStream from 'turtle/logger/hackyLogglyStream';
import S3Stream from 'turtle/logger/s3Stream';
import { isOffline } from 'turtle/turtleContext';
interface Stream {
// type error when using import
// https://github.com/googleapis/nodejs-logging-bunyan/issues/241
// tslint:disable-next-line:no-var-requires
const { LoggingBunyan } = require('@google-cloud/logging-bunyan');
interface IStream {
stream: any;
type?: string;
level?: string;
@@ -20,7 +25,7 @@ interface Stream {
export const s3logger = new S3Stream();
let logglyStream: HackyLogglyStream;
const streams: Stream[] = [];
const streams: IStream[] = [];
if (isOffline()) {
const prettyStdOut = new bunyanDebugStream({ forceColor: true });
@@ -33,7 +38,7 @@ if (isOffline()) {
stream: s3logger,
reemitErrorEvents: true,
level: config.logger.client.level,
}
},
);
}
@@ -54,9 +59,7 @@ if (config.logger.loggly.token) {
// prettier-ignore
tags: [
'app-shell-apps',
`platform.${config.platform}`,
`environment.${config.deploymentEnv}`,
]
],
};
logglyStream = new HackyLogglyStream(logglyConfig);
streams.push({
@@ -65,9 +68,23 @@ if (config.logger.loggly.token) {
});
}
if (config.google.credentials) {
const resource = {
type: 'generic_node',
labels: {
node_id: config.hostname,
location: '', // default value
namespace: '', // default value
},
};
streams.push(new LoggingBunyan({ name: 'turtle', resource }).stream('info'));
}
const logger = bunyan.createLogger({
name: 'turtle',
level: config.logger.level,
platform: config.platform,
environment: config.deploymentEnv,
streams,
});
@@ -91,9 +108,9 @@ logger.cleanup = async () => {
export default logger;
export function withFields(_logger: any, extraFields: any) {
export function withFields(loggerObj: any, extraFields: any) {
return constants.LEVELS.reduce((obj, level) => {
obj[level] = (...args: any[]) => _logger[level](extraFields, ...args);
obj[level] = (...args: any[]) => loggerObj[level](extraFields, ...args);
return obj;
}, {} as any);
}
+24 -24
View File
@@ -1,6 +1,6 @@
import path from 'path';
import EventEmitter from 'events';
import path from 'path';
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 {
s3Url: string | null;
lastFlush: number | null;
filePath: string | null;
fileHandle: number | null;
finishedPromise: Promise<any> | null;
finishedPromiseResolveFn: any;
secrets: any;
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;
constructor() {
super();
@@ -29,7 +29,7 @@ export default class S3Stream extends EventEmitter {
this.finishedPromiseResolveFn = null;
}
async init(job: IJob) {
public 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(res => (this.finishedPromiseResolveFn = res));
this.finishedPromise = new Promise((resolveFn) => (this.finishedPromiseResolveFn = resolveFn));
const res = await this.flush() as any;
return res.Location;
}
async cleanup() {
public async cleanup() {
const res = this.finishedPromiseResolveFn;
try {
await this.flush(true);
@@ -59,7 +59,7 @@ export default class S3Stream extends EventEmitter {
}
}
flush(force = false) {
public flush(force = false) {
if (force || !this.lastFlush) {
return this._flush();
}
@@ -69,15 +69,7 @@ export default class S3Stream extends EventEmitter {
}
}
_flush() {
this.lastFlush = Date.now();
return uploadFile({
srcPath: this.filePath as string,
key: this.s3Url as string,
});
}
write(recRaw: any) {
public write(recRaw: any) {
if (this.fileHandle) {
const rec = JSON.stringify({
...recRaw,
@@ -88,7 +80,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`)
@@ -98,14 +90,22 @@ export default class S3Stream extends EventEmitter {
}
}
addSecretsToFilter(secrets: any) {
public 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);
}
waitForLogger() {
public 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 logger from 'turtle/logger';
import * as cloudwatch from 'turtle/aws/cloudwatch';
import * as buildStatus from 'turtle/metrics/buildStatus';
import logger from 'turtle/logger';
import * as buildDuration from 'turtle/metrics/buildDuration';
import * as buildStatus from 'turtle/metrics/buildStatus';
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?: Function);
write(data: any): void;
constructor(options: BunyanLoggly.IOptions, bufferLength?: number, bufferTimeout?: number, logglyCallback?: any);
public write(data: any): void;
}
declare namespace BunyanLoggly {
interface IOptions {
token: string
subdomain: string
token: string;
subdomain: string;
}
}
+19 -7
View File
@@ -1,12 +1,15 @@
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 { doJob } from 'turtle/jobManager';
import { setShouldExit, checkShouldExit } from 'turtle/turtleContext';
import { checkShouldExit, setShouldExit, turtleVersion } from 'turtle/turtleContext';
import { getRedisClient, RedisClient } from 'turtle/utils/redis';
process.on('unhandledRejection', err => logger.error('Unhandled promise rejection:', err));
const REDIS_TURTLE_VERSION_KEY = 'turtle:version';
process.on('unhandledRejection', (err) => logger.error('Unhandled promise rejection:', err));
function handleExit() {
if (checkShouldExit()) {
@@ -14,7 +17,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 +26,22 @@ 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
try {
const redis = await getRedisClient(RedisClient.Configuration);
await redis.set(REDIS_TURTLE_VERSION_KEY, turtleVersion);
logger.info(`Register Turtle version ${turtleVersion} in Redis`);
} catch (err) {
logger.error('Failed to register Turtle version in Redis', err);
}
while (true) {
try {
await doJob();
@@ -41,5 +53,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 ios from 'turtle/setup/ios';
import android from 'turtle/setup/android';
import ios from 'turtle/setup/ios';
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))));
}
}
+8 -3
View File
@@ -1,7 +1,12 @@
import fs from 'fs-extra';
import path from 'path';
import config from 'turtle/config';
const { version: turtleVersion } = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
export { turtleVersion };
let shouldExit = false;
let _currentJobId: string | null = null;
let currentJobId: string | null = null;
export const setShouldExit = () => {
shouldExit = true;
@@ -12,11 +17,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';
+4 -3
View File
@@ -1,7 +1,8 @@
import config from 'turtle/config';
import os from 'os';
import logger from 'turtle/logger';
import Joi from 'joi';
import os from 'os';
import config from 'turtle/config';
import logger from 'turtle/logger';
import { getRedisClient, RedisClient } from 'turtle/utils/redis';
export const NORMAL = 'normalPriority';
+8 -9
View File
@@ -1,21 +1,20 @@
import Redis from 'ioredis';
import logger from 'turtle/logger';
import config from 'turtle/config';
import logger from 'turtle/logger';
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',
}
type ClientConfig = { [key: string]: string; };
interface IClientConfig { [key: string]: string; }
const clientToUrl: ClientConfig = {
const clientToUrl: IClientConfig = {
[RedisClient.Subscriber]: config.redis.url,
[RedisClient.Default]: config.redis.url,
[RedisClient.Configuration]: config.redis.configUrl,
@@ -42,21 +41,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) {
@@ -76,7 +75,7 @@ export async function registerListener(jobId: string, deleteMessage: any) {
try {
const redis = await getRedisClient(RedisClient.Subscriber);
redis.subscribe('jobs:cancelled');
redis.on('message', async function(_: any, message: any) {
redis.on('message', async (_: any, message: any) => {
if (message === jobId) {
logger.info({ lastBuildLog: true }, 'Job cancelled - killing process');
await deleteMessage();
+5
View File
@@ -8,4 +8,9 @@ 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"
+2 -1
View File
@@ -4,7 +4,8 @@
"jsRules": {},
"rules": {
"quotemark": [true, "single", "jsx-double"],
"object-literal-sort-keys": false
"object-literal-sort-keys": false,
"no-namespace": false
},
"rulesDirectory": []
}
+4639 -1000
View File
File diff suppressed because it is too large. Load diff