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