You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@weex.apache.org by ky...@apache.org on 2019/08/08 07:13:50 UTC
[incubator-weex] branch master updated: [Travis] Add Lint in
TravisCI and Update iOS TravisCI (#2731)
This is an automated email from the ASF dual-hosted git repository.
kyork pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-weex.git
The following commit(s) were added to refs/heads/master by this push:
new 3bfb619 [Travis] Add Lint in TravisCI and Update iOS TravisCI (#2731)
3bfb619 is described below
commit 3bfb619122bbb87431b90790dd0d47a8db8ef193
Author: Renmin <33...@users.noreply.github.com>
AuthorDate: Thu Aug 8 15:13:42 2019 +0800
[Travis] Add Lint in TravisCI and Update iOS TravisCI (#2731)
Add the following tasks:
* Android Lint
* OCLint
---
.travis.yml | 117 +++++++++++++++++++++++-----
Dangerfile | 0
dangerfile-ios.js | 21 +----
dangerfile-output.js | 71 +++++++++++++++++
dangerfile-static-check.js | 89 +++++++++++++++++++++
ios/sdk/.gitignore | 1 +
ios/sdk/WeexSDK/Sources/Utility/WXVersion.m | 4 +-
package-lock.json | 41 +++-------
package.json | 2 +-
9 files changed, 276 insertions(+), 70 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 47dace8..868c588 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
os: linux
language: node_js
-node_js: 7.6
+node_js: 12.6.0
matrix:
fast_finish: true
include:
@@ -31,7 +31,20 @@ matrix:
android:
components:
- android-26
+ - extra-android-m2repository
+ # static check
+ - env: TEST_SUITE=static_code_analysis OCLINT=true
+ osx_image: xcode7.2
+ language: objective-c
+ - env: TEST_SUITE=static_code_analysis ANDROID_LINT=true
+ language: android
+ dist: trusty
+ jdk: oraclejdk8
+ android:
+ components:
+ - android-26
- extra-android-m2repository
+
cache:
directories:
- npm
@@ -39,15 +52,36 @@ cache:
- $HOME/.gradle/wrapper/
- $HOME/.android/build-cache
- $HOME/android-ndk-r18b
+ - bundle
+
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
+ - rm -fr $HOME/node_modules/*
+
+before_install:
+ - |
+ # install android lint
+ if [[ ("$TEST_SUITE" = "static_code_analysis") && ("${ANDROID_LINT}" = "true") ]]; then
+ apt-get install rubygems
+ gem install bundler
+ bundle install
+ fi
+
+ # install oclint
+ if [[ ("$TEST_SUITE" = "static_code_analysis") && ("${OCLINT}" = "true") ]]; then
+ brew cask uninstall oclint
+ brew tap oclint/formulae
+ brew install oclint
+ fi
+
+
+
install:
- |
- case $TEST_SUITE in
- "android")
+ if [[ ("$TEST_SUITE" = "android") || ("${ANDROID_LINT}" = "true") ]]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
- nvm install 7.6
+ nvm install 12.6.0
npm install
echo y | sdkmanager "cmake;3.6.4111459"
if find "${HOME}/android-ndk-r18b" -mindepth 1 | read; then
@@ -64,11 +98,22 @@ install:
export ANDROID_NDK_HOME=$HOME/android-ndk-r18b
export PATH=$PATH:$ANDROID_NDK_HOME
echo "ndk.dir=$ANDROID_NDK_HOME" > android/local.properties
- ;;
- "jsfm" | "danger" | "ios" )
+ elif [[ ("$TEST_SUITE" = "jsfm") || ("$TEST_SUITE" = "danger") || ("${OCLINT}" = "true") ]]; then
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
+ nvm install 12.6.0
npm install
- ;;
- esac
+ elif [[ ("$TEST_SUITE" = "ios") ]]; then
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
+ nvm install 12.6.0
+
+ git submodule update --init --remote
+ cd weex-playground/ios && bash update_podfile_for_travisci.sh
+ cd ../../ && npm install
+ cd weex-playground/ios && pod install --repo-update
+ cd ../../
+ fi
+
+
script:
- |
case $TEST_SUITE in
@@ -87,29 +132,65 @@ script:
GRADLE_ABI=""
;;
esac
-
- hasAndroidFile=$(npm run danger -- run --dangerfile ./dangerfile-android.js)
- echo "The value of hasAndroidFile is ${hasAndroidFile}"
- if [[ "$hasAndroidFile" =~ "hasAndroidFile" ]]; then
+ if npm run danger -- ci --dangerfile ./dangerfile-android.js | grep -q "hasAndroidFile" ; then
cd android
./gradlew clean install -PbuildRuntimeApi=true ${GRADLE_ABI} --info
./gradlew install -PbuildRuntimeApi=false ${GRADLE_ABI} --info
fi
;;
"jsfm" )
- npm run danger -- run --dangerfile ./dangerfile-jsfm.js
+ npm run danger -- ci -i jsfm --dangerfile ./dangerfile-jsfm.js
;;
"danger" )
- npm run danger -- run --dangerfile ./dangerfile.js
+ npm run danger -- ci -i danger --dangerfile ./dangerfile.js
;;
"ios" )
- hasIosFile=$(npm run danger -- run --dangerfile ./dangerfile-ios.js)
- echo "The value of hasIosFile is ${hasIosFile}"
- if [[ "$hasIosFile" =~ "hasIosFile" ]]; then
- xcodebuild -project ios/sdk/WeexSDK.xcodeproj test -scheme WeexSDKTests CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -destination "platform=iOS Simulator,name=iPhone 6"
+ if npm run danger -- ci --dangerfile ./dangerfile-ios.js | grep -q "hasIosFile" ; then
+ # build WeexSDK and run WeexSDKTests
+ xcodebuild -quiet -project ios/sdk/WeexSDK.xcodeproj test -scheme WeexSDKTests CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -destination "platform=iOS Simulator,name=iPhone 6" || exit 1
+ # build WeexDemo and run WeexDemo test
+ cd weex-playground/ios && mkdir tmp && mv * tmp;cd tmp
+ xcodebuild -quiet -workspace WeexDemo.xcworkspace test -scheme WeexDemo CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -destination "platform=iOS Simulator,name=iPhone 6" || exit 1
fi
;;
esac
+
+ if [[ ("$TEST_SUITE" = "static_code_analysis") && ("${OCLINT}" = "true") ]]; then
+ if npm run danger -- ci --dangerfile ./dangerfile-static-check.js | grep -q "hasCFile" ; then
+ echo "hasCFile"
+ cd ios/sdk && xcodebuild | xcpretty -r json-compilation-database -o compile_commands.json
+ oclint-json-compilation-database oclint_args -- \
+ -disable-rule=ShortVariableName \
+ -disable-rule=LongLine \
+ -disable-rule=LongMethod \
+ -disable-rule=HighNcssMethod \
+ -disable-rule=LongVariableName \
+ -disable-rule=HighCyclomaticComplexity \
+ -disable-rule=HighNPathComplexity \
+ -disable-rule=UnusedLocalVariable \
+ -disable-rule=DoubleNegative \
+ -disable-rule=MultipleUnaryOperator \
+ -disable-rule=DeepNestedBlock \
+ -disable-rule=AssignIvarOutsideAccessors \
+ -disable-rule=BitwiseOperatorInConditional \
+ -max-priority-1=15000 \
+ -max-priority-2=15000 \
+ -max-priority-3=15000 > oclint.log
+ export TITLE="OCLint Result"
+ cd ../../ && npm run danger -- ci -i oclint --dangerfile ./dangerfile-output.js
+ fi
+ fi
+
+ if [[ ("$TEST_SUITE" = "static_code_analysis") && ("${ANDROID_LINT}" = "true") ]]; then
+ if npm run danger -- ci --dangerfile ./dangerfile-static-check.js | grep -q "hasAndroidFile" ; then
+ echo "hasAndroidFile"
+ cd android
+ ./gradlew :weex_sdk:lint --quiet
+ export TITLE="AndroidLint Result"
+ cd ../ && npm run danger -- ci -i androidlint --dangerfile ./dangerfile-output.js
+ fi
+ fi
+
notifications:
webhooks:
on_pull_requests: false
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
index 0000000..e69de29
diff --git a/dangerfile-ios.js b/dangerfile-ios.js
index c507cce..e14aec5 100644
--- a/dangerfile-ios.js
+++ b/dangerfile-ios.js
@@ -90,22 +90,5 @@ if (!hasIosFile && danger.git.deleted_files) {
}
if(hasIosFile){
- console.log('hasIosFile');
-}
-// console.log('-----------------------------hasIosFile-----------------------------:'+hasIosFile);
-// if(hasIosFile){
-// var runTestCmd='source ~/.bash_profile; '
-// +'xcodebuild -project ios/sdk/WeexSDK.xcodeproj test '
-// +'-scheme WeexSDKTests CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO '
-// +'-destination "platform=iOS Simulator,name=iPhone 6"'
-// runSuccess = shell.exec(runTestCmd,{ async: false, timeout: 8 * 60 * 1000, maxBuffer: 200 * 1024 * 1024 }).code == 0;
-// if(!runSuccess){
-// fail("ios platform run unit test failed!");
-// }
-// }else{
-// console.log('has no ios file changed.');
-// message('has no ios file changed.');
-// }
-// message('ios test finished.')
-
-
+ console.log('hasIosFile!!!');
+}
\ No newline at end of file
diff --git a/dangerfile-output.js b/dangerfile-output.js
new file mode 100644
index 0000000..d4d8a89
--- /dev/null
+++ b/dangerfile-output.js
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { fail, warn } from 'danger'
+const shell = require('shelljs')
+const title = process.env.TITLE
+const fs = require('fs')
+
+// code come from: https://stackoverflow.com/a/12745196
+// get the index of nth char in string
+function nth_occurrence (string, char, nth) {
+ var first_index = string.indexOf(char)
+ var length_up_to_first_index = first_index + 1
+ if (nth === 1) {
+ return first_index;
+ } else {
+ var string_after_first_occurrence = string.slice(length_up_to_first_index);
+ var next_occurrence = nth_occurrence(string_after_first_occurrence, char, nth - 1);
+ if (next_occurrence === -1) {
+ return -1;
+ } else {
+ return length_up_to_first_index + next_occurrence;
+ }
+ }
+}
+
+if (title === 'OCLint Result') {
+ const command = 'cat ios/sdk/oclint.log | grep -i "P[1|2]"'
+ const child = shell.exec(command)
+ if (child.stdout !== '') {
+ fail(child.stdout)
+ fail(title)
+ }
+ if (child.stderr !== '') {
+ fail(child.stderr)
+ fail(title)
+ }
+}
+else if (title === 'AndroidLint Result') {
+ var content = fs.readFileSync('android/sdk/build/reports/lint-results.html', 'utf8')
+ // in xml report,Overview Section,Disabled Checks Section and Suppressing Warnings and Errors Section is not reported.
+ // in html report, those Section are included,
+ // but Overview Section can't work in markdown,
+ // on the other hand,Disabled Checks Section and Suppressing Warnings and Errors Section are not needed.
+ // the first section is Overview section,
+ // the last two section are Disabled Checks Section and Suppressing Warnings and Errors Section.
+ // so we should grep the str from the second <section to the third </section> from last"
+ const occurance = content.split('</section>').length - 1
+ if (occurance > 3) {
+ content = content.substr(nth_occurrence(content, '<section ', 2))
+ content = content.substr(0, nth_occurrence(content, '</section>', occurance - 3))
+ fail(content)
+ fail(title)
+ }
+}
\ No newline at end of file
diff --git a/dangerfile-static-check.js b/dangerfile-static-check.js
new file mode 100644
index 0000000..2497588
--- /dev/null
+++ b/dangerfile-static-check.js
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { danger } from "danger";
+import fs from "fs";
+import path from 'path';
+import GitHubApi from 'github';
+import parseDiff from 'parse-diff';
+import shell from "shelljs";
+
+const type_unknown = 0;
+const type_c = 1;
+const type_android = 2;
+
+const getFileType = file => {
+ if (file.match(/.+\.(m|h|mm|cpp|cc)/)) {
+ return type_c;
+ } else if (file.match(/android/)) {
+ return type_android;
+ } else {
+ return type_unknown;
+ }
+}
+
+var hasCFile = false;
+var hasAndroidFile = false;
+
+function check(file_type) {
+ var has_file_type = false;
+ if (!has_file_type && danger.git.created_files) {
+ danger.git.created_files.some(file => {
+ var f = (getFileType(file) == file_type)
+ if (f) {
+ has_file_type = f;
+ }
+ return f;
+ });
+ }
+
+ if (!has_file_type && danger.git.modified_files) {
+ danger.git.modified_files.some(file => {
+ var f = (getFileType(file) == file_type)
+ if (f) {
+ has_file_type = f;
+ }
+ return f;
+ });
+ }
+
+ if (!has_file_type && danger.git.deleted_files) {
+ danger.git.deleted_files.some(file => {
+ var f = (getFileType(file) == file_type)
+ if (f) {
+ has_file_type = f;
+ }
+ return f;
+ });
+ }
+
+ return has_file_type
+}
+
+hasCFile = check(type_c)
+hasAndroidFile = check(type_android)
+
+var output_str = ""
+if (hasCFile) {
+ output_str += 'hasCFile\n'
+}
+if (hasAndroidFile) {
+ output_str += 'hasAndroidFile\n'
+}
+console.log(output_str)
diff --git a/ios/sdk/.gitignore b/ios/sdk/.gitignore
new file mode 100644
index 0000000..0dbf501
--- /dev/null
+++ b/ios/sdk/.gitignore
@@ -0,0 +1 @@
+*compile_commands.json
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXVersion.m b/ios/sdk/WeexSDK/Sources/Utility/WXVersion.m
index 0138907..2775310 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXVersion.m
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXVersion.m
@@ -20,8 +20,8 @@
#import "WXVersion.h"
#import "WXDefine.h"
-static const char* WeexSDKBuildTime = "2019-07-16 07:08:34 UTC";
-static const unsigned long WeexSDKBuildTimestamp = 1563260914;
+static const char* WeexSDKBuildTime = "2019-07-21 09:08:41 UTC";
+static const unsigned long WeexSDKBuildTimestamp = 1563700121;
NSString* GetWeexSDKVersion(void)
{
diff --git a/package-lock.json b/package-lock.json
index 211b6b4..a16a0f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3778,8 +3778,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true,
- "optional": true
+ "dev": true
},
"aproba": {
"version": "1.2.0",
@@ -3803,15 +3802,13 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3828,22 +3825,19 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3974,8 +3968,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -3989,7 +3982,6 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -4006,7 +3998,6 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -4015,15 +4006,13 @@
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
- "dev": true,
- "optional": true
+ "dev": true
},
"minipass": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz",
"integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -4044,7 +4033,6 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -4133,8 +4121,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -4148,7 +4135,6 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -4244,8 +4230,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
- "dev": true,
- "optional": true
+ "dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4287,7 +4272,6 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4309,7 +4293,6 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4358,15 +4341,13 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true,
- "optional": true
+ "dev": true
},
"yallist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
- "dev": true,
- "optional": true
+ "dev": true
}
}
},
diff --git a/package.json b/package.json
index 80cccf9..28cb05c 100644
--- a/package.json
+++ b/package.json
@@ -97,7 +97,7 @@
"chromedriver": "^2.21.2",
"cross-spawn": "^4.0.0",
"css-loader": "^0.26.1",
- "danger": "^0.18.0",
+ "danger": "^9.0.0",
"dateformat": "^2.0.0",
"eslint": "^2.11.1",
"eslint-plugin-flowtype": "^2.40.1",