├── .babelrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── index.js ├── package.json ├── src ├── android │ ├── check.js │ ├── link.js │ └── softlink.js ├── commands │ ├── create.js │ ├── init.js │ ├── link.js │ ├── remotelink.js │ ├── update.js │ └── use.js ├── config.js ├── generator │ ├── creator.js │ ├── linker.js │ ├── remoteLinker.js │ ├── shellProject.js │ ├── updator.js │ └── use.js ├── ios │ ├── 1.5 │ │ ├── fbpodhelper.rb │ │ └── link.js │ ├── 1.9 │ │ ├── fbpodhelper.rb │ │ ├── link.js │ │ └── xcode_backend_1.9.1_bugfix.sh │ ├── link-common.js │ └── softlink.js ├── log.js ├── market │ └── FlutterBoost │ │ ├── addtpl.js │ │ ├── config.json │ │ └── tpl │ │ ├── android │ │ └── fb │ │ │ ├── FBInitializer.java │ │ │ ├── FBViewEntry.java │ │ │ └── README.md │ │ ├── dart │ │ ├── main.dart │ │ └── my_fb_app.dart │ │ └── ios │ │ └── fb │ │ ├── FBDemoRouter.h │ │ └── FBDemoRouter.m ├── repo │ ├── git.js │ ├── index.js │ ├── manager.js │ └── svn.js ├── scripts │ ├── add_ios_tpl.rb │ ├── duplicate_target.rb │ ├── fbinclude_flutter.groovy │ └── inject_flutter_script.rb ├── ui.js ├── util.js └── utils │ ├── execUtils.js │ ├── exit.js │ ├── flutterRecorder.js │ ├── fsutils.js │ ├── isEmpty.js │ ├── isNull.js │ ├── marketConfigHelper.js │ ├── pathUtils.js │ ├── pubspecHelper.js │ └── typof.js └── test ├── cmd.js ├── sandbox.js ├── test.create.js ├── test.link.js ├── test.remotelink.js ├── test.use.js └── tpl ├── tandroid ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── app │ ├── .classpath │ ├── .gitignore │ ├── .project │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── fbi │ │ │ └── tandroid │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── fbi │ │ │ │ └── tandroid │ │ │ │ ├── MainActivity.java │ │ │ │ └── MyApplication.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── fbi │ │ └── tandroid │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── tflutter_1_5 ├── .gitignore ├── .metadata ├── README.md ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart ├── tflutter_1_5.iml └── tflutter_1_5_android.iml ├── tflutter_1_9 ├── .gitignore ├── .metadata ├── README.md ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart ├── tflutter_1_9.iml └── tflutter_1_9_android.iml └── tios ├── tios.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── tios ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── tiosTests ├── Info.plist └── tiosTests.m └── tiosUITests ├── Info.plist └── tiosUITests.m /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .last_flutter_module 4 | test/sandbox/ 5 | test/tpl/tios/tios.xcodeproj/project.xcworkspace/xcuserdata/ 6 | test/tpl/tios/tios.xcodeproj/xcuserdata/ -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | const program = require('commander') 6 | const log = require('./src/log') 7 | 8 | process.env.FB_DIR = __dirname 9 | log.level = 'silly' 10 | 11 | // Commands 12 | require('./src/commands/init')(program) 13 | require('./src/commands/create')(program) 14 | require('./src/commands/link')(program) 15 | require('./src/commands/remotelink')(program) 16 | require('./src/commands/update')(program) 17 | require('./src/commands/use')(program) 18 | 19 | program 20 | .version('0.0.7') 21 | .description('Flutter Boot: install && run') 22 | .parse(process.argv) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter-boot", 3 | "version": "0.0.7", 4 | "description": "Flutter existing App integration tool.", 5 | "bin": { 6 | "flutter-boot": "./index.js" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "debug": "node --inspect --inspect-port=50521 index.js .", 11 | "test": "./node_modules/.bin/mocha --require babel-register --timeout 5000" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "" 16 | }, 17 | "keywords": [ 18 | "flutter-boot", 19 | "flutter", 20 | "plugin" 21 | ], 22 | "author": "Tino, Zhiming Xiang, Yin Ma, Hang Wu", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "" 26 | }, 27 | "homepage": "", 28 | "dependencies": { 29 | "ncp": "^2.0.0", 30 | "cross-spawn": "7.0.0", 31 | "uuid": "3.3.3", 32 | "concat-stream": "2.0.0", 33 | "del": "5.1.0", 34 | "json-parse-helpfulerror": "^1.0.3", 35 | "npmlog": "^4.0.0", 36 | "assets": "^3.0.1", 37 | "commander": "^2.19.0", 38 | "eslint": "^5.12.0", 39 | "js-yaml": "^3.12.1", 40 | "mustache": "^3.0.1", 41 | "easy-table": "~1.0.0", 42 | "inquirer": "7.0.0", 43 | "ora": "^0.3.0", 44 | "colors": "^1.1.2", 45 | "babel-register": "^6.24.1", 46 | "babel-preset-es2015": "^6.3.13", 47 | "babel-cli": "^6.0.0", 48 | "chai": "3.5.0", 49 | "svn-info": "1.0.0", 50 | "git-url-parse": "11.1.2" 51 | }, 52 | "devDependencies": { 53 | "mocha": "6.2.0" 54 | } 55 | } -------------------------------------------------------------------------------- /src/android/check.js: -------------------------------------------------------------------------------- 1 | const log = require('../log') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const fsutils = require('../utils/fsutils') 5 | const TAG = '[AndroidChecker]' 6 | 7 | const FLAG_ANDROIDX_1 = 'android.useAndroidX=true' 8 | const FLAG_ANDROIDX_2 = 'android.enableJetifier=true' 9 | 10 | class checker { 11 | 12 | checker () { 13 | this.flutterPath = '' 14 | this.nativePath = '' 15 | } 16 | 17 | flutterGradleProperties () { 18 | return path.join(this.flutterPath, '.android/gradle.properties') 19 | } 20 | 21 | nativeGradleProperties () { 22 | return path.join(this.nativePath, 'gradle.properties') 23 | } 24 | 25 | check(options) { 26 | this.flutterPath = options.flutterPath 27 | this.nativePath = options.nativePath 28 | var checkResult = this.checkAndroidX() 29 | if (!checkResult) { 30 | return checkResult 31 | } 32 | log.info(TAG, 'android check passed!!!') 33 | return checkResult 34 | } 35 | 36 | checkAndroidX() { 37 | var nativeAndroidX = this.isSupportAndroidX(this.nativeGradleProperties()) 38 | var flutterAndroidX = this.isSupportAndroidX(this.flutterGradleProperties()) 39 | if (nativeAndroidX != flutterAndroidX) { 40 | if (!nativeAndroidX) { 41 | log.error(TAG, 'check androidx failed: ' + this.nativePath + ' not support AndroidX!') 42 | } else if (!flutterAndroidX) { 43 | log.error(TAG, 'check androidx failed: ' + this.flutterPath + ' not support AndroidX!') 44 | } 45 | return false 46 | } 47 | return true 48 | } 49 | 50 | isSupportAndroidX(gradlePropertiesPath) { 51 | if (!fs.existsSync(gradlePropertiesPath)) { 52 | return false 53 | } 54 | let rawdata = fs.readFileSync(gradlePropertiesPath, 'utf8') 55 | return rawdata.includes(FLAG_ANDROIDX_1) && rawdata.includes(FLAG_ANDROIDX_2) 56 | } 57 | } 58 | 59 | module.exports = new checker() 60 | -------------------------------------------------------------------------------- /src/android/link.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const execSync = require('child_process').execSync 3 | const fs = require('fs') 4 | const log = require('../log') 5 | const fsutils = require('../utils/fsutils') 6 | const { softlink } = require('./softlink') 7 | const checker = require('./check') 8 | const TAG = '[Androidlink]' 9 | 10 | const INJECTION_BUILD_GRADLE = 11 | '\n\ 12 | compileOptions { \n\ 13 | sourceCompatibility 1.8 \n\ 14 | targetCompatibility 1.8 \n\ 15 | } \n' 16 | 17 | const INJECTION_GRADLE_PROPERTIES = 18 | '\n# [FLUTTER_CONFIG_BEGIN]\n\ 19 | # 自动探测flutter module源码目录功能开关,默认开 \n\ 20 | android_enableDetectFlutterDir=true \n\ 21 | # [FLUTTER_CONFIG_END]' 22 | 23 | const INJECTION_GRADLE_SETTINGS = 24 | "\n // [FLUTTER_CONFIG_BEGIN]\n\ 25 | setBinding(new Binding([gradle: this])) \n\ 26 | evaluate(new File('fbinclude_flutter.groovy')) \n\ 27 | // [FLUTTER_CONFIG_END]" 28 | 29 | class linker { 30 | linker () { 31 | this.flutterPath = '' 32 | this.nativePath = '' 33 | } 34 | 35 | setOptions (options) { 36 | this.flutterPath = options.flutterPath 37 | this.nativePath = options.nativePath 38 | } 39 | 40 | link (options) { 41 | this.flutterPath = options.flutterPath 42 | this.nativePath = options.nativePath 43 | if (!checker.check(options)) { 44 | log.error(TAG, 'link failed: check failed') 45 | process.exit(1) 46 | } 47 | this.injectCompileOptions() 48 | this.injectDependency() 49 | this.injectGradleProperties() 50 | this.prepareIncludeFlutter() 51 | this.injectGradleSettings() 52 | this.injectGitIgnore() 53 | softlink(options) 54 | } 55 | 56 | injectionBuildGradle () { 57 | return INJECTION_BUILD_GRADLE 58 | } 59 | 60 | injectionGradleProperties () { 61 | return INJECTION_GRADLE_PROPERTIES 62 | } 63 | 64 | injectionGradleSettings () { 65 | return INJECTION_GRADLE_SETTINGS 66 | } 67 | 68 | buildGradle () { 69 | return path.join(this.nativePath, 'app/build.gradle') 70 | } 71 | 72 | settingsGradle () { 73 | return path.join(this.nativePath, 'settings.gradle') 74 | } 75 | 76 | includeFlutter () { 77 | return path.join(this.flutterPath, '.android/include_flutter.groovy') 78 | } 79 | 80 | gradleProperties () { 81 | return path.join(this.nativePath, 'gradle.properties') 82 | } 83 | 84 | gitignore () { 85 | return path.join(this.nativePath, '.gitignore') 86 | } 87 | 88 | execSync (command, option) { 89 | execSync( 90 | command, 91 | Object.assign({}, option, { 92 | stdio: 'inherit', 93 | cwd: this.nativePath 94 | }) 95 | ) 96 | } 97 | 98 | injectCompileOptions () { 99 | log.info(TAG, 'init compile options') 100 | let realPath = this.buildGradle() 101 | var fs = require('fs') 102 | var rawdata = fs.readFileSync(realPath, 'utf8') 103 | 104 | const ai = rawdata.indexOf('android') 105 | if (ai < 0) { 106 | log.error( 107 | TAG, 108 | 'invalid android build.gradle file, not android section found!' 109 | ) 110 | return 111 | } 112 | 113 | const si = rawdata.indexOf('{', ai + 7) 114 | 115 | const injection = INJECTION_BUILD_GRADLE 116 | 117 | if (!rawdata.includes(injection)) { 118 | const content = 119 | rawdata.substring(0, si + 1) + injection + rawdata.substring(si + 1) 120 | fs.writeFileSync(realPath, content) 121 | log.silly(TAG, 'Patch:' + injection) 122 | } else { 123 | } 124 | 125 | log.info(TAG, 'compile options settled into app/build.gradle') 126 | } 127 | 128 | injectDependency () { 129 | log.info(TAG, 'update flutter dependency') 130 | let realPath = this.buildGradle() 131 | var fs = require('fs') 132 | var rawdata = fs.readFileSync(realPath, 'utf8') 133 | 134 | const ai = rawdata.indexOf('dependencies') 135 | if (ai < 0) { 136 | log.error( 137 | TAG, 138 | 'invalid android build.gradle file, not dependencies section found!' 139 | ) 140 | return 141 | } 142 | 143 | const si = rawdata.indexOf('{', ai + 'dependencies'.length) 144 | 145 | const dependencyTag = "implementation project(':flutter')" 146 | if (!rawdata.includes(dependencyTag)) { 147 | const injection = 148 | " \n\ 149 | // [FLUTTER_DEPENDENCY_BEGIN] \n\ 150 | if (gradle.isDetectedFlutterDir) { \n\ 151 | implementation project(':flutter') \n\ 152 | } else { \n\ 153 | // 换成自己的远程flutter产物 \n\ 154 | } \n\ 155 | // [FLUTTER_DEPENDENCY_END] \n\ 156 | " 157 | const content = 158 | rawdata.substring(0, si + 1) + injection + rawdata.substring(si + 1) 159 | fs.writeFileSync(realPath, content) 160 | log.silly(TAG, 'Patch:' + injection) 161 | } 162 | 163 | log.info(TAG, 'dependency settled in app/build.gradle') 164 | } 165 | 166 | injectGradleProperties () { 167 | log.info(TAG, 'init gradle.properties') 168 | let realPath = this.gradleProperties() 169 | if (!fs.existsSync(realPath)) { 170 | log.silly(TAG, 'create gradle.properties') 171 | fs.writeFileSync(realPath, '') 172 | } 173 | const reg = /\n# \[FLUTTER_CONFIG_BEGIN\](\n|.)*?# \[FLUTTER_CONFIG_END\]/g 174 | const injection = INJECTION_GRADLE_PROPERTIES 175 | if (fsutils.addOrReplaceContent(realPath, reg, injection)) { 176 | log.silly(TAG, 'Add properties:' + injection) 177 | } 178 | } 179 | 180 | prepareIncludeFlutter () { 181 | log.info(TAG, 'inject file: fbinclude_flutter.groovy') 182 | fs.copyFileSync( 183 | path.join( 184 | process.env.FB_DIR, 185 | 'src', 186 | 'scripts', 187 | 'fbinclude_flutter.groovy' 188 | ), 189 | path.join(this.nativePath, 'fbinclude_flutter.groovy') 190 | ) 191 | } 192 | 193 | injectGradleSettings () { 194 | log.info(TAG, 'init gradle settings') 195 | let realPath = this.settingsGradle() 196 | if (!fs.existsSync(realPath)) { 197 | log.info(TAG, 'create app/settings.gradle') 198 | fs.writeFileSync(realPath, '') 199 | } 200 | const reg = /\n\/\/ \[FLUTTER_CONFIG_BEGIN\](\n|.)*?\/\/ \[FLUTTER_CONFIG_END\]/g 201 | const injection = INJECTION_GRADLE_SETTINGS 202 | if (fsutils.addOrReplaceContent(realPath, reg, injection)) { 203 | log.info(TAG, 'Patch:' + injection) 204 | } 205 | log.info(TAG, 'settings settled in settings.gradle') 206 | } 207 | 208 | injectGitIgnore () { 209 | log.info(TAG, 'inject gitignore') 210 | let realPath = this.gitignore() 211 | const injection = '\nfbConfig.local.json' 212 | if (!fs.existsSync(realPath)) { 213 | fs.writeFileSync(realPath, injection) 214 | } else { 215 | fsutils.addOrReplaceContent(realPath, /\nfbConfig.local.json/g, injection) 216 | } 217 | } 218 | } 219 | 220 | module.exports = new linker() 221 | -------------------------------------------------------------------------------- /src/android/softlink.js: -------------------------------------------------------------------------------- 1 | const log = require('../log') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const fsutils = require('../utils/fsutils') 5 | const TAG = '[softlink]' 6 | 7 | function softlink (options) { 8 | var flutterPath = options.flutterPath 9 | var nativePath = options.nativePath 10 | log.info(TAG, `flutterPath: ${flutterPath}; nativePath: ${nativePath}`) 11 | configAndroidHost(nativePath) 12 | fsutils.createSoftLink(path.join(flutterPath, 'android'), nativePath) 13 | } 14 | 15 | /** 16 | * 配置native业务工程 17 | */ 18 | function configAndroidHost (nativePath) { 19 | let injection = "def flutterPluginVersion = 'managed' \n\n" 20 | let filePath = path.join(nativePath, 'app/build.gradle') 21 | let rawdata = fs.readFileSync(filePath, 'utf8') 22 | if (!rawdata.includes(injection)) { 23 | const content = injection + rawdata 24 | fs.writeFileSync(filePath, content) 25 | log.silly('SYMLINK', `Patch: ${injection}`) 26 | } 27 | } 28 | 29 | module.exports = { 30 | softlink 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/create.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const ui = require('../ui') 5 | const fs = require('fs') 6 | const path = require('path') 7 | const execSync = require('child_process').execSync 8 | const log = require('../log') 9 | const isEmpty = require('../utils/isEmpty') 10 | 11 | const creator = require('../generator/creator') 12 | const util = require('../util') 13 | 14 | const TAG = '[create]' 15 | 16 | module.exports = program => { 17 | program 18 | .command('create') 19 | .option('-n, --modulename [name]', '名称') 20 | .option('-r, --repo [gitrepo]') 21 | .option('-b, --branch [gitbranch]') 22 | .option('-R, --no-repo') 23 | .description('创建flutter module') 24 | .action(async function () { 25 | const cmd = arguments[arguments.length - 1] 26 | log.info(TAG, 'creating flutter module.') 27 | 28 | let moduleName 29 | if (cmd.modulename) { 30 | moduleName = cmd.modulename 31 | } else { 32 | moduleName = await ui.input( 33 | '请输入flutter工程名称:', 34 | 'my_flutter_module' 35 | ) 36 | } 37 | 38 | let enableAndroidX 39 | // flutter stable 1.9.1 40 | if (util.getShortFlutterVersion() == '1.9') { 41 | enableAndroidX = await ui.confirm('是否使用androidX?') 42 | } 43 | 44 | let flutterRepo 45 | if (cmd.repo != undefined && cmd.repo != true) { 46 | flutterRepo = cmd.repo 47 | } else { 48 | flutterRepo = (await ui.input('请输入flutter仓库地址,回车跳过')).trim() 49 | } 50 | 51 | let flutterRepoBranchOrTag 52 | if (cmd.branch) { 53 | flutterRepoBranchOrTag = cmd.branch 54 | } else if (!isEmpty(flutterRepo)) { 55 | flutterRepoBranchOrTag = (await ui.input( 56 | '请输入flutter仓库分支或tag', 57 | 'master' 58 | )).trim() 59 | } 60 | 61 | if (fs.existsSync(path.join(process.cwd(), moduleName))) { 62 | // log.error('[create]', 'module文件夹已存在,请换一个名字') 63 | // const continueInstall = await ui.confirm('工程已存在,是否继续?') 64 | log.error(TAG, '工程已存在') 65 | return 66 | } 67 | await creator.createModule({ 68 | initDir: process.cwd(), 69 | moduleName, 70 | flutterRepo, 71 | flutterRepoBranchOrTag, 72 | androidX: enableAndroidX 73 | }) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /src/commands/init.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const path = require('path') 5 | const YAML = require('js-yaml') 6 | const ui = require('../ui') 7 | const fs = require('fs') 8 | const log = require('../log') 9 | const linker = require('../generator/linker') 10 | const remoteLinker = require('../generator/remoteLinker') 11 | const creator = require('../generator/creator') 12 | const execSync = require('child_process').execSync 13 | const inquirer = require('inquirer') 14 | const isEmpty = require('../utils/isEmpty') 15 | const pathUtils = require('../utils/pathUtils') 16 | const util = require('../util') 17 | 18 | const TAG = '[init]' 19 | 20 | module.exports = program => { 21 | program 22 | .command('init') 23 | .option('-i --interface [interface]') 24 | .description('初始化你的混合项目') 25 | .action(async function () { 26 | log.info(TAG, 'init flutter module.') 27 | await init({ 28 | interface: program.interface 29 | }) 30 | }) 31 | } 32 | 33 | async function init (options) { 34 | // if (!options.interface || options.interface == 'list') { 35 | if (options.interface == 'list') { 36 | interfaceA(options) 37 | return 38 | } 39 | 40 | const moduleName = await ui.input( 41 | '请输入flutter工程名称:', 42 | 'my_flutter_module' 43 | ) 44 | 45 | let enableAndroidX 46 | // flutter stable 1.9.1 47 | if (util.getShortFlutterVersion() == '1.9') { 48 | enableAndroidX = await ui.confirm('是否使用androidX?') 49 | } 50 | 51 | const flutterRepo = (await ui.input('请输入flutter仓库地址,回车跳过')).trim() 52 | let flutterRepoBranchOrTag 53 | if (!isEmpty(flutterRepo)) { 54 | flutterRepoBranchOrTag = (await ui.input( 55 | '请输入flutter仓库分支或tag', 56 | 'master' 57 | )).trim() 58 | } 59 | await creator.createModule({ 60 | initDir: process.cwd(), 61 | moduleName, 62 | flutterRepo, 63 | flutterRepoBranchOrTag, 64 | androidX: enableAndroidX 65 | }) 66 | if (!moduleName) { 67 | log.error(TAG, 'invalid module name') 68 | return 69 | } 70 | 71 | const exist_ios = await ui.confirm('是否存在iOS工程?') 72 | if (exist_ios) { 73 | let ios_path = (await ui.input('iOS工程本地地址,回车跳过', '')).trim() 74 | let input_ios_path = ios_path 75 | if (ios_path.length == 0) { 76 | log.info('[init]', '跳过iOS') 77 | } else { 78 | ios_path = pathUtils.absolutePath(ios_path, process.cwd()) 79 | if (fs.existsSync(ios_path)) { 80 | linker.link({ 81 | flutterPath: path.join(process.cwd(), moduleName), 82 | nativePath: ios_path 83 | }) 84 | } else { 85 | log.error('[init]', `地址不存在:${input_ios_path};绝对路径:${ios_path}`) 86 | } 87 | } 88 | } else { 89 | log.info(TAG, '你可以在创建iOS工程后调用 flutter-boot link来关联flutter') 90 | } 91 | const exist_aos = await ui.confirm('是否存在Android工程?') 92 | if (exist_aos) { 93 | let aos_path = (await ui.input('Android工程本地地址,回车跳过', '')).trim() 94 | let input_aos_path = aos_path 95 | if (aos_path.length == 0) { 96 | log.info('[init]', '跳过Android') 97 | } else { 98 | aos_path = pathUtils.absolutePath(aos_path, process.cwd()) 99 | if (fs.existsSync(aos_path)) { 100 | linker.link({ 101 | flutterPath: path.join(process.cwd(), moduleName), 102 | nativePath: aos_path 103 | }) 104 | } else { 105 | log.error(TAG, `地址不存在:${input_aos_path};绝对路径:${aos_path}`) 106 | } 107 | } 108 | } else { 109 | log.info( 110 | TAG, 111 | '你可以在创建Android工程后调用 flutter-boot link来关联flutter' 112 | ) 113 | } 114 | log.info(TAG, '混合工程初始化完成') 115 | } 116 | 117 | async function interfaceA (options) { 118 | inquirer 119 | .prompt([ 120 | { 121 | type: 'input', 122 | name: 'moduleName', 123 | message: 'module name', 124 | default: 'my_flutter_module' 125 | }, 126 | { 127 | type: 'input', 128 | name: 'iOSPath', 129 | message: 'ios path', 130 | default: '' 131 | }, 132 | { 133 | type: 'input', 134 | name: 'androidPath', 135 | message: 'android path', 136 | default: '' 137 | } 138 | ]) 139 | .then(async answer => { 140 | const moduleName = answer.moduleName 141 | const iOSPath = answer.iOSPath 142 | const androidPath = answer.androidPath 143 | 144 | await creator.createModule({ 145 | initDir: process.cwd(), 146 | moduleName 147 | }) 148 | linker.link({ 149 | flutterPath: path.join(process.cwd(), moduleName), 150 | nativePath: iOSPath 151 | }) 152 | linker.link({ 153 | flutterPath: path.join(process.cwd(), moduleName), 154 | nativePath: androidPath 155 | }) 156 | }) 157 | } 158 | -------------------------------------------------------------------------------- /src/commands/link.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const linker = require('../generator/linker') 4 | const ui = require('../ui') 5 | const log = require('../log') 6 | const fs = require('fs') 7 | const fbconfig = require('../config') 8 | const isEmpty = require('../utils/isEmpty') 9 | const fsutils = require('../utils/fsutils') 10 | const pathUtils = require('../utils/pathUtils') 11 | 12 | const flutterRecorder = require('../utils/flutterRecorder') 13 | 14 | const TAG = '[link]' 15 | 16 | module.exports = program => { 17 | program 18 | .command('link [path]') 19 | .description('本地链接:链接你的本地flutter工程') 20 | .option('-f --force', 'force link') 21 | .action(async function () { 22 | log.info(TAG, 'Running flutter boot link...') 23 | 24 | const cmd = arguments[arguments.length - 1] 25 | const arg0 = arguments[0] 26 | const flutterPath = typeof arg0 === 'string' ? arg0 : undefined 27 | await link({ 28 | force: cmd.force, 29 | flutterPath: flutterPath 30 | }) 31 | }) 32 | } 33 | 34 | async function link (options) { 35 | if (!options.force) { 36 | log.info(TAG, 'checking if installed...') 37 | const localConfig = fbconfig.readLocal(process.cwd()) 38 | if (localConfig && !isEmpty(localConfig.flutterPath)) { 39 | log.error(TAG, '你已经完成本地链接,如需重置请使用flutter-boot link -f') 40 | return 41 | } 42 | } 43 | 44 | const projChecker = fsutils.projectChecker(process.cwd()) 45 | 46 | log.silly(TAG, 'checking current path...') 47 | if (!projChecker.isNative()) { 48 | log.error( 49 | TAG, 50 | '当前目录不是有效的iOS或Android目录,请切换到iOS或Android目录下' 51 | ) 52 | return 53 | } 54 | 55 | let flutterModulePath = 56 | options.flutterPath || flutterRecorder.existedFlutterModule() 57 | if (!flutterModulePath) { 58 | flutterModulePath = (await ui.input('请输入flutter module路径')).trim() 59 | } 60 | let input_flutterModulePath = flutterModulePath 61 | flutterModulePath = pathUtils.absolutePath(flutterModulePath, process.cwd()) 62 | 63 | if (!fs.existsSync(flutterModulePath)) { 64 | log.error( 65 | TAG, 66 | `can not find flutter module path:${input_flutterModulePath}; absolute path:${flutterModulePath}` 67 | ) 68 | return 69 | } 70 | 71 | linker.link({ 72 | flutterPath: flutterModulePath, 73 | nativePath: process.cwd() 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /src/commands/remotelink.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const remoteLinker = require('../generator/remoteLinker') 4 | const ui = require('../ui') 5 | const log = require('../log') 6 | const fs = require('fs') 7 | const isEmpty = require('../utils/isEmpty') 8 | const fsutils = require('../utils/fsutils') 9 | 10 | const fbconfig = require('../config') 11 | 12 | const flutterRecorder = require('../utils/flutterRecorder') 13 | const repo = require('../repo')() 14 | 15 | const TAG = '[remote-link]' 16 | 17 | module.exports = program => { 18 | program 19 | .command('remotelink') 20 | .description('远程连接:链接你的远程flutter工程') 21 | .alias('rl') 22 | .option('-f --force', 'update remote flutter') 23 | .action(async function () { 24 | log.info(TAG, 'Running flutter boot remotelink...') 25 | const cmd = arguments[arguments.length - 1] 26 | await link({ 27 | force: cmd.force 28 | }) 29 | }) 30 | } 31 | 32 | async function link (options) { 33 | const projChecker = fsutils.projectChecker(process.cwd()) 34 | 35 | if (!projChecker.isNative()) { 36 | log.error( 37 | TAG, 38 | '当前目录不是有效的iOS或Android目录,请切换到iOS或Android目录下' 39 | ) 40 | return 41 | } 42 | if (!options.force) { 43 | const remoteConfig = fbconfig.read(process.cwd()) 44 | if ( 45 | remoteConfig && 46 | !isEmpty(remoteConfig.flutterRepo) && 47 | !isEmpty(remoteConfig.flutterRepoBranchOrTag) 48 | ) { 49 | log.info(TAG, '你已经完成远程链接,如需重置请使用flutter-boot rl -f') 50 | return 51 | } 52 | } 53 | 54 | let flutterRepo 55 | const localConfig = fbconfig.readLocal(process.cwd()) 56 | if ( 57 | localConfig && 58 | localConfig.flutterPath && 59 | fs.existsSync(localConfig.flutterPath) 60 | ) { 61 | log.info(TAG, 'fetch remote repo from local') 62 | const url = await repo 63 | .remotePushUrl({ 64 | baseDir: localConfig.flutterPath 65 | }) 66 | .catch(e => { 67 | return undefined 68 | }) 69 | flutterRepo = url 70 | if (flutterRepo) { 71 | log.info(TAG, `repo path is:${flutterRepo}`) 72 | } else { 73 | log.info(TAG, 'local flutter is not a git repo') 74 | } 75 | } else { 76 | log.info(TAG, "haven't done local-link") 77 | log.info(TAG, 'do remote-link') 78 | } 79 | 80 | if (!flutterRepo) { 81 | flutterRepo = (await ui.input('请输入flutter仓库地址')).trim() 82 | } 83 | 84 | const flutterRepoBranchOrTag = (await ui.input( 85 | '请输入flutter仓库分支或tag', 86 | 'master' 87 | )).trim() 88 | if (isEmpty(flutterRepo) || isEmpty(flutterRepoBranchOrTag)) { 89 | log.error(TAG, 'flutter仓库信息为空,请重试') 90 | return 91 | } 92 | 93 | await remoteLinker.link({ 94 | nativePath: process.cwd(), 95 | flutterRepo, 96 | flutterRepoBranchOrTag, 97 | update: true 98 | }) 99 | } 100 | 101 | async function update () { 102 | const config = fbconfig.read(process.cwd()) 103 | if (!config.flutterRepo || !config.flutterRepoBranchOrTag) { 104 | log.error(TAG, '未指定远程flutter依赖,你可以运行flutter-boot remotelink') 105 | } 106 | await remoteLinker.update( 107 | Object.assign({}, config, { 108 | nativePath: process.cwd() 109 | }) 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /src/commands/update.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const updator = require('../generator/updator') 4 | const fsutils = require('../utils/fsutils') 5 | const log = require('../log') 6 | const TAG = '[update]' 7 | 8 | module.exports = program => { 9 | program 10 | .command('update') 11 | .description('更新远程flutter') 12 | .action(function () { 13 | log.info(TAG, 'Running flutter boot update...') 14 | update() 15 | }) 16 | } 17 | 18 | function update () { 19 | const projChecker = fsutils.projectChecker(process.cwd()) 20 | 21 | if (!projChecker.isNative()) { 22 | log.error( 23 | TAG, 24 | '当前目录不是有效的iOS或Android目录,请切换到iOS或Android目录下' 25 | ) 26 | return 27 | } 28 | 29 | updator.update({ 30 | nativePath: process.cwd() 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/use.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const path = require('path') 5 | const ui = require('../ui') 6 | const _use = require('../generator/use.js') 7 | const config = require('../config.js') 8 | const fsutils = require('../utils/fsutils') 9 | const log = require('../log') 10 | 11 | const TAG = '[use]' 12 | module.exports = program => { 13 | program 14 | .command('use') 15 | .description('集成插件') 16 | .action(async function () { 17 | const curPath = process.cwd() 18 | const projChecker = fsutils.projectChecker(curPath) 19 | 20 | if (!projChecker.isNative()) { 21 | log.error( 22 | TAG, 23 | '当前目录不是有效的iOS或Android目录,请切换到iOS或Android目录下' 24 | ) 25 | return 26 | } 27 | 28 | const answer = await ui.list('选择需要集成的插件:', [ 29 | { 30 | name: 'FlutterBoost', 31 | value: 'FlutterBoost' 32 | } 33 | // { 34 | // name: 'Router', 35 | // value: 'Router' 36 | // } 37 | ]) 38 | const supportList = _use.supportList(answer) 39 | let version 40 | if (supportList) { 41 | version = await ui.list( 42 | '选择版本号', 43 | supportList.map(v => { 44 | return { 45 | name: v, 46 | value: v 47 | } 48 | }) 49 | ) 50 | } 51 | const loadConfig = config.readLocal(process.cwd()) 52 | if (!loadConfig.flutterPath) { 53 | log.error(TAG, '找不到flutter路径,请运行flutter-boot link') 54 | return 55 | } 56 | await _use.use({ 57 | depName: answer, 58 | version, 59 | flutterPath: loadConfig.flutterPath, 60 | nativePath: curPath 61 | }) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | class fbconfig { 5 | configPath (nativePath) { 6 | return path.join(nativePath, 'fbConfig.json') 7 | } 8 | localConfigPath (projectPath) { 9 | return path.join(projectPath, 'fbConfig.local.json') 10 | } 11 | read (nativePath) { 12 | const _configPath = this.configPath(nativePath) 13 | if (!fs.existsSync(_configPath)) { 14 | // fs.writeFileSync(_configPath, '') 15 | return {} 16 | } 17 | const config = fs.readFileSync(_configPath, 'utf-8') 18 | return JSON.parse(config) 19 | } 20 | update (nativePath, config) { 21 | config = Object.assign(this.read(nativePath), config) 22 | fs.writeFileSync( 23 | this.configPath(nativePath), 24 | JSON.stringify(config, null, '\t') 25 | ) 26 | } 27 | readLocal (projectPath) { 28 | const _localConfigPath = this.localConfigPath(projectPath) 29 | if (!fs.existsSync(_localConfigPath)) { 30 | // fs.writeFileSync(_localConfigPath, '') 31 | return {} 32 | } 33 | const config = fs.readFileSync(_localConfigPath, 'utf-8') 34 | return JSON.parse(config) 35 | } 36 | 37 | updateLocal (projectPath, config) { 38 | config = Object.assign(this.readLocal(projectPath), config) 39 | fs.writeFileSync( 40 | this.localConfigPath(projectPath), 41 | JSON.stringify(config, null, '\t') 42 | ) 43 | } 44 | 45 | updateLocalIOSPath (projectPath, iOSPath) { 46 | this.updateLocal(projectPath, { 47 | iOSPath: iOSPath 48 | }) 49 | } 50 | 51 | updateLocalAndroidPath (projectPath, androidPath) { 52 | this.updateLocal(projectPath, { 53 | androidPath: androidPath 54 | }) 55 | } 56 | 57 | updateLocalFlutterPath (projectPath, flutterPath) { 58 | this.updateLocal(projectPath, { 59 | flutterPath: path.resolve(projectPath, flutterPath) 60 | }) 61 | } 62 | } 63 | 64 | module.exports = new fbconfig() 65 | -------------------------------------------------------------------------------- /src/generator/creator.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ui = require('../ui') 3 | const fs = require('fs') 4 | const log = require('../log') 5 | const { recordFlutterModule } = require('../utils/flutterRecorder') 6 | const { generateShellProject } = require('./shellProject') 7 | const execSync = require('child_process').execSync 8 | const repo = require('../repo')() 9 | const TAG = '[create]' 10 | const del = require('del') 11 | const fsutils = require('../utils/fsutils') 12 | 13 | class creator { 14 | async createModule (options) { 15 | // if (fs.existsSync(path.join(process.cwd(), 'pubspec.yaml'))) { 16 | // recordFlutterModule(process.cwd()) 17 | // return 18 | // } 19 | await this.cleanGit(options) 20 | const moduleName = await this.createFlutterModule(options) 21 | const flutterPath = path.join(options.initDir, moduleName) 22 | this.prepareGitIgnore(flutterPath) 23 | await this.createGit(options) 24 | return moduleName 25 | } 26 | 27 | async createFlutterModule (options) { 28 | const initDir = options.initDir 29 | const moduleName = options.moduleName 30 | const androidX = options.androidX 31 | log.info(TAG, 'creating flutter module') 32 | let createCmd = 'flutter create' 33 | if (androidX) { 34 | createCmd += ' --androidx' 35 | } 36 | createCmd += (' -t module ' + moduleName.toLowerCase()) 37 | execSync(createCmd, { 38 | stdio: 'inherit' 39 | }) 40 | recordFlutterModule(path.join(initDir, moduleName)) 41 | log.info(TAG, 'flutter module created') 42 | generateShellProject(path.join(initDir, moduleName)) 43 | log.info(TAG, 'native shell project created') 44 | return moduleName 45 | } 46 | 47 | async cleanGit (options) { 48 | log.info(TAG, 'clean flutter module git') 49 | const initDir = options.initDir 50 | const moduleName = options.moduleName 51 | const flutterRepo = options.flutterRepo 52 | const flutterRepoBranchOrTag = options.flutterRepoBranchOrTag 53 | const flutterPath = path.join(initDir, moduleName) 54 | 55 | if ( 56 | !flutterRepo || 57 | flutterRepo.length == 0 || 58 | !flutterRepoBranchOrTag || 59 | flutterRepoBranchOrTag.length == 0 60 | ) { 61 | log.info('[create]', 'done: no git info') 62 | return 63 | } 64 | path.resolve() 65 | 66 | await repo 67 | .clone({ 68 | baseDir: flutterPath, 69 | distUrl: flutterRepo, 70 | version: flutterRepoBranchOrTag 71 | }) 72 | .catch(() => {}) 73 | .then(() => { 74 | const dirfiles = fs.readdirSync(flutterPath) 75 | if (dirfiles.length == 1) { 76 | log.info(TAG, 'empty git, remove for git init') 77 | del.sync([flutterPath], { 78 | dot: true, 79 | onlyFiles: false, 80 | absolute: true, 81 | force: true 82 | }) 83 | options.emptyGit = true 84 | } else { 85 | log.info(TAG, 'non-empty git, remove all files') 86 | del.sync([flutterPath + '/**/*', '!' + flutterPath + '/.git{,/**}'], { 87 | dot: true, 88 | onlyFiles: false, 89 | absolute: true, 90 | force: true 91 | }) 92 | return repo.commitAll({ 93 | baseDir: flutterPath, 94 | msg: 'clean' 95 | }) 96 | } 97 | }) 98 | } 99 | 100 | async createGit (options) { 101 | log.info(TAG, 'init flutter module git') 102 | const initDir = options.initDir 103 | const moduleName = options.moduleName 104 | const flutterRepo = options.flutterRepo 105 | const flutterRepoBranchOrTag = options.flutterRepoBranchOrTag 106 | const flutterPath = path.join(initDir, moduleName) 107 | 108 | if ( 109 | !flutterRepo || 110 | flutterRepo.length == 0 || 111 | !flutterRepoBranchOrTag || 112 | flutterRepoBranchOrTag.length == 0 113 | ) { 114 | log.info('[create]', 'done: no git info') 115 | return 116 | } 117 | if (options.emptyGit) { 118 | await repo.init({ 119 | baseDir: path.join(initDir, moduleName), 120 | distUrl: flutterRepo, 121 | version: flutterRepoBranchOrTag 122 | }) 123 | } else { 124 | repo.commitAll({ 125 | baseDir: flutterPath, 126 | msg: 'init flutter' 127 | }) 128 | } 129 | log.info('[create]', 'done: init flutter module git') 130 | } 131 | 132 | gitignore (flutterPath) { 133 | return path.join(flutterPath, '.gitignore') 134 | } 135 | prepareGitIgnore (flutterPath) { 136 | log.silly(TAG, 'prepare gitignore') 137 | const _gitignore = this.gitignore(flutterPath) 138 | if (!fs.existsSync(_gitignore)) { 139 | fs.writeFileSync(_gitignore, '.ios/Flutter/engine') 140 | } else { 141 | let filecontent = fs.readFileSync(_gitignore, 'utf-8') 142 | // ignore string likes .iosSomePath 143 | if (filecontent.indexOf('.ios') >= 0) { 144 | filecontent = filecontent.replace('.ios', '.ios/Flutter/engine') 145 | } 146 | fs.writeFileSync(_gitignore, filecontent) 147 | fsutils.addOrReplaceContent(_gitignore, /\nios\n/g, '\nios\n') 148 | fsutils.addOrReplaceContent(_gitignore, /\nandroid\n/g, '\nandroid\n') 149 | } 150 | } 151 | } 152 | 153 | module.exports = new creator() 154 | -------------------------------------------------------------------------------- /src/generator/linker.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const log = require('../log') 5 | const fsutils = require('../utils/fsutils') 6 | const fsconfig = require('../config') 7 | const execSync = require('child_process').execSync 8 | const util = require('../util') 9 | 10 | const TAG = '[link]' 11 | 12 | class BaseLinker { 13 | link (options) { 14 | const nativePath = options.nativePath 15 | const flutterPath = options.flutterPath 16 | 17 | log.silly(TAG, 'checking platform...') 18 | let linker 19 | const projChecker = fsutils.projectChecker(nativePath) 20 | if (projChecker.isAndroid()) { 21 | linker = require('../android/link.js') 22 | fsconfig.updateLocalFlutterPath(nativePath, flutterPath) 23 | } else if (projChecker.isIOS()) { 24 | linker = this.getIOSLinker() 25 | fsconfig.updateLocalFlutterPath(nativePath, flutterPath) 26 | } else { 27 | log.error(TAG, '不在native工程内') 28 | process.exit(1) 29 | } 30 | log.silly(TAG, 'linking...') 31 | linker.link(options) 32 | log.silly(TAG, 'link process finished') 33 | log.silly(TAG, 'run packages get...') 34 | execSync('flutter packages get', { 35 | stdio: 'inherit', 36 | cwd: options.flutterPath 37 | }) 38 | log.info(TAG, 'link success') 39 | } 40 | 41 | getIOSLinker () { 42 | let version = util.getShortFlutterVersion() 43 | if (version.startsWith('1.5')) { 44 | return require('../ios/1.5/link.js') 45 | } else if (version.startsWith('1.9')) { 46 | return require('../ios/1.9/link.js') 47 | } 48 | } 49 | } 50 | 51 | module.exports = new BaseLinker() 52 | -------------------------------------------------------------------------------- /src/generator/remoteLinker.js: -------------------------------------------------------------------------------- 1 | const repo = require('../repo')() 2 | const fs = require('fs') 3 | const path = require('path') 4 | const fbconfig = require('../config') 5 | const updator = require('./updator') 6 | const log = require('../log') 7 | 8 | const TAG = '[remote-link]' 9 | 10 | class RemoteLinker { 11 | async link (options) { 12 | const flutterRepo = options.flutterRepo 13 | const flutterRepoBranchOrTag = options.flutterRepoBranchOrTag 14 | fbconfig.update(process.cwd(), { 15 | flutterRepo, 16 | flutterRepoBranchOrTag 17 | }) 18 | if (options.update) { 19 | log.info(TAG, 'auto update after remote link') 20 | await updator.update(options) 21 | } 22 | } 23 | } 24 | 25 | module.exports = new RemoteLinker() 26 | -------------------------------------------------------------------------------- /src/generator/shellProject.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const log = require('../log') 5 | const path = require('path') 6 | const fsutils = require('../utils/fsutils') 7 | const execSync = require('child_process').execSync 8 | const ANDROID_SHELL = 'android_shell' 9 | const IOS_SHELL = 'ios_shell' 10 | const TAG = '[ShellProject]' 11 | 12 | function androidShellProject (flutterPath) { 13 | return path.join(flutterPath, ANDROID_SHELL) 14 | } 15 | 16 | function iosShellProject(flutterPath) { 17 | return path.join(flutterPath, IOS_SHELL) 18 | } 19 | 20 | /** 21 | * 生成端壳子工程:用于产物打包等 22 | * @param {} flutterPath 23 | */ 24 | function generateShellProject(flutterPath) { 25 | execSync('flutter make-host-app-editable', { 26 | cwd: flutterPath, 27 | stdio: 'inherit' 28 | }) 29 | fs.renameSync(path.join(flutterPath, 'android'), androidShellProject(flutterPath)) 30 | fs.renameSync(path.join(flutterPath, 'ios'), iosShellProject(flutterPath)) 31 | configAndroidShellProject(flutterPath) 32 | configIosShellProject(flutterPath) 33 | } 34 | 35 | function configAndroidShellProject(flutterPath) { 36 | if (!fs.existsSync(androidShellProject(flutterPath))) { 37 | return 38 | } 39 | // 默认创建软链接到shell工程 40 | if (!fs.existsSync(path.join(flutterPath, 'android'))) { 41 | log.info(TAG, `create android softlink to ${androidShellProject(flutterPath)}`) 42 | fsutils.createSoftLink(path.join(flutterPath, 'android'), androidShellProject(flutterPath)) 43 | } 44 | } 45 | 46 | function configIosShellProject(flutterPath) { 47 | if (!fs.existsSync(iosShellProject(flutterPath))) { 48 | return 49 | } 50 | // 默认创建软链接到shell工程 51 | if (!fs.existsSync(path.join(flutterPath, 'ios'))) { 52 | log.info(TAG, `create ios softlink to ${iosShellProject(flutterPath)}`) 53 | fsutils.createSoftLink(path.join(flutterPath, 'ios'), iosShellProject(flutterPath)) 54 | } 55 | } 56 | 57 | module.exports = { 58 | generateShellProject 59 | } -------------------------------------------------------------------------------- /src/generator/updator.js: -------------------------------------------------------------------------------- 1 | const repo = require('../repo')() 2 | const fs = require('fs') 3 | const path = require('path') 4 | const fbconfig = require('../config') 5 | const log = require('../log') 6 | const execSync = require('child_process').execSync 7 | 8 | const TAG = '[update]' 9 | const LocalFlutterFolder = '.fbflutter' 10 | log.level = 'silly' 11 | class Updator { 12 | remoteFlutterInLocal () { 13 | return path.join(this.nativePath, LocalFlutterFolder) 14 | } 15 | remoteFlutterGitInLocal () { 16 | return path.join(this.nativePath, LocalFlutterFolder, '.git') 17 | } 18 | remoteFlutterPubspecInLocal () { 19 | return path.join(this.nativePath, LocalFlutterFolder, 'pubspec.yaml') 20 | } 21 | 22 | async update (options) { 23 | log.info(TAG, 'updateing...') 24 | this.nativePath = options.nativePath 25 | 26 | const config = fbconfig.read(options.nativePath) 27 | if (!config.flutterRepo || !config.flutterRepoBranchOrTag) { 28 | log.error(TAG, '未指定远程flutter依赖,你可以运行flutter-boot remotelink') 29 | } 30 | const flutterRepo = config.flutterRepo 31 | const flutterRepoBranchOrTag = config.flutterRepoBranchOrTag 32 | if (!flutterRepo) { 33 | log.error(TAG, '请指定你的flutter业务代码仓库') 34 | return 35 | } 36 | if (!flutterRepoBranchOrTag) { 37 | log.error(TAG, '请指定你的flutter业务代码仓库的分支或版本号') 38 | return 39 | } 40 | const _remoteFlutterInLocal = this.remoteFlutterInLocal() 41 | const _remoteFlutterGitInLocal = this.remoteFlutterGitInLocal() 42 | if (!fs.existsSync(_remoteFlutterGitInLocal)) { 43 | log.silly(TAG, "flutter module hasn't checkout, do checkout") 44 | // if(fs.existsSync(_remoteFlutterInLocal)) { 45 | 46 | // } 47 | await this.checkout(flutterRepo, flutterRepoBranchOrTag) 48 | } else { 49 | await repo 50 | .getCurrentBranch({ baseDir: _remoteFlutterInLocal }) 51 | .then(branch => { 52 | if (branch == flutterRepoBranchOrTag) { 53 | return repo.update({ 54 | baseDir: _remoteFlutterInLocal 55 | }) 56 | } else { 57 | log.silly(TAG, `branch ${branch} doesn't match config, do switch`) 58 | return repo.switchBranch({ 59 | baseDir: _remoteFlutterInLocal, 60 | branchUrl: flutterRepoBranchOrTag 61 | }) 62 | } 63 | }) 64 | } 65 | if (!fs.existsSync(this.remoteFlutterPubspecInLocal())) { 66 | log.error(TAG, '不存在pubspec文件') 67 | return 68 | } 69 | execSync('flutter packages get', { 70 | cwd: _remoteFlutterInLocal, 71 | stdio: 'inherit' 72 | }) 73 | log.info(TAG, 'updated!') 74 | } 75 | 76 | checkout (flutterRepo, flutterRepoBranchOrTag) { 77 | const _remoteFlutterInLocal = this.remoteFlutterInLocal() 78 | if (!fs.existsSync(_remoteFlutterInLocal)) { 79 | fs.mkdirSync(_remoteFlutterInLocal) 80 | } 81 | return repo.checkout({ 82 | distUrl: flutterRepo, 83 | baseDir: _remoteFlutterInLocal, 84 | version: flutterRepoBranchOrTag 85 | }) 86 | } 87 | } 88 | 89 | module.exports = new Updator() 90 | -------------------------------------------------------------------------------- /src/generator/use.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const pubspecHelper = require('../utils/pubspecHelper') 4 | const marketConfigHelper = require('../utils/marketConfigHelper') 5 | const execSync = require('child_process').execSync 6 | const fsutils = require('../utils/fsutils') 7 | const execUtils = require('../utils/execUtils') 8 | const log = require('../log') 9 | 10 | const TAG = '[use]' 11 | 12 | class GeneratorUse { 13 | async use (options) { 14 | const depName = options.depName 15 | const config = this.depConfig(depName) 16 | const version = options.version 17 | ? config.support_version[options.version] 18 | : undefined 19 | const flutterPath = options.flutterPath 20 | const nativePath = options.nativePath 21 | const pubspecConfig = marketConfigHelper.pubspec(config, version) 22 | const androidConfig = marketConfigHelper.android(config, version) 23 | const iosConfig = marketConfigHelper.ios(config, version) 24 | const dartConfig = marketConfigHelper.dart(config, version) 25 | const gradleConfig = marketConfigHelper.gradle(config, version) 26 | pubspecHelper.addDep(flutterPath, pubspecConfig) 27 | execSync('flutter packages get', { 28 | stdio: 'inherit', 29 | cwd: options.flutterPath 30 | }) 31 | 32 | const projChecker = fsutils.projectChecker(nativePath) 33 | 34 | const vEnv = { 35 | FLUTTER_PATH: flutterPath, 36 | PROJECT_NAME: projChecker.getProjectName() 37 | } 38 | 39 | if (projChecker.isAndroid()) { 40 | vEnv.ANDROID_PATH = nativePath 41 | let targetPath = androidConfig.targetPath 42 | targetPath = marketConfigHelper.resolveVar(nativePath, vEnv) 43 | log.silly(TAG, `android target path:${targetPath}`) 44 | await this.copyTpl('android', depName, targetPath) 45 | 46 | if (gradleConfig) { 47 | this.injectGradle(projChecker.gradlePath(), gradleConfig) 48 | } 49 | } else if (projChecker.isIOS()) { 50 | vEnv.IOS_PATH = nativePath 51 | let targetPath = iosConfig.targetPath 52 | targetPath = marketConfigHelper.resolveVar(nativePath, vEnv) 53 | log.silly(TAG, `ios target path:${targetPath}`) 54 | await this.copyTpl('ios', depName, targetPath) 55 | execUtils.execRubySync('add_ios_tpl.rb', [ 56 | projChecker.xcodeproj(), 57 | targetPath 58 | ]) 59 | } 60 | if (dartConfig) { 61 | let targetPath = dartConfig.targetPath 62 | targetPath = marketConfigHelper.resolveVar(flutterPath, vEnv) 63 | log.silly(TAG, `flutter target path:${targetPath}`) 64 | await this.copyTpl('dart', depName, targetPath) 65 | } 66 | 67 | if (projChecker.isIOS()) { 68 | execSync('pod update --no-repo-update', { 69 | stdio: 'inherit', 70 | cwd: nativePath 71 | }) 72 | } 73 | } 74 | 75 | depConfig (depName) { 76 | return JSON.parse( 77 | fs.readFileSync( 78 | path.join(process.env.FB_DIR, 'src/market', depName, 'config.json'), 79 | 'utf-8' 80 | ) 81 | ) 82 | } 83 | 84 | supportList (depName) { 85 | const config = this.depConfig(depName) 86 | if (config.support_version) { 87 | return Object.keys(config.support_version) 88 | } else { 89 | return undefined 90 | } 91 | } 92 | 93 | tplfile (platform, packageName) { 94 | const tplPath = path.join( 95 | process.env.FB_DIR, 96 | 'src', 97 | 'market', 98 | packageName, 99 | 'tpl', 100 | platform 101 | ) 102 | log.silly(TAG, `tpl path:${tplPath}`) 103 | return tplPath 104 | } 105 | 106 | async copyTpl (platform, packageName, targetPath) { 107 | await fsutils 108 | .copyFolderAsync(this.tplfile(platform, packageName), targetPath, { 109 | mkdes: true 110 | }) 111 | .catch(e => { 112 | log.error(TAG, `copy error:${e}`) 113 | }) 114 | } 115 | 116 | injectGradle (gradlePath, depStr) { 117 | log.info(TAG, 'inject gradle') 118 | if (!fs.existsSync(gradlePath)) { 119 | log.error(TAG, `未找到build.gradle:${gradlePath}`) 120 | return 121 | } 122 | const start = '[FLUTTER_DEPENDENCY_END]' 123 | const injection = this.gradleInjection(depStr) 124 | fsutils.replaceContent(gradlePath, injection, '') 125 | if ( 126 | fsutils.addOrReplaceContentBySurround(gradlePath, start, '', injection) 127 | ) { 128 | log.info(TAG, `injection path: ${gradlePath}, content: ${injection}`) 129 | } else { 130 | log.info(TAG, `inject failed, path: ${gradlePath}`) 131 | } 132 | } 133 | 134 | gradleInjection (str) { 135 | return `\n//[FLUTTER_MARKET_START]\n${str} \n//[FLUTTER_MARKET_END]\n` 136 | } 137 | } 138 | 139 | module.exports = new GeneratorUse() 140 | -------------------------------------------------------------------------------- /src/ios/1.5/fbpodhelper.rb: -------------------------------------------------------------------------------- 1 | # https://github.com/CocoaPods/Core/blob/master/lib/cocoapods-core/podfile/dsl.rb#L256 2 | 3 | $REGISTERED_DEPENDENCIES = Hash.new 4 | $REGISTER_MODE = true 5 | 6 | fbFlutterPath = '' 7 | fbJsonPath = File.join(File.dirname(__FILE__), 'fbconfig.local.json') 8 | if File.exists? fbJsonPath 9 | fbjson = File.read(fbJsonPath) 10 | fbConfig = JSON.parse(fbjson) 11 | if fbConfig['flutterPath'] 12 | fbFlutterPath = fbConfig['flutterPath'] 13 | end 14 | end 15 | if fbFlutterPath == nil 16 | result = `flutter-boot update` 17 | fbFlutterPath = '.fbflutter' 18 | end 19 | 20 | flutter_application_path = fbFlutterPath 21 | eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding) 22 | 23 | $REGISTER_MODE = false 24 | -------------------------------------------------------------------------------- /src/ios/1.5/link.js: -------------------------------------------------------------------------------- 1 | const _execSync = require('child_process').execSync 2 | const fs = require('fs') 3 | const path = require('path') 4 | const log = require('../../log') 5 | const fsutils = require('../../utils/fsutils') 6 | const exit = require('../../utils/exit') 7 | 8 | const { softlink } = require('../softlink') 9 | 10 | const TAG = '[link-1.5]' 11 | const XCODEPROJ_SUFFIX = '.xcodeproj' 12 | const FLAG = '# Created by flutter-boot' 13 | 14 | class linker { 15 | linker () { 16 | this.flutterPath = '' 17 | this.nativePath = '' 18 | this.projectName = '' 19 | } 20 | 21 | setOptions (options) { 22 | this.flutterPath = options.flutterPath 23 | this.nativePath = options.nativePath 24 | this.projectName = options.projectName 25 | } 26 | 27 | fbpodhelperPath () { 28 | return path.join(this.nativePath, 'fbpodhelper.rb') 29 | } 30 | 31 | podfile () { 32 | return path.join(this.nativePath, 'Podfile') 33 | } 34 | 35 | xcodeproj () { 36 | return path.join(this.nativePath, this.projectName + XCODEPROJ_SUFFIX) 37 | } 38 | pbxproj () { 39 | return path.join(this.xcodeproj(), 'project.pbxproj') 40 | } 41 | 42 | gitignore () { 43 | return path.join(this.nativePath, '.gitignore') 44 | } 45 | 46 | execSync (command, option) { 47 | _execSync( 48 | command, 49 | Object.assign( 50 | {}, 51 | { 52 | stdio: 'inherit', 53 | cwd: this.nativePath 54 | }, 55 | option 56 | ) 57 | ) 58 | } 59 | 60 | link (options) { 61 | this.nativePath = options.nativePath 62 | this.flutterPath = options.flutterPath 63 | this.projectName = this.getProjectName() 64 | this.preparePodfile() 65 | this.preparePodHelper() 66 | this.inject_xcode(this.projectName) 67 | this.addRunnerTargetToProject() 68 | this.addRunnerTargetToPodfile() 69 | this.injectGitIgnore() 70 | this.execSync('pod install') 71 | softlink(options) 72 | } 73 | 74 | linkRemote () {} 75 | 76 | preparePodfile () { 77 | let hasPodfile = fs.existsSync(this.podfile()) 78 | if (hasPodfile) { 79 | this.checkPostInstallHook() 80 | } else { 81 | log.silly(TAG, 'prepare podfile') 82 | try { 83 | this.execSync('pod init') 84 | fsutils.addOrReplaceContentByReg( 85 | this.podfile(), 86 | '# platform', 87 | 'platform' 88 | ) 89 | } catch (e) { 90 | exit(TAG, `error when pod init, code: ${e}`, -1) 91 | } 92 | } 93 | } 94 | 95 | preparePodHelper () { 96 | fs.copyFileSync( 97 | path.join(process.env.FB_DIR, 'src', 'ios', '1.5', 'fbpodhelper.rb'), 98 | path.join(this.nativePath, 'fbpodhelper.rb') 99 | ) 100 | } 101 | 102 | getProjectName () { 103 | const dirfiles = fs.readdirSync(this.nativePath) 104 | let projectFullName = '' 105 | dirfiles.every(filename => { 106 | if (filename.endsWith(XCODEPROJ_SUFFIX)) { 107 | projectFullName = filename 108 | return false 109 | } 110 | return true 111 | }) 112 | const projectName = projectFullName.substring( 113 | 0, 114 | projectFullName.indexOf(XCODEPROJ_SUFFIX) 115 | ) 116 | return projectName 117 | } 118 | 119 | inject_xcode (project_name) { 120 | const xcodeprojPath = this.xcodeproj() 121 | const pbxproj = this.pbxproj() 122 | const xcodeAlreadyUpdated = fs 123 | .readFileSync(pbxproj, 'utf8') 124 | .includes('Flutter Build Script') 125 | if (xcodeAlreadyUpdated) { 126 | log.info(TAG, 'xcode already settled') 127 | return 128 | } 129 | 130 | log.silly(TAG, 'updating xcode setting') 131 | const task = [ 132 | 'gem install xcodeproj', 133 | `ruby ${path.join( 134 | process.env.FB_DIR, 135 | 'src/scripts/inject_flutter_script.rb' 136 | )} ${xcodeprojPath}` 137 | ].join('&&') 138 | 139 | try { 140 | this.execSync(task) 141 | } catch (e) { 142 | exit(TAG, `error when update xcode setting, code: ${code}`, -1) 143 | } 144 | } 145 | 146 | // 在工程中创建Runner Target,以便执行flutter run时可以直接运行 147 | addRunnerTargetToProject () { 148 | log.silly(TAG, 'prepare Runner target') 149 | const xcodeprojPath = this.xcodeproj() 150 | const projectName = this.getProjectName() 151 | const task = `ruby ${path.join( 152 | process.env.FB_DIR, 153 | 'src/scripts/duplicate_target.rb' 154 | )} ${xcodeprojPath} ${projectName} Runner` 155 | 156 | try { 157 | this.execSync(task) 158 | } catch (e) { 159 | exit(TAG, 'error when add Runner target to project.' + e, -1) 160 | } 161 | } 162 | 163 | addRunnerTargetToPodfile () { 164 | log.silly(TAG, 'prepare Runner target to podfile') 165 | let podfilePath = this.podfile() 166 | let rawdata = fs.readFileSync(podfilePath, 'utf8') 167 | let start = this.getTargetNameStr(this.projectName) 168 | let end = 'end' 169 | let runnerStart = this.getTargetNameStr('Runner') 170 | if (!rawdata.includes(start)) { 171 | exit(TAG, 'no main target found in Podfile', -1) 172 | } else if (rawdata.includes(runnerStart)) { 173 | log.info(TAG, 'Runner target found in Podfile') 174 | } else { 175 | let defaultTargetStr = this.readPodfile(rawdata, this.projectName) 176 | let reg = new RegExp(this.projectName, 'g') 177 | let replacedTargetStr = defaultTargetStr.replace(reg, 'Runner') 178 | let startIndex = replacedTargetStr.indexOf(start) 179 | let endIndex = replacedTargetStr.lastIndexOf(end) 180 | let str = 181 | "\n eval(File.read(File.join(File.dirname(__FILE__), 'fbpodhelper.rb')), binding)\n" 182 | let targetContent = 183 | FLAG + '\n' + replacedTargetStr.substring(startIndex, endIndex) + str 184 | let injection = '\n' + targetContent + '\n' + end + '\n' 185 | fs.appendFileSync(podfilePath, injection) 186 | } 187 | } 188 | 189 | getTargetNameStr (name) { 190 | return "target '" + name + "' do" 191 | } 192 | 193 | readPodfile (rawdata, targetName) { 194 | let stk = [] 195 | let startStr = 'target' 196 | let endStr = 'end' 197 | let retStr = '' 198 | let isInTarget = false 199 | let i = rawdata.indexOf(startStr) 200 | 201 | while (i < rawdata.length) { 202 | let startIndex = rawdata.indexOf(startStr, i) 203 | let endIndex = rawdata.indexOf(endStr, i) 204 | if (startIndex > 0 && startIndex < endIndex) { 205 | if ( 206 | stk.length == 0 && 207 | rawdata 208 | .substring(startIndex + startStr.length) 209 | .trim() 210 | .startsWith("'" + targetName) 211 | ) { 212 | isInTarget = true 213 | } 214 | stk.push({ 215 | value: startStr, 216 | pos: startIndex 217 | }) 218 | i = startIndex + startStr.length 219 | } else if (endIndex > 0) { 220 | if (stk.length < 1) { 221 | exit( 222 | TAG, 223 | 'Podfile contains unmatched target and end, fail to modify Podfile!', 224 | -1 225 | ) 226 | break 227 | } else { 228 | let res = stk.pop() 229 | if (stk.length == 0 && isInTarget) { 230 | retStr = rawdata.substring(res.pos, endIndex + endStr.length) 231 | break 232 | } 233 | } 234 | i = endIndex + endStr.length 235 | } else { 236 | exit(TAG, 'No target or end found in Podfile.', -1) 237 | break 238 | } 239 | } 240 | return retStr 241 | } 242 | 243 | checkPostInstallHook () { 244 | let rawdata = fs.readFileSync(this.podfile(), 'utf8') 245 | if ( 246 | rawdata.includes('post_install do |installer|') && 247 | !rawdata.includes(FLAG) 248 | ) { 249 | log.warn( 250 | TAG, 251 | '`post_install` hook exists, which will rise a `multiple post_install hooks` error. \ 252 | See https://github.com/flutter/flutter/issues/26212 for detail.' 253 | ) 254 | } 255 | } 256 | 257 | injectGitIgnore () { 258 | log.info(TAG, 'inject gitignore') 259 | let realPath = this.gitignore() 260 | const injection = '\nfbConfig.local.json' 261 | if (!fs.existsSync(realPath)) { 262 | fs.writeFileSync(realPath, injection) 263 | } else { 264 | fsutils.addOrReplaceContent(realPath, /\nfbConfig.local.json/g, injection) 265 | } 266 | } 267 | } 268 | 269 | module.exports = new linker() 270 | -------------------------------------------------------------------------------- /src/ios/1.9/fbpodhelper.rb: -------------------------------------------------------------------------------- 1 | # https://github.com/CocoaPods/Core/blob/master/lib/cocoapods-core/podfile/dsl.rb#L256 2 | 3 | $REGISTERED_DEPENDENCIES = Hash.new 4 | $REGISTER_MODE = true 5 | 6 | fbFlutterPath = '' 7 | fbJsonPath = File.join(File.dirname(__FILE__), 'fbconfig.local.json') 8 | if File.exists? fbJsonPath 9 | fbjson = File.read(fbJsonPath) 10 | fbConfig = JSON.parse(fbjson) 11 | if fbConfig['flutterPath'] 12 | fbFlutterPath = fbConfig['flutterPath'] 13 | end 14 | end 15 | if fbFlutterPath == nil 16 | result = `flutter-boot update` 17 | fbFlutterPath = '.fbflutter' 18 | end 19 | 20 | flutter_application_path = fbFlutterPath 21 | require File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') 22 | install_flutter_engine_pod 23 | install_flutter_plugin_pods(flutter_application_path) 24 | install_flutter_application_pod(flutter_application_path) 25 | 26 | $REGISTER_MODE = false 27 | -------------------------------------------------------------------------------- /src/ios/link-common.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/src/ios/link-common.js -------------------------------------------------------------------------------- /src/ios/softlink.js: -------------------------------------------------------------------------------- 1 | const log = require('../log') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const fsutils = require('../utils/fsutils') 5 | const execSync = require('child_process').execSync 6 | const rimraf = require("rimraf"); 7 | 8 | const TAG = '[softlink]' 9 | const IOS_LINK_PATH = 'ios' 10 | 11 | function softlink (options) { 12 | var nativePath = options.nativePath 13 | var flutterPath = path.resolve(nativePath, options.flutterPath) 14 | linkUserProject(flutterPath, nativePath) 15 | exportEnv(flutterPath) 16 | } 17 | 18 | function linkUserProject(flutterPath, nativePath) { 19 | log.info(TAG, 'create soft link') 20 | var iosLinkPath = path.join(flutterPath, IOS_LINK_PATH) 21 | rimraf.sync(iosLinkPath) 22 | fs.mkdirSync(iosLinkPath) 23 | 24 | fs.writeFileSync(path.join(iosLinkPath, 'Podfile')) 25 | fs.writeFileSync(path.join(iosLinkPath, 'fbpodhelper.rb')) 26 | execSync('mkdir -p .ios/Flutter', {cwd: iosLinkPath}) 27 | fs.writeFileSync(path.join(iosLinkPath, '.ios/Flutter', 'podhelper.rb')) 28 | fs.writeFileSync(path.join(iosLinkPath, 'Runner.xcodeproj')) 29 | fs.writeFileSync(path.join(iosLinkPath, 'Runner.xcworkspace')) 30 | fs.writeFileSync(path.join(iosLinkPath, path.basename(nativePath))) 31 | fs.writeFileSync(path.join(iosLinkPath, 'Info.plist')) 32 | fs.writeFileSync(path.join(iosLinkPath, 'fbConfig.local.json')) 33 | 34 | fsutils.createSoftLink(path.join(iosLinkPath, 'Podfile'), path.join(nativePath, 'Podfile')) 35 | fsutils.createSoftLink(path.join(iosLinkPath, 'fbpodhelper.rb'), path.join(nativePath, 'fbpodhelper.rb')) 36 | fsutils.createSoftLink(path.join(iosLinkPath, '.ios/Flutter', 'podhelper.rb'), path.join(flutterPath, '.ios/Flutter', 'podhelper.rb')) 37 | fsutils.createSoftLink(path.join(iosLinkPath, 'Runner.xcodeproj'), path.join(nativePath, path.basename(nativePath) + '.xcodeproj')) 38 | fsutils.createSoftLink(path.join(iosLinkPath, 'Runner.xcworkspace'), path.join(nativePath, path.basename(nativePath) + '.xcworkspace')) 39 | fsutils.createSoftLink(path.join(iosLinkPath, path.basename(nativePath)), path.join(nativePath, path.basename(nativePath))) 40 | fsutils.createSoftLink(path.join(iosLinkPath, 'Info.plist'), path.join(nativePath, path.basename(nativePath), 'Info.plist')) 41 | fsutils.createSoftLink(path.join(iosLinkPath, 'fbConfig.local.json'), path.join(nativePath, 'fbConfig.local.json')) 42 | } 43 | 44 | function exportEnv(flutterPath) { 45 | execSync('export FLUTTER_APPLICATION_PATH=' + flutterPath, {cwd: flutterPath}) 46 | } 47 | 48 | module.exports = { 49 | softlink 50 | } 51 | -------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const util = require('util') 4 | const log = require('npmlog') 5 | 6 | log.stream = process.stdout 7 | 8 | // adapt console.* 9 | const slice = Array.prototype.slice 10 | const oldConsole = { 11 | log: console.log, 12 | info: console.info, 13 | warn: console.warn, 14 | error: console.error 15 | } 16 | 17 | function _apply (args, key) { 18 | args = slice.call(args) 19 | log.record.push({ 20 | level: '', 21 | prefix: '', 22 | message: util.format.apply(util, args) 23 | }) 24 | oldConsole[key].apply(console, args) 25 | } 26 | 27 | console.log = function () { 28 | _apply(arguments, 'log') 29 | } 30 | console.info = function () { 31 | _apply(arguments, 'info') 32 | } 33 | console.warn = function () { 34 | _apply(arguments, 'warn') 35 | } 36 | console.error = function () { 37 | _apply(arguments, 'error') 38 | } 39 | 40 | // adapter 41 | log.Adapter = function (prefix) { 42 | return { 43 | verbose: function () { 44 | return log.verbose.apply( 45 | log, 46 | ['[' + prefix + ']'].concat(slice.call(arguments)) 47 | ) 48 | }, 49 | info: function () { 50 | return log.info.apply( 51 | log, 52 | ['[' + prefix + ']'].concat(slice.call(arguments)) 53 | ) 54 | }, 55 | warn: function () { 56 | return log.warn.apply( 57 | log, 58 | ['[' + prefix + ']'].concat(slice.call(arguments)) 59 | ) 60 | }, 61 | error: function () { 62 | return log.error.apply( 63 | log, 64 | ['[' + prefix + ']'].concat(slice.call(arguments)) 65 | ) 66 | } 67 | } 68 | } 69 | 70 | module.exports = log 71 | -------------------------------------------------------------------------------- /src/market/FlutterBoost/addtpl.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | class AddTPL { 5 | constructor () { 6 | this.insertPoints = [] 7 | } 8 | 9 | replace (options) { 10 | const delegatePath = options.givenDelegateFilePath || this.findDelegate() 11 | var rawdata = fs.readFileSync(delegatePath, 'utf8') 12 | this.insertPoints.forEach(p => { 13 | rawdata.replace(p.search, p.replacement) 14 | }) 15 | } 16 | 17 | findDelegate (projPath) { 18 | const dirfiles = fs.readdirSync(projPath) 19 | const stack = [] 20 | const targetPath = undefined 21 | dirfiles.forEach(filename => { 22 | stack.push(path.join(projPath, filename)) 23 | }) 24 | while (stack.length > 0) { 25 | const curPath = stack.shift() 26 | if (fs.statSync(curPath).isDirectory()) { 27 | stack = stack.concat(fs.readdirSync(projPath)) 28 | } else { 29 | const curname = path.basename(curPath) 30 | if (curname.toLowerCase() == 'appdelegate.m') { 31 | targetPath = curPath 32 | return targetPath 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/market/FlutterBoost/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "support_version": { 3 | "0.1.52": { 4 | "tag": "1" 5 | }, 6 | "1.9.1.hotfix": { 7 | "tag": "2" 8 | } 9 | }, 10 | "1": { 11 | "pubspec": { 12 | "name": "flutter_boost", 13 | "version": "0.1.52" 14 | } 15 | }, 16 | "2": { 17 | "pubspec": { 18 | "name": "flutter_boost", 19 | "version": "0.1.61" 20 | } 21 | }, 22 | "android": { 23 | "targetPath": "${ANDROID_PATH}/app/src/main/java/com/example/fbi" 24 | }, 25 | "ios": { 26 | "targetPath": "${IOS_PATH}/${PROJECT_NAME}/fbi" 27 | }, 28 | "dart": { 29 | "targetPath": "${FLUTTER_PATH}/lib" 30 | }, 31 | "gradle": "compile project(path: ':flutter_boost')" 32 | } -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/android/fb/FBInitializer.java: -------------------------------------------------------------------------------- 1 | package com.example.fbi.fb; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.idlefish.flutterboost.BoostEngineProvider; 7 | import com.idlefish.flutterboost.BoostFlutterEngine; 8 | import com.idlefish.flutterboost.FlutterBoost; 9 | import com.idlefish.flutterboost.Platform; 10 | import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider; 11 | 12 | import java.util.Map; 13 | 14 | import io.flutter.app.FlutterApplication; 15 | import io.flutter.embedding.engine.dart.DartExecutor; 16 | import io.flutter.view.FlutterMain; 17 | 18 | public class FBInitializer { 19 | 20 | public static void init(Application app) { 21 | if(!(app instanceof FlutterApplication)) { 22 | FlutterMain.startInitialization(app); 23 | } 24 | FlutterBoost.init(new Platform() { 25 | 26 | @Override 27 | public Application getApplication() { 28 | return app; 29 | } 30 | 31 | @Override 32 | public boolean isDebug() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public void openContainer(Context context, String url, Map urlParams, int requestCode, 38 | Map exts) { 39 | /// TODO 40 | // open a new activity from flutter 41 | } 42 | 43 | @Override 44 | public IFlutterEngineProvider engineProvider() { 45 | return new BoostEngineProvider() { 46 | @Override 47 | public BoostFlutterEngine createEngine(Context context) { 48 | return new BoostFlutterEngine(context, 49 | new DartExecutor.DartEntrypoint(context.getResources().getAssets(), 50 | FlutterMain.findAppBundlePath(context), "main"), 51 | "/"); 52 | } 53 | }; 54 | } 55 | 56 | @Override 57 | public int whenEngineStart() { 58 | return ANY_ACTIVITY_CREATED; 59 | } 60 | }); 61 | } 62 | } -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/android/fb/FBViewEntry.java: -------------------------------------------------------------------------------- 1 | package com.example.fbi.fb; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Handler; 6 | import android.text.Layout; 7 | import android.view.Gravity; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | import android.widget.PopupWindow; 13 | import android.widget.RelativeLayout; 14 | import android.widget.TextView; 15 | 16 | import com.idlefish.flutterboost.containers.BoostFlutterDefaultActivity; 17 | 18 | public class FBViewEntry { 19 | 20 | public static void setup(Activity activity) { 21 | final Handler handler = new Handler(); 22 | handler.postDelayed(new Runnable() { 23 | @Override 24 | public void run() { 25 | final RelativeLayout context = new RelativeLayout(activity); 26 | context.setBackgroundColor(Color.rgb(27,163,251)); 27 | 28 | final TextView text = new TextView(activity); 29 | text.setTextColor(Color.WHITE); 30 | text.setText("Navigate To Flutter"); 31 | text.setOnClickListener(new View.OnClickListener() { 32 | @Override 33 | public void onClick(View view) { 34 | BoostFlutterDefaultActivity.open(activity, "demo://mypage", null); 35 | } 36 | }); 37 | 38 | final RelativeLayout.LayoutParams tvLayoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 39 | tvLayoutParams.setMargins(15,5,15,5); 40 | 41 | text.setLayoutParams(tvLayoutParams); 42 | context.addView(text); 43 | 44 | final PopupWindow popupWindow = new PopupWindow(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 45 | popupWindow.setOutsideTouchable(false); 46 | popupWindow.showAtLocation(activity.getWindow().getDecorView(), Gravity.BOTTOM,0,150); 47 | } 48 | }, 1000); 49 | } 50 | } -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/android/fb/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Add flutter-boost 3 | 4 | create a MyApplication class if your project do not have a custom application, then add it to AndroidManifest.xml 5 | ``` 6 | android:name=".MyApplication" 7 | ``` 8 | 9 | insert the code below into the "onCreate" method in your application class 10 | ``` 11 | /// initialize the flutter boost 12 | FBInitializer.init(this); 13 | ``` 14 | 15 | insert the code below into the "onCreate" method in your Actvity class 16 | ``` 17 | /// add a button in native activity for navigating to flutter 18 | FBViewEntry.setup(this); 19 | ``` -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/dart/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'my_fb_app.dart'; 4 | 5 | void main() => runApp(MyFlutterBoostApp({ 6 | 'demo://mypage': (String url, Map params, String id) => 7 | MyHomePage(title: '$url $id'), 8 | })); 9 | 10 | class MyApp extends StatelessWidget { 11 | // This widget is the root of your application. 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | title: 'Flutter Demo', 16 | theme: ThemeData( 17 | // This is the theme of your application. 18 | // 19 | // Try running your application with "flutter run". You'll see the 20 | // application has a blue toolbar. Then, without quitting the app, try 21 | // changing the primarySwatch below to Colors.green and then invoke 22 | // "hot reload" (press "r" in the console where you ran "flutter run", 23 | // or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the 24 | // counter didn't reset back to zero; the application is not restarted. 25 | primarySwatch: Colors.blue, 26 | ), 27 | home: MyHomePage(title: 'Flutter Demo Home Page'), 28 | ); 29 | } 30 | } 31 | 32 | class MyHomePage extends StatefulWidget { 33 | MyHomePage({Key key, this.title}) : super(key: key); 34 | 35 | // This widget is the home page of your application. It is stateful, meaning 36 | // that it has a State object (defined below) that contains fields that affect 37 | // how it looks. 38 | 39 | // This class is the configuration for the state. It holds the values (in this 40 | // case the title) provided by the parent (in this case the App widget) and 41 | // used by the build method of the State. Fields in a Widget subclass are 42 | // always marked "final". 43 | 44 | final String title; 45 | 46 | @override 47 | _MyHomePageState createState() => _MyHomePageState(); 48 | } 49 | 50 | class _MyHomePageState extends State { 51 | int _counter = 0; 52 | 53 | void _incrementCounter() { 54 | setState(() { 55 | // This call to setState tells the Flutter framework that something has 56 | // changed in this State, which causes it to rerun the build method below 57 | // so that the display can reflect the updated values. If we changed 58 | // _counter without calling setState(), then the build method would not be 59 | // called again, and so nothing would appear to happen. 60 | _counter++; 61 | }); 62 | } 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | // This method is rerun every time setState is called, for instance as done 67 | // by the _incrementCounter method above. 68 | // 69 | // The Flutter framework has been optimized to make rerunning build methods 70 | // fast, so that you can just rebuild anything that needs updating rather 71 | // than having to individually change instances of widgets. 72 | return Scaffold( 73 | appBar: AppBar( 74 | // Here we take the value from the MyHomePage object that was created by 75 | // the App.build method, and use it to set our appbar title. 76 | title: Text(widget.title), 77 | ), 78 | body: Center( 79 | // Center is a layout widget. It takes a single child and positions it 80 | // in the middle of the parent. 81 | child: Column( 82 | // Column is also layout widget. It takes a list of children and 83 | // arranges them vertically. By default, it sizes itself to fit its 84 | // children horizontally, and tries to be as tall as its parent. 85 | // 86 | // Invoke "debug painting" (press "p" in the console, choose the 87 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 88 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 89 | // to see the wireframe for each widget. 90 | // 91 | // Column has various properties to control how it sizes itself and 92 | // how it positions its children. Here we use mainAxisAlignment to 93 | // center the children vertically; the main axis here is the vertical 94 | // axis because Columns are vertical (the cross axis would be 95 | // horizontal). 96 | mainAxisAlignment: MainAxisAlignment.center, 97 | children: [ 98 | Text( 99 | 'You have pushed the button this many times:', 100 | ), 101 | Text( 102 | '$_counter', 103 | style: Theme.of(context).textTheme.display1, 104 | ), 105 | ], 106 | ), 107 | ), 108 | floatingActionButton: FloatingActionButton( 109 | onPressed: _incrementCounter, 110 | tooltip: 'Increment', 111 | child: Icon(Icons.add), 112 | ), // This trailing comma makes auto-formatting nicer for build methods. 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/dart/my_fb_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_boost/flutter_boost.dart'; 3 | 4 | class MyFlutterBoostApp extends StatefulWidget { 5 | final Map builders; 6 | 7 | MyFlutterBoostApp(this.builders); 8 | 9 | @override 10 | _MyFlutterBoostAppState createState() => _MyFlutterBoostAppState(); 11 | } 12 | 13 | class _MyFlutterBoostAppState extends State { 14 | @override 15 | void initState() { 16 | super.initState(); 17 | FlutterBoost.singleton.registerPageBuilders(widget.builders); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return MaterialApp( 23 | builder: FlutterBoost.init(postPush: _onRoutePushed), 24 | home: Container(), 25 | ); 26 | } 27 | 28 | void _onRoutePushed( 29 | String pageName, 30 | String uniqueId, 31 | Map params, 32 | Route route, 33 | Future _, 34 | ) {} 35 | } 36 | -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/ios/fb/FBDemoRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // FBDemoRouter.h 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/9/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface FBDemoRouter : NSObject 16 | 17 | @property (nonatomic,strong) UINavigationController *navigationController; 18 | 19 | + (void)registerInFlutterBoost; 20 | 21 | + (FBDemoRouter *)shared; 22 | 23 | - (void)addEntryView:(UIViewController *)vc; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /src/market/FlutterBoost/tpl/ios/fb/FBDemoRouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // FBDemoRouter.m 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/9/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import "FBDemoRouter.h" 10 | #import 11 | #import 12 | 13 | @implementation FBDemoRouter 14 | 15 | + (void)registerInFlutterBoost { 16 | [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:[self shared] 17 | onStart:^(id engine) { 18 | 19 | }]; 20 | } 21 | 22 | + (FBDemoRouter *)shared { 23 | static dispatch_once_t onceToken; 24 | static FBDemoRouter *instance; 25 | dispatch_once(&onceToken, ^{ 26 | instance = [FBDemoRouter new]; 27 | }); 28 | return instance; 29 | } 30 | 31 | - (void)openPage:(NSString *)name 32 | params:(NSDictionary *)params 33 | animated:(BOOL)animated 34 | from:(UIViewController *)fromVC 35 | completion:(void (^)(BOOL))completion 36 | { 37 | if([params[@"present"] boolValue]){ 38 | FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new; 39 | [vc setName:name params:params]; 40 | [fromVC presentViewController:vc animated:animated completion:^{}]; 41 | }else if([fromVC isKindOfClass:[UINavigationController class]]){ 42 | FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new; 43 | [vc setName:name params:params]; 44 | [(UINavigationController *)fromVC pushViewController:vc animated:animated]; 45 | } 46 | } 47 | 48 | - (void)openPage:(NSString *)name 49 | params:(NSDictionary *)params 50 | animated:(BOOL)animated 51 | completion:(void (^)(BOOL))completion 52 | { 53 | [self openPage:name params:params animated:animated completion:completion]; 54 | } 55 | 56 | 57 | - (void)closePage:(NSString *)uid animated:(BOOL)animated params:(NSDictionary *)params completion:(void (^)(BOOL))completion 58 | { 59 | FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController; 60 | if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){ 61 | [vc dismissViewControllerAnimated:animated completion:^{}]; 62 | }else{ 63 | [vc.navigationController popViewControllerAnimated:animated]; 64 | } 65 | } 66 | 67 | #pragma mark - View Related 68 | - (void)addEntryView:(UIViewController *)vc { 69 | UIViewController *root = [UIViewController new]; 70 | UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 71 | CGRect bounds = [[UIScreen mainScreen]bounds]; 72 | CGFloat width = 200; 73 | btn.frame = CGRectMake((CGRectGetWidth(bounds)-width)/2, 88, width, 44); 74 | [btn setTitle:@"Navigate To Flutter" forState:UIControlStateNormal]; 75 | [btn setBackgroundColor:[UIColor colorWithRed:0.106 green:0.639 blue:0.984 alpha:1.0]]; 76 | [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 77 | [btn addTarget:self action:@selector(jump) forControlEvents:UIControlEventTouchUpInside]; 78 | [root.view addSubview:btn]; 79 | root.title = @"Hello World"; 80 | root.view.backgroundColor = [UIColor whiteColor]; 81 | self.navigationController = [[UINavigationController alloc]initWithRootViewController:root]; 82 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 83 | [vc presentViewController:self.navigationController animated:NO completion:nil]; 84 | }); 85 | } 86 | 87 | - (void)jump { 88 | [self openPage:@"demo://mypage" params:@{} animated:YES from:self.navigationController completion:nil]; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /src/repo/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function () { 4 | const Promise = require('bluebird') 5 | 6 | const manager = Promise.promisifyAll( 7 | require('./manager')(require('./git')('git'), require('./svn')('svn')) 8 | ) 9 | 10 | const repo = {} 11 | 12 | repo.init = init 13 | repo.remotePushUrl = remotePushUrl 14 | repo.checkout = checkout 15 | repo.clone = clone 16 | repo.update = update 17 | repo.commitAll = commitAll 18 | repo.getCurrentBranch = getCurrentBranch 19 | repo.getBranches = getBranches 20 | repo.deleteBranch = deleteBranch 21 | repo.switchBranch = switchBranch 22 | repo.getChanged = getChanged 23 | repo.status = status 24 | repo.publish = publish 25 | repo.fetch = fetch 26 | repo.info = info 27 | repo.getAllTags = getAllTags 28 | 29 | return repo 30 | 31 | function init (data) { 32 | data = data || {} 33 | return manager.initAsync( 34 | data.baseDir || process.cwd(), 35 | data.distUrl, 36 | data.version 37 | ) 38 | } 39 | 40 | function remotePushUrl (data) { 41 | data = data || {} 42 | return manager.remotePushUrlAsync(data.baseDir || process.cwd()) 43 | } 44 | 45 | function checkout (data) { 46 | data = data || {} 47 | return manager.checkoutAsync( 48 | data.distUrl, 49 | data.baseDir || process.cwd(), 50 | data.version 51 | ) 52 | } 53 | 54 | function clone (data) { 55 | data = data || {} 56 | return manager.cloneAsync( 57 | data.distUrl, 58 | data.baseDir || process.cwd(), 59 | data.version 60 | ) 61 | } 62 | function update (data) { 63 | data = data || {} 64 | return manager.updateAsync(data.baseDir || process.cwd()) 65 | } 66 | function commitAll (data) { 67 | data = data || {} 68 | return manager.commitAllAsync(data.baseDir || process.cwd(), data.msg) 69 | } 70 | function getCurrentBranch (data) { 71 | data = data || {} 72 | return manager.getCurrentBranchAsync(data.baseDir || process.cwd()) 73 | } 74 | function getBranches (data) { 75 | data = data || {} 76 | return manager.getBranchesAsync( 77 | data.baseDir || process.cwd(), 78 | data.includeRemote || false 79 | ) 80 | } 81 | function deleteBranch (data) { 82 | data = data || {} 83 | return manager.deleteBranchAsync( 84 | data.baseDir || process.cwd(), 85 | data.branchUrl, 86 | data.includeRemote || false 87 | ) 88 | } 89 | function switchBranch (data) { 90 | data = data || {} 91 | return manager.switchAsync(data.baseDir || process.cwd(), data.branchUrl) 92 | } 93 | function getChanged (data) { 94 | data = data || {} 95 | return manager.getChangedAsync(data.baseDir || process.cwd()) 96 | } 97 | function status (data) { 98 | data = data || {} 99 | return manager.statusAsync(data.baseDir || process.cwd()) 100 | } 101 | function publish (data) { 102 | data = data || {} 103 | return manager.publishAsync( 104 | data.baseDir || process.cwd(), 105 | data.tagName, 106 | data.msg 107 | ) 108 | } 109 | function fetch (data) { 110 | data = data || {} 111 | return manager.fetchAsync(data.baseDir || process.cwd()) 112 | } 113 | function info (data) { 114 | data = data || {} 115 | return manager.infoAsync(data.baseDir || process.cwd()) 116 | } 117 | function getAllTags (data) { 118 | data = data || {} 119 | return manager.getAllTagsAsync(data.baseDir || process.cwd(), data.reg) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/repo/manager.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | var Promise = require('bluebird') 6 | 7 | module.exports = function (gitShell, svnShell) { 8 | var manager = {} 9 | 10 | manager.init = function (baseDir, repo, version, callback) { 11 | checkDirExists(baseDir).then(function (baseDir) { 12 | gitShell.init(baseDir, repo, version, callback) 13 | }) 14 | } 15 | 16 | manager.remotePushUrl = function (baseDir, callback) { 17 | checkDirExists(baseDir).then(function (baseDir) { 18 | gitShell.remotePushUrl(baseDir, callback) 19 | }) 20 | } 21 | 22 | manager.checkout = function (distUrl, baseDir, version, callback) { 23 | checkDirExists(baseDir).then(function (baseDir) { 24 | if (/git/.test(distUrl)) { 25 | gitShell.clone(distUrl, baseDir, version, callback) 26 | } else { 27 | callback() 28 | } 29 | }) 30 | } 31 | 32 | manager.clone = function (distUrl, baseDir, version, callback) { 33 | new Promise(function (resolve, reject) { 34 | if (/git/.test(distUrl)) { 35 | gitShell.clone(distUrl, baseDir, version, callback) 36 | } else { 37 | callback() 38 | } 39 | resolve() 40 | }) 41 | } 42 | 43 | manager.update = function (baseDir, callback) { 44 | checkDirExists(baseDir) 45 | .then(getShell) 46 | .then(function (shell) { 47 | shell ? shell.update(baseDir, callback) : callback() 48 | }) 49 | } 50 | 51 | manager.commit = function (baseDir, msg, callback) { 52 | checkDirExists(baseDir) 53 | .then(getShell) 54 | .then(function (shell) { 55 | shell ? shell.commit(baseDir, msg, callback) : callback() 56 | }) 57 | } 58 | 59 | manager.getCurrentBranch = function (baseDir, callback) { 60 | checkDirExists(baseDir) 61 | .then(getShell) 62 | .then(function (shell) { 63 | shell ? shell.getCurrentBranch(baseDir, callback) : callback() 64 | }) 65 | } 66 | 67 | manager.getBranches = function (baseDir, includeRemote, callback) { 68 | if (typeof includeRemote === 'function') { 69 | callback = includeRemote 70 | includeRemote = false 71 | } 72 | checkDirExists(baseDir) 73 | .then(getShell) 74 | .then(function (shell) { 75 | shell ? shell.getBranches(baseDir, includeRemote, callback) : callback() 76 | }) 77 | } 78 | 79 | manager.status = function (baseDir, callback) { 80 | checkDirExists(baseDir) 81 | .then(getShell) 82 | .then(function (shell) { 83 | shell ? shell.status(baseDir, callback) : callback() 84 | }) 85 | } 86 | 87 | manager.switch = function (baseDir, branchUrl, callback) { 88 | checkDirExists(baseDir) 89 | .then(getShell) 90 | .then(function (shell) { 91 | shell ? shell.switch(baseDir, branchUrl, callback) : callback() 92 | }) 93 | } 94 | 95 | manager.getChanged = function (baseDir, branchUrl, callback) { 96 | checkDirExists(baseDir) 97 | .then(getShell) 98 | .then(function (shell) { 99 | shell ? shell.getChanged(baseDir, branchUrl, callback) : callback() 100 | }) 101 | } 102 | 103 | manager.publish = function (baseDir, tagName, msg, callback) { 104 | checkDirExists(baseDir) 105 | .then(getShell) 106 | .then(function (shell) { 107 | shell ? shell.publish(baseDir, tagName, msg, callback) : callback() 108 | }) 109 | } 110 | 111 | manager.deleteBranch = function (baseDir, branchUrl, includeRemote, callback) { 112 | if (typeof includeRemote === 'function') { 113 | callback = includeRemote 114 | includeRemote = false 115 | } 116 | 117 | checkDirExists(baseDir) 118 | .then(getShell) 119 | .then(function (shell) { 120 | shell 121 | ? shell.deleteBranch(baseDir, branchUrl, includeRemote, callback) 122 | : callback() 123 | }) 124 | } 125 | 126 | manager.commitAll = function (baseDir, msg, callback) { 127 | checkDirExists(baseDir) 128 | .then(getShell) 129 | .then(function (shell) { 130 | shell ? shell.commitAll(baseDir, msg, callback) : callback() 131 | }) 132 | } 133 | 134 | manager.fetch = function (baseDir, callback) { 135 | checkDirExists(baseDir) 136 | .then(getShell) 137 | .then(function (shell) { 138 | shell ? shell.fetch(baseDir, callback) : callback() 139 | }) 140 | } 141 | 142 | manager.info = function (baseDir, callback) { 143 | checkDirExists(baseDir) 144 | .then(getShell) 145 | .then(function (shell) { 146 | shell ? shell.info(baseDir, callback) : callback() 147 | }) 148 | } 149 | 150 | manager.getAllTags = function (baseDir, reg, callback) { 151 | checkDirExists(baseDir) 152 | .then(getShell) 153 | .then(function (shell) { 154 | shell ? shell.allTags(baseDir, reg, callback) : callback() 155 | }) 156 | } 157 | 158 | return manager 159 | 160 | function checkDirExists (baseDir) { 161 | return new Promise(function (resolve, reject) { 162 | if (fs.existsSync(baseDir)) { 163 | resolve(baseDir) 164 | } else { 165 | reject('dir not found') 166 | } 167 | }) 168 | } 169 | 170 | function getShell (baseDir) { 171 | return new Promise(function (resolve, reject) { 172 | var gitExists = fs.existsSync(path.join(baseDir, '.git')) 173 | var svnExists = fs.existsSync(path.join(baseDir, '.svn')) 174 | if (gitExists) { 175 | resolve(gitShell) 176 | } else if (svnExists) { 177 | resolve(svnShell) 178 | } else { 179 | gitShell.getChanged(baseDir, function (err, result) { 180 | if (err) { 181 | ;/Not a git repository/.test(err.message) ? resolve() : reject(err) 182 | } else { 183 | resolve(gitShell) 184 | } 185 | }) 186 | } 187 | }) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/scripts/add_ios_tpl.rb: -------------------------------------------------------------------------------- 1 | require 'xcodeproj' 2 | 3 | # path_to_project = ARGV[0] 4 | 5 | # project = Xcodeproj::Project.open(path_to_project) 6 | # main_target = project.targets.first 7 | 8 | # phase = main_target.new_shell_script_build_phase("Flutter Build Script") 9 | 10 | # #Remove the new phase from the end of array. 11 | # main_target.build_phases.delete(phase) 12 | 13 | # #Insert the phase into the array at index 0. 14 | # main_target.build_phases.insert(0,phase) 15 | 16 | # phase.shell_script = "\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build \n\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed" 17 | # project.save() 18 | 19 | 20 | print "adding tpl ...\n" 21 | xcodeproj_path = ARGV[0] 22 | project_name = File.basename(xcodeproj_path, ".xcodeproj") 23 | project_path = File.dirname(xcodeproj_path) 24 | print "get project name:" + project_name + "\n" 25 | file_dir_path = ARGV[1] 26 | 27 | def addFilesToGroup(aTarget, aGroup) 28 | Dir.foreach(aGroup.real_path) do |entry| 29 | next if entry == '.' or entry == '..' or entry == '.DS_Store' 30 | filePath = File.join(aGroup.real_path, entry) 31 | print filePath+"\n" 32 | # 过滤目录和.DS_Store文件 33 | if !File.directory?(filePath) && entry != ".DS_Store" then 34 | # 向group中增加文件引用 35 | fileReference = aGroup.new_reference(filePath) 36 | if filePath.to_s.end_with?(".m", ".mm") then 37 | aTarget.source_build_phase.add_file_reference(fileReference, true) 38 | elsif filePath.to_s.end_with?(".plist") then 39 | aTarget.resources_build_phase.add_file_reference(fileReference, true) 40 | end 41 | elsif File.directory?(filePath) then 42 | subGroup = aGroup.find_subpath(File.basename(filePath), true) 43 | subGroup.set_source_tree('') 44 | subGroup.set_path(filePath) 45 | addFilesToGroup(aTarget, subGroup) 46 | end 47 | end 48 | end 49 | project = Xcodeproj::Project.open(xcodeproj_path) 50 | target = project.targets.first 51 | 52 | group_subpath = file_dir_path.sub project_path+File::SEPARATOR, "" 53 | print "get group subpath:" + group_subpath + "\n" 54 | group = project.main_group.find_subpath(group_subpath, true) 55 | group.set_source_tree('') 56 | group.set_path(file_dir_path) 57 | addFilesToGroup(target, group) 58 | 59 | 60 | # group.set_source_tree('SOURCE_ROOT') 61 | # file_ref = group.new_reference(file_path) 62 | # target.add_file_references([file_ref]) 63 | project.save 64 | print "add tpl finished\n" 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/scripts/duplicate_target.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'xcodeproj' 5 | 6 | $src_target_name = ARGV[1] 7 | $new_target_name = ARGV[2] 8 | 9 | def get_src_target(proj) 10 | src_target = proj.targets.find { |item| 11 | item.name == $src_target_name 12 | } 13 | return src_target 14 | end 15 | 16 | def get_dependent_targets(proj, main_uuid) 17 | if main_uuid.empty? 18 | return [] 19 | end 20 | dependencies_array = Array.new 21 | #PBXNativeTarget(inner target) -> PBXTargetDependency -> PBXNativeTarget(main target) 22 | proj.targets.each { |item| 23 | item.dependencies.each { |t_item| 24 | if t_item.target.uuid.eql?(main_uuid) 25 | dependencies_array << item 26 | end 27 | } 28 | } 29 | return dependencies_array 30 | end 31 | 32 | def clear_env(proj) 33 | proj.targets.delete_if { |item| 34 | item.product_name.index($new_target_name) == 0 #the same prefix 35 | } 36 | end 37 | 38 | def get_target_name(src_name) 39 | replaced_name = "" 40 | target_prefix = $new_target_name.to_s 41 | index = src_name.index($src_target_name) 42 | if src_name.eql?($src_target_name) 43 | replaced_name = src_names 44 | elsif index == 0 && src_name.length > $src_target_name.length #prefix 45 | replaced_name = target_prefix + src_name[index + $src_target_name.length..src_name.length] 46 | else 47 | replaced_name = target_prefix + src_name.titleize 48 | end 49 | return replaced_name 50 | end 51 | 52 | def create_targets(proj, src_target, dependencies_array) 53 | target = proj.new_target(src_target.symbol_type, $new_target_name, src_target.platform_name, src_target.deployment_target) 54 | 55 | dependencies_array.each { |item| 56 | name = get_target_name(item.name) 57 | dep_target = proj.new_target(src_target.symbol_type, name, src_target.platform_name, src_target.deployment_target) 58 | dep_target.product_name = name 59 | dep_target.product_type = item.product_type 60 | dep_target.add_dependency(target) 61 | } 62 | return target 63 | end 64 | 65 | def create_scheme(target) 66 | scheme = Xcodeproj::XCScheme.new 67 | scheme.add_build_target(target) 68 | scheme.set_launch_target(target) 69 | # scheme.save_as(proj.path, new_target_name) 70 | end 71 | 72 | def copy_settings(src_target, target) 73 | # copy build_configurations 74 | target.build_configurations.map do |item| 75 | item.build_settings.update(src_target.build_settings(item.name)) 76 | end 77 | # Copy the build phases 78 | target.build_phases.clear 79 | src_target.build_phases.each do |phase| 80 | target.build_phases << phase 81 | end 82 | end 83 | 84 | def add_files(proj, target) 85 | classes = proj.main_group.groups.find { |x| x.to_s == 'Group' }.groups.find { |x| x.name == 'Classes' } 86 | sources = target.build_phases.find { |x| x.instance_of? Xcodeproj::Project::Object::PBXSourcesBuildPhase } 87 | # file_ref = classes.new_file('test.m') 88 | build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile) 89 | # build_file.file_ref = file_ref 90 | sources.files << build_file 91 | end 92 | 93 | def create_runner_target(proj, src_target, dependencies_array) 94 | clear_env(proj) 95 | target = create_targets(proj, src_target, dependencies_array) 96 | create_scheme(target) 97 | copy_settings(src_target, target) 98 | add_files(proj, target) 99 | proj.save 100 | end 101 | 102 | if __FILE__ == $0 103 | proj = Xcodeproj::Project.open(ARGV[0]) 104 | src_target = get_src_target(proj) 105 | dependencies_array = get_dependent_targets(proj, src_target.uuid) 106 | create_runner_target(proj, src_target, dependencies_array) 107 | end 108 | -------------------------------------------------------------------------------- /src/scripts/fbinclude_flutter.groovy: -------------------------------------------------------------------------------- 1 | // Generated file. Do not edit. 2 | // by flutter boot 3 | 4 | boolean enableDetectFlutterDir = gradle.android_enableDetectFlutterDir && gradle.android_enableDetectFlutterDir.toBoolean() 5 | def localConfigFile = new File(gradle.settingsDir, 'fbConfig.local.json') 6 | String flutterProjectRoot = '' 7 | if (localConfigFile.exists()) { 8 | String fileStr = localConfigFile.getText('UTF-8') 9 | def configJsonObj = new groovy.json.JsonSlurper().parseText(fileStr) 10 | flutterProjectRoot = configJsonObj.flutterPath 11 | } 12 | 13 | if (flutterProjectRoot == null || flutterProjectRoot.isEmpty()) { 14 | def remoteConfigFile = new File(gradle.settingsDir, 'fbConfig.json') 15 | if (remoteConfigFile.exists()) { 16 | def result = "flutter-boot update".execute() 17 | flutterProjectRoot = '.fbflutter' 18 | } 19 | } 20 | 21 | boolean isDetectedFlutterDir = flutterProjectRoot != null && !flutterProjectRoot.isEmpty() && enableDetectFlutterDir 22 | gradle.getGradle().ext.isDetectedFlutterDir = isDetectedFlutterDir 23 | if (isDetectedFlutterDir) { 24 | evaluate(new File(flutterProjectRoot, '.android/include_flutter.groovy')) 25 | } -------------------------------------------------------------------------------- /src/scripts/inject_flutter_script.rb: -------------------------------------------------------------------------------- 1 | require 'xcodeproj' 2 | 3 | path_to_project = ARGV[0] 4 | 5 | project = Xcodeproj::Project.open(path_to_project) 6 | main_target = project.targets.first 7 | 8 | phase = main_target.new_shell_script_build_phase("Flutter Build Script") 9 | 10 | #Remove the new phase from the end of array. 11 | main_target.build_phases.delete(phase) 12 | 13 | #Insert the phase into the array at index 0. 14 | main_target.build_phases.insert(0,phase) 15 | 16 | phase.shell_script = "\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build \n\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed" 17 | project.save() 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/ui.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Inquirer = require('inquirer') 4 | const Table = require('easy-table') 5 | const Ora = require('ora') 6 | 7 | exports.prompt = function (questions) { 8 | return Inquirer.prompt(questions) 9 | } 10 | 11 | exports.confirm = function (msg) { 12 | return this.prompt([ 13 | { 14 | type: 'confirm', 15 | name: 'ok', 16 | message: msg 17 | } 18 | ]).then(answer => answer.ok) 19 | } 20 | 21 | exports.list = function (msg, choices, defaultVal) { 22 | return this.prompt([ 23 | { 24 | type: 'list', 25 | name: 'result', 26 | message: msg, 27 | choices: choices, // -> [{name, value, short}] 28 | default: defaultVal 29 | } 30 | ]).then(answer => answer.result) 31 | } 32 | 33 | exports.checkbox = function (msg, choices, defaultVal) { 34 | return this.prompt([ 35 | { 36 | type: 'checkbox', 37 | name: 'result', 38 | message: msg, 39 | choices: choices // -> [{name, value, short, checked}] 40 | } 41 | ]).then(answer => answer.result) 42 | } 43 | 44 | exports.input = function (msg, defaultVal) { 45 | return this.prompt([ 46 | { 47 | type: 'input', 48 | name: 'result', 49 | message: msg, 50 | default: defaultVal 51 | } 52 | ]).then(answer => answer.result) 53 | } 54 | 55 | exports.editor = function (msg) { 56 | return this.prompt([ 57 | { 58 | type: 'editor', 59 | name: 'result', 60 | message: msg 61 | } 62 | ]).then(answer => answer.result) 63 | } 64 | 65 | exports.table = function (rows) { 66 | var t = new Table() 67 | rows.forEach(row => { 68 | for (let k in row) { 69 | t.cell(k, row[k]) 70 | } 71 | t.newRow() 72 | }) 73 | return t.toString() 74 | } 75 | 76 | exports.spinner = function (desc) { 77 | return Ora(desc).start() 78 | } 79 | 80 | exports.colors = require('colors/safe') 81 | 82 | exports.boxen = function (input, opts) { 83 | return require('boxen')(input, opts || {}) 84 | } 85 | 86 | exports.marked = function (text) { 87 | const marked = require('marked') 88 | const renderer = new (require('./library/marked-terminal'))({ 89 | tab: 2, 90 | showSectionPrefix: false 91 | }) 92 | marked.setOptions({ renderer: renderer }) 93 | return marked(text) 94 | } 95 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const spawn = require('child_process').spawn 5 | const execSync = require('child_process').execSync 6 | 7 | const BUILDER_REG = /^(@ali\/)?builder-[a-zA-Z0-9_-]+$/ 8 | const GENERATOR_REG = /^(@ali\/)?generator-[a-zA-Z0-9_-]+$/ 9 | const PLUGIN_REG = /^(@ali\/)?def-plugin-[a-zA-Z0-9_-]+$/ 10 | const MOD_REG = /^(@ali\/)?def-[a-zA-Z0-9_-]+$/ 11 | const KIT_REG = /^(@ali\/)?def-kit-[a-zA-Z0-9_-]+$/ 12 | 13 | const REG = /^(@ali\/)?(builder|generator|def-kit|def-plugin|def)-([-_a-zA-Z0-9]+)$/ 14 | 15 | const util = (module.exports = {}) 16 | 17 | util.spawnCommand = function (command, args, options) { 18 | var win32 = process.platform === 'win32' 19 | 20 | var winCommand = win32 ? 'cmd' : command 21 | var winArgs = win32 ? ['/c'].concat(command, args) : args 22 | 23 | return spawn(winCommand, winArgs, options) 24 | } 25 | 26 | util.getStrictSSL = function () { 27 | try { 28 | var strictSSL = execSync('npm config get strict-ssl') 29 | .toString() 30 | .trim() 31 | return strictSSL !== 'false' 32 | } catch (err) { 33 | console.error('exec npm config get strict-ssl ERROR: ' + err.message) 34 | return true 35 | } 36 | } 37 | 38 | util.getIgnoreScripts = function () { 39 | try { 40 | var ignoreScripts = execSync('npm config get ignore-scripts') 41 | .toString() 42 | .trim() 43 | return ignoreScripts === 'true' 44 | } catch (err) { 45 | console.error('exec npm config get ignore-scripts ERROR: ' + err.message) 46 | return false 47 | } 48 | } 49 | 50 | util.getPython = function () { 51 | try { 52 | return execSync('npm config get python') 53 | .toString() 54 | .trim() 55 | } catch (err) { 56 | console.error('exec npm config get python ERROR:' + err.message) 57 | } 58 | return '' 59 | } 60 | 61 | util.getMsvsVersion = function () { 62 | try { 63 | return execSync('npm config get msvs_version') 64 | .toString() 65 | .trim() 66 | } catch (err) { 67 | console.error('exec npm config get msvs_version ERROR:' + err.message) 68 | } 69 | return '' 70 | } 71 | 72 | util.generatePath = function (m, suffix, flatten) { 73 | if (flatten) { 74 | return path.join(m, suffix || '') 75 | } 76 | return path.join(m, 'node_modules', m, suffix || '') 77 | } 78 | 79 | util.info = function (n) { 80 | let m = n.match(REG) 81 | if (!m) { 82 | return { 83 | name: n, 84 | type: 'unknown' 85 | } 86 | } 87 | 88 | let name = m[3] 89 | let type = m[2] 90 | if (type == 'def' || type == 'def-plugin') { 91 | type = 'plugin' 92 | } else if (type == 'def-kit') { 93 | type = 'kit' 94 | } 95 | return { 96 | name: name, 97 | type: type 98 | } 99 | } 100 | 101 | util.isGenerator = function (m) { 102 | return GENERATOR_REG.test(m) 103 | } 104 | util.isBuilder = function (m) { 105 | return BUILDER_REG.test(m) 106 | } 107 | util.isPlugin = function (m) { 108 | let c = PLUGIN_REG.test(m) 109 | if (!c) { 110 | c = MOD_REG.test(m) && !KIT_REG.test(m) 111 | } 112 | return c 113 | } 114 | util.isKit = function (m) { 115 | return KIT_REG.test(m) 116 | } 117 | util.type = function (m) { 118 | let t = '' 119 | if (this.isGenerator(m)) { 120 | t = 'generator' 121 | } else if (this.isBuilder(m)) { 122 | t = 'builder' 123 | } else if (this.isPlugin(m)) { 124 | t = 'plugin' 125 | } else if (this.isKit(m)) { 126 | t = 'kit' 127 | } 128 | return t 129 | } 130 | util.suffix = function (m) { 131 | if (PLUGIN_REG.test(m)) { 132 | return m.replace('@ali/def-plugin-', '') 133 | } else if (KIT_REG.test(m)) { 134 | return m.replace('@ali/def-kit-', '') 135 | } else if (MOD_REG.test(m)) { 136 | return m.replace('def-', '').replace('@ali/', '') 137 | } else if (BUILDER_REG.test(m)) { 138 | return m.replace() 139 | } 140 | } 141 | 142 | util.parseJSON = function (str) { 143 | return require('json-parse-helpfulerror').parse(str) 144 | } 145 | 146 | util.getFullFlutterVersion = function () { 147 | let ret = '' 148 | try { 149 | const prefix = 'Flutter' 150 | let infos = execSync('flutter --version') 151 | .toString() 152 | .trim() 153 | .split('•') 154 | if (infos.length > 0) { 155 | ret = infos[0] 156 | .substring(infos[0].indexOf(prefix) + prefix.length) //Flutter 1.9.1+hotfix.6 157 | .trim() 158 | } 159 | } catch (err) { 160 | console.error('get flutter version ERROR:' + err.message) 161 | } 162 | return ret 163 | } 164 | 165 | util.getShortFlutterVersion = function () { 166 | let fullVersion = util.getFullFlutterVersion() 167 | let arr = fullVersion 168 | .split('.') 169 | .slice(0, 2); 170 | return arr.length == 2 ? 171 | ''.concat(arr[0], '.', arr[1]) : 172 | fullVersion 173 | } 174 | -------------------------------------------------------------------------------- /src/utils/execUtils.js: -------------------------------------------------------------------------------- 1 | const execSync = require('child_process').execSync 2 | const path = require('path') 3 | 4 | function execRubySync (name, args) { 5 | const cmdstr = `ruby ${path.join( 6 | process.env.FB_DIR, 7 | 'src/scripts/' + name 8 | )} ${args.join(' ')}` 9 | execSync(cmdstr, { 10 | stdio: 'inherit' 11 | }) 12 | } 13 | 14 | module.exports = { 15 | execRubySync 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/exit.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('../log') 4 | 5 | module.exports = function exitOnError(tag, err, errNo) { 6 | log.error(tag, err) 7 | process.exit(errNo) 8 | } -------------------------------------------------------------------------------- /src/utils/flutterRecorder.js: -------------------------------------------------------------------------------- 1 | const isEmpty = require('./isEmpty') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const assert = require('assert') 5 | const log = require('../log') 6 | 7 | function flutterRecorderPath () { 8 | return path.join(process.env.FB_DIR, '.last_flutter_module') 9 | } 10 | 11 | function recordFlutterModule (flutterModulePath) { 12 | assert(!isEmpty(flutterModulePath), 'flutter module路径不能为空') 13 | log.silly('[create]', 'flutter module recorded') 14 | const selfRoot = process.env.FB_DIR 15 | try { 16 | fs.writeFileSync(flutterRecorderPath(), flutterModulePath) 17 | return true 18 | } catch (e) { 19 | return false 20 | } 21 | } 22 | 23 | function existedFlutterModule () { 24 | const selfRoot = process.env.FB_DIR 25 | const modulePath = flutterRecorderPath() 26 | if (fs.existsSync(modulePath)) { 27 | return fs.readFileSync(modulePath, 'utf-8') 28 | } else { 29 | return undefined 30 | } 31 | } 32 | 33 | function cleanFlutterRecord () { 34 | const selfRoot = process.env.FB_DIR 35 | const modulePath = flutterRecorderPath() 36 | if(fs.existsSync(modulePath)) { 37 | fs.unlink(modulePath, () => {}) 38 | } 39 | } 40 | 41 | module.exports = { 42 | recordFlutterModule, 43 | existedFlutterModule, 44 | flutterRecorderPath, 45 | cleanFlutterRecord 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/fsutils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const execSync = require('child_process').execSync 3 | const ncp = require('ncp').ncp 4 | const path = require('path') 5 | const XCODEPROJ_SUFFIX = '.xcodeproj' 6 | const log = require('../log') 7 | 8 | let lsCache = {} 9 | 10 | function hasFileSync (path, reg) { 11 | const dirfiles = fs.readdirSync(path.resolve()) 12 | return !dirfiles.every(filename => { 13 | return !reg.test(filename) 14 | }) 15 | } 16 | function hasFileInList (list, reg) { 17 | return !list.every(filename => { 18 | return !reg.test(filename) 19 | }) 20 | } 21 | 22 | function ls () {} 23 | 24 | function syncLs () {} 25 | 26 | function replaceContent (filePath, searchContent, replaceContent) { 27 | let rawdata = fs.readFileSync(filePath, 'utf8') 28 | let lines = rawdata.split('\n') 29 | let searchIndex = lines.findIndex(line => { 30 | return line.includes(searchContent) 31 | }) 32 | if (searchIndex > -1) { 33 | lines[searchIndex] = replaceContent 34 | fs.writeFileSync(filePath, lines.join('\n')) 35 | return true 36 | } 37 | return false 38 | } 39 | 40 | function addContent (filePath, searchContent, addContent) { 41 | let rawdata = fs.readFileSync(filePath, 'utf8') 42 | let lines = rawdata.split('\n') 43 | let isAddContentExist = lines.includes(addContent) 44 | if (isAddContentExist) { 45 | return 46 | } 47 | let searchIndex = lines.findIndex(line => { 48 | return line.includes(searchContent) 49 | }) 50 | if (searchIndex > -1) { 51 | lines.splice(searchIndex + 1, 0, addContent) 52 | fs.writeFileSync(filePath, lines.join('\n')) 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | function addOrReplaceContent (filePath, searchReg, replaceContent) { 59 | let rawdata = fs.readFileSync(filePath, 'utf8') 60 | let isContentExist = rawdata.includes(replaceContent) 61 | if (isContentExist) { 62 | return false 63 | } 64 | let res = searchReg.test(rawdata) 65 | if (res) { 66 | rawdata = rawdata.replace(searchReg, replaceContent) 67 | } else { 68 | rawdata = rawdata + replaceContent 69 | } 70 | fs.writeFileSync(filePath, rawdata) 71 | return true 72 | } 73 | 74 | function addOrReplaceContentByReg (filePath, searchValue, replaceValue) { 75 | let rawdata = fs.readFileSync(filePath, 'utf8') 76 | rawdata = rawdata.replace(searchValue, replaceValue) 77 | fs.writeFileSync(filePath, rawdata) 78 | return true 79 | } 80 | 81 | function addOrReplaceContentBySurround ( 82 | filePath, 83 | startContent, 84 | endContent, 85 | replaceContent 86 | ) { 87 | let rawdata = fs.readFileSync(filePath, 'utf8') 88 | let isContentExist = rawdata.includes(replaceContent) 89 | 90 | if (isContentExist) { 91 | return false 92 | } 93 | let startContentIndex = rawdata.indexOf(startContent) 94 | let endContentIndex = rawdata.indexOf(endContent) 95 | let injection = rawdata 96 | 97 | const validEnd = !(endContent == '' || endContent == undefined) 98 | if ( 99 | startContentIndex > -1 && 100 | (!validEnd || (endContentIndex > -1 && startContentIndex < endContentIndex)) 101 | ) { 102 | log.silly('REPLACE_CONTENT', 103 | rawdata.substring(0, startContentIndex + startContent.length) + 104 | replaceContent 105 | ) 106 | injection = 107 | rawdata.substring(0, startContentIndex + startContent.length) + 108 | replaceContent + 109 | (validEnd 110 | ? rawdata.substring(endContentIndex) 111 | : rawdata.substring(startContentIndex + startContent.length)) 112 | log.silly('REPLACE_CONTENT',injection) 113 | } else { 114 | injection = rawdata + replaceContent 115 | } 116 | fs.writeFileSync(filePath, injection) 117 | return true 118 | } 119 | 120 | function createSoftLink (linkPath, targetDir) { 121 | try { 122 | fs.unlinkSync(linkPath) 123 | } catch (e) {} 124 | fs.symlinkSync(targetDir, linkPath, 'dir') 125 | } 126 | 127 | function projectChecker (projectPath) { 128 | let checker = function () {} 129 | checker.refresh = () => { 130 | const dirfiles = fs.readdirSync(projectPath) 131 | checker.dirfiles = dirfiles 132 | checker.currentPath = projectPath 133 | checker.getProjectName(true) 134 | } 135 | checker.isAndroid = () => { 136 | return hasFileInList(checker.dirfiles, /gradle$/) 137 | } 138 | checker.isIOS = () => { 139 | return hasFileInList( 140 | checker.dirfiles, 141 | /(xcodeproj)|(xcworkspace)|(Podfile)$/ 142 | ) 143 | } 144 | checker.isNative = () => { 145 | return checker.isIOS() || checker.isAndroid() 146 | } 147 | checker.gradlePath = () => { 148 | return path.join(checker.currentPath, 'app/build.gradle') 149 | } 150 | checker.getProjectName = (force = false) => { 151 | if (!force && checker.projectName) { 152 | return checker.projectName 153 | } 154 | if (checker.isIOS()) { 155 | const dirfiles = checker.dirfiles 156 | let projectFullName = '' 157 | dirfiles.every(filename => { 158 | if (filename.endsWith(XCODEPROJ_SUFFIX)) { 159 | projectFullName = filename 160 | return false 161 | } 162 | return true 163 | }) 164 | const projectName = projectFullName.substring( 165 | 0, 166 | projectFullName.indexOf(XCODEPROJ_SUFFIX) 167 | ) 168 | checker.projectName = projectName 169 | } else { 170 | checker.projectName = path.basename(checker.currentPath) 171 | } 172 | return checker.projectName 173 | } 174 | checker.xcodeproj = () => { 175 | return path.join( 176 | checker.currentPath, 177 | checker.getProjectName() + XCODEPROJ_SUFFIX 178 | ) 179 | } 180 | checker.refresh() 181 | return checker 182 | } 183 | 184 | function copyFolderAsync (source, destination, options = {}) { 185 | return new Promise((resolve, reject) => { 186 | if (options.mkdes && !fs.existsSync(destination)) { 187 | log.silly('COPY_FOLDER', 'create target folder') 188 | execSync(`mkdir -p ${destination}`) 189 | } 190 | ncp(source, destination, options, function (err) { 191 | if (err) { 192 | reject(err) 193 | return 194 | } 195 | log.silly('COPY_FOLDER', `copy success from ${source} to ${destination}`) 196 | resolve(true) 197 | }) 198 | }) 199 | } 200 | 201 | module.exports = { 202 | hasFileSync, 203 | hasFileInList, 204 | addContent, 205 | replaceContent, 206 | createSoftLink, 207 | projectChecker, 208 | addOrReplaceContent, 209 | copyFolderAsync, 210 | addOrReplaceContentByReg, 211 | addOrReplaceContentBySurround 212 | } 213 | -------------------------------------------------------------------------------- /src/utils/isEmpty.js: -------------------------------------------------------------------------------- 1 | const typof = require('./typof') 2 | const isNull = require('./isNull') 3 | module.exports = function isEmpty (v) { 4 | if (isNull(v)) { 5 | return true 6 | } else { 7 | const type = typof(v) 8 | if (type === 'object') { 9 | return Object.keys(v).length === 0 10 | } else if (type === 'array' || type === 'string') { 11 | return v.length === 0 12 | } 13 | return false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/isNull.js: -------------------------------------------------------------------------------- 1 | module.exports = function isNull (v) { 2 | if (v === 'null' || v === 'undefined') { 3 | return true 4 | } 5 | return !v 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/marketConfigHelper.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | function hasTagConfig (config, version) { 4 | return version && version.tag && config[version.tag] 5 | } 6 | 7 | function condi (config, version, name) { 8 | let content 9 | if (hasTagConfig(config, version)) { 10 | content = config[version.tag][name] 11 | } 12 | if (!content) { 13 | content = config[name] 14 | } 15 | return content 16 | } 17 | function pubspec (config, version) { 18 | return condi(config, version, 'pubspec') 19 | } 20 | 21 | function android (config, version) { 22 | return condi(config, version, 'android') 23 | } 24 | 25 | function ios (config, version) { 26 | return condi(config, version, 'ios') 27 | } 28 | 29 | function dart (config, version) { 30 | return condi(config, version, 'dart') 31 | } 32 | 33 | function gradle (config, version) { 34 | return condi(config, version, 'gradle') 35 | } 36 | 37 | function resolveVar (condi, vEnv) { 38 | Object.keys(vEnv).forEach(k => { 39 | condi = condi.replace(`\$\{${k}\}`, vEnv[k]) 40 | }) 41 | return condi 42 | } 43 | 44 | module.exports = { 45 | pubspec, 46 | android, 47 | ios, 48 | dart, 49 | gradle, 50 | resolveVar 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/pathUtils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | function absolutePath (input, base) { 4 | if (!path.isAbsolute(input)) { 5 | input = path.resolve(base, input) 6 | } 7 | return input 8 | } 9 | 10 | module.exports = { 11 | absolutePath 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/pubspecHelper.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const yaml = require('js-yaml') 5 | const fsutils = require('./fsutils') 6 | 7 | function pubspec (flutterPath) { 8 | return path.join(flutterPath, 'pubspec.yaml') 9 | } 10 | 11 | function addDep (flutterPath, dep) { 12 | console.log(dep) 13 | __pubspecPath = pubspec(flutterPath) 14 | const pubspecContent = yaml.load(fs.readFileSync(__pubspecPath, 'utf-8')) 15 | if(dep.git) { 16 | pubspecContent.dependencies[dep.name] = { 17 | git: { 18 | url: dep.git, 19 | ref: dep.version 20 | } 21 | } 22 | } else { 23 | pubspecContent.dependencies[dep.name] = dep.version 24 | } 25 | fs.writeFileSync(__pubspecPath, yaml.dump(pubspecContent)) 26 | } 27 | 28 | module.exports = { 29 | addDep 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/typof.js: -------------------------------------------------------------------------------- 1 | module.exports = function typof (v) { 2 | const s = Object.prototype.toString.call(v) 3 | return s.substring(8, s.length - 1).toLowerCase() 4 | } 5 | -------------------------------------------------------------------------------- /test/cmd.js: -------------------------------------------------------------------------------- 1 | const { existsSync } = require('fs') 2 | const { constants } = require('os') 3 | const spawn = require('cross-spawn') 4 | const concat = require('concat-stream') 5 | const path = require('path') 6 | const PATH = process.env.PATH 7 | 8 | function createProcess (args = [], opts = null) { 9 | args = [path.join(process.cwd(), 'index.js')].concat(args) 10 | 11 | // This works for node based CLIs, but can easily be adjusted to 12 | // any other process installed in the system 13 | return spawn( 14 | 'node', 15 | args, 16 | Object.assign( 17 | { 18 | env: Object.assign({}, process.env, { 19 | NODE_ENV: 'test', 20 | preventAutoStart: false, 21 | PATH // This is needed in order to get all the binaries in your current terminal 22 | }), 23 | stdio: [null, null, null, 'ipc'] // This enables interprocess communication (IPC) 24 | }, 25 | opts 26 | ) 27 | ) 28 | } 29 | 30 | function executeWithInput ( 31 | args = [], 32 | processOption = {}, 33 | inputs = [], 34 | opts = {} 35 | ) { 36 | if (!Array.isArray(inputs)) { 37 | opts = inputs 38 | inputs = [] 39 | } 40 | const tmpInputs = [] 41 | inputs.forEach((i, index) => { 42 | tmpInputs.push(i) 43 | tmpInputs.push('\x0D') 44 | }) 45 | inputs = tmpInputs 46 | const { timeout = 1000, maxTimeout = 10000 } = opts 47 | const env = processOption.env || { DEBUG: true } 48 | 49 | const childProcess = createProcess(args, processOption) 50 | childProcess.stdin.setEncoding('utf-8') 51 | 52 | let currentInputTimeout, killIOTimeout 53 | const loop = inputs => { 54 | if (killIOTimeout) { 55 | clearTimeout(killIOTimeout) 56 | } 57 | if (!inputs.length) { 58 | childProcess.stdin.end() 59 | 60 | // Set a timeout to wait for CLI response. If CLI takes longer than 61 | // maxTimeout to respond, kill the childProcess and notify user 62 | killIOTimeout = setTimeout(() => { 63 | console.error('Error: Reached I/O timeout') 64 | childProcess.kill(constants.signals.SIGTERM) 65 | }, maxTimeout) 66 | 67 | return 68 | } 69 | let tmptime = timeout 70 | let input = inputs[0] 71 | let inputValue = inputs[0] 72 | let beforeInput 73 | if (typeof input === 'object') { 74 | inputValue = input.value 75 | if (input.timeout) { 76 | tmptime = input.timeout 77 | } 78 | if (input.before) { 79 | beforeInput = input.before 80 | } 81 | } 82 | currentInputTimeout = setTimeout(() => { 83 | if (beforeInput) { 84 | beforeInput() 85 | } 86 | childProcess.stdin.write(inputValue) 87 | // Log debug I/O statements on tests 88 | if (env && env.DEBUG) { 89 | console.log('input:', inputs[0]) 90 | } 91 | loop(inputs.slice(1)) 92 | }, tmptime) 93 | } 94 | const promise = new Promise((resolve, reject) => { 95 | childProcess.stderr.on('data', data => { 96 | // Log debug I/O statements on tests 97 | if (env && env.DEBUG) { 98 | console.log('error:', data.toString()) 99 | } 100 | }) 101 | 102 | // Get output from CLI 103 | childProcess.stdout.on('data', data => { 104 | // Log debug I/O statements on tests 105 | if (env && env.DEBUG) { 106 | console.log('output:', data.toString()) 107 | } 108 | }) 109 | childProcess.stderr.once('data', err => { 110 | childProcess.stdin.end() 111 | if (currentInputTimeout) { 112 | clearTimeout(currentInputTimeout) 113 | inputs = [] 114 | } 115 | reject(err.toString()) 116 | }) 117 | 118 | childProcess.on('error', reject) 119 | // Kick off the process 120 | loop(inputs) 121 | childProcess.stdout.pipe( 122 | concat(result => { 123 | if (killIOTimeout) { 124 | clearTimeout(killIOTimeout) 125 | } 126 | resolve({ 127 | code: 0, 128 | result: result.toString() 129 | }) 130 | }) 131 | ) 132 | }) 133 | 134 | // Appending the process to the promise, in order to 135 | // add additional parameters or behavior (such as IPC communication) 136 | promise.attachedProcess = childProcess 137 | return promise 138 | } 139 | module.exports = { execute: executeWithInput } 140 | -------------------------------------------------------------------------------- /test/sandbox.js: -------------------------------------------------------------------------------- 1 | const _del = require('del') 2 | const uuid = require('uuid') 3 | const fs = require('fs') 4 | const path = require('path') 5 | const fsutils = require('../src/utils/fsutils') 6 | 7 | const SANDBOX = path.join(process.cwd(), 'test', 'sandbox/') 8 | const cmd = require('./cmd') 9 | 10 | function tplfile (name) { 11 | return path.join(process.cwd(), 'test', 'tpl', name) 12 | } 13 | 14 | function one () { 15 | const _id = uuid() 16 | const sandbox = path.join(SANDBOX, _id) 17 | if (fs.existsSync(sandbox)) { 18 | return tmp() 19 | } else { 20 | if (!fs.existsSync(SANDBOX)) { 21 | fs.mkdirSync(SANDBOX, { recursive: true }) 22 | } 23 | fs.mkdirSync(sandbox, { recursive: true }) 24 | } 25 | return { 26 | _id, 27 | // sandbox路径 28 | path: sandbox, 29 | // 在sandbox下执行命令 30 | execute: (args, processOption, inputs, opts) => { 31 | return cmd.execute( 32 | args, 33 | Object.assign( 34 | { 35 | cwd: sandbox 36 | }, 37 | processOption 38 | ), 39 | inputs, 40 | opts 41 | ) 42 | }, 43 | // 将tpl文件复制到sandbox下 44 | getTpl: type => { 45 | let target = path.join(sandbox, type) 46 | if (type == 'fluttergit') { 47 | target = path.join(sandbox, '.git') 48 | return fsutils 49 | .copyFolderAsync(path.join(tplfile('fluttergit'), '.git'), target) 50 | .then(() => { 51 | return target 52 | }) 53 | } else { 54 | return fsutils.copyFolderAsync(tplfile(type), target).then(() => { 55 | return target 56 | }) 57 | } 58 | } 59 | } 60 | } 61 | 62 | function del (id) { 63 | _del.sync(SANDBOX) 64 | } 65 | 66 | module.exports = { 67 | one, 68 | del 69 | } 70 | -------------------------------------------------------------------------------- /test/test.create.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const flutterRecorder = require('../src/utils/flutterRecorder') 5 | const execSync = require('child_process').execSync 6 | const sd = require('./sandbox') 7 | 8 | describe('test create', () => { 9 | describe('flutterRecorder', () => { 10 | before(() => { 11 | process.env.FB_DIR = process.cwd() 12 | }) 13 | // it 是单个测试用例 14 | it('recordFlutterModule', () => { 15 | flutterRecorder.recordFlutterModule('fakeflutterpath') 16 | // assert 用来进行断言,没通过的用例会让mocha输出错误信息 17 | assert( 18 | fs.existsSync(flutterRecorder.flutterRecorderPath()) && 19 | fs.readFileSync(flutterRecorder.flutterRecorderPath(), 'utf-8') == 20 | 'fakeflutterpath', 21 | 'flutter module记录失败' 22 | ) 23 | }) 24 | it('existedFlutterModule', () => { 25 | assert(flutterRecorder.existedFlutterModule(), 'flutter module记录失败') 26 | }) 27 | it('cleanFlutterRecord', () => { 28 | assert( 29 | !fs.existsSync(flutterRecorder.existedFlutterModule()), 30 | 'flutter无记录' 31 | ) 32 | flutterRecorder.cleanFlutterRecord() 33 | assert( 34 | !fs.existsSync(flutterRecorder.existedFlutterModule()), 35 | '删除flutter module记录失败' 36 | ) 37 | }) 38 | }) 39 | 40 | describe('create command', () => { 41 | it('test create', async () => { 42 | const sandbox = sd.one() 43 | const response = await sandbox 44 | .execute( 45 | ['create'], 46 | {}, 47 | [ 48 | 'tflutter', 49 | { 50 | value: '', 51 | timeout: 1000 52 | // before: () => { 53 | // sandbox.getTpl('fluttergit') 54 | // fs.copyFileSync( 55 | // path.join(sandbox.path, '.git'), 56 | // path.join(sandbox.path, 'tflutter', '.git') 57 | // ) 58 | // } 59 | } 60 | ], 61 | { 62 | maxTimeout: 40000 63 | } 64 | ) 65 | .catch(() => { 66 | assert(false) 67 | }) 68 | console.log('response') 69 | console.log(response) 70 | assert(response.code == 0) 71 | sd.del(sandbox) 72 | }).timeout(40000) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /test/test.link.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const flutterRecorder = require('../src/utils/flutterRecorder') 5 | const execSync = require('child_process').execSync 6 | const sd = require('./sandbox') 7 | const { cleanFlutterRecord } = require('../src/utils/flutterRecorder') 8 | const androidLinker = require('../src/android/link.js') 9 | const util = require('../src/util') 10 | 11 | describe('test link', () => { 12 | var tflutterPath 13 | before(() => { 14 | // execSync('flutter-boot create -n tflutter -R', { 15 | // cwd: path.join(process.cwd(), 'test', 'tpl') 16 | // }) 17 | cleanFlutterRecord() 18 | tflutterPath = 'tflutter_1_5' 19 | if (util.getShortFlutterVersion() == '1.9') { 20 | tflutterPath = 'tflutter_1_9' 21 | } 22 | }) 23 | 24 | describe('test iOS link', () => { 25 | let sandbox 26 | let tios 27 | let tflutter 28 | let iosLinker 29 | before(async () => { 30 | sandbox = sd.one() 31 | tios = await sandbox.getTpl('tios') 32 | tflutter = await sandbox.getTpl(tflutterPath) 33 | iosLinker = getIOSLinker() 34 | iosLinker.setOptions({ 35 | flutterPath: tflutter, 36 | nativePath: tios, 37 | projectName: 'tios' 38 | }) 39 | }) 40 | 41 | it('test preparePodfile', () => { 42 | iosLinker.preparePodfile() 43 | let path = iosLinker.podfile() 44 | assert(fs.existsSync(path)) 45 | }) 46 | 47 | it('test preparePodHelper', () => { 48 | iosLinker.preparePodHelper() 49 | let path = iosLinker.fbpodhelperPath() 50 | assert(fs.existsSync(path)) 51 | }) 52 | 53 | if (util.getShortFlutterVersion().startsWith('1.5')) { 54 | it('test injectXcode', () => { 55 | iosLinker.injectXcode() 56 | let path = iosLinker.pbxproj() 57 | assert(fs.existsSync(path)) 58 | assert(fs 59 | .readFileSync(path, 'utf8') 60 | .includes('Flutter Build Script')) 61 | }) 62 | } 63 | 64 | it('test addRunnerTargetToProject', () => { 65 | iosLinker.addRunnerTargetToProject() 66 | let path = iosLinker.pbxproj() 67 | assert(fs.existsSync(path)) 68 | assert(fs 69 | .readFileSync(path, 'utf8') 70 | .includes('/* Runner */')) 71 | }) 72 | 73 | it('test addRunnerTargetToPodfile', () => { 74 | iosLinker.addRunnerTargetToPodfile() 75 | let path = iosLinker.podfile() 76 | assert(path) 77 | let rawdata = fs.readFileSync(path, 'utf8') 78 | assert(rawdata.includes("target 'Runner' do") && 79 | rawdata.includes("eval(File.read(File.join(File.dirname(__FILE__), 'fbpodhelper.rb')), binding)")) 80 | }) 81 | 82 | after(() => { 83 | sd.del(sandbox) 84 | }) 85 | }) 86 | 87 | describe('link command', () => { 88 | it('test link ios and flutter', async () => { 89 | const sandbox = sd.one() 90 | 91 | const tios = await sandbox.getTpl('tios') 92 | const tflutter = await sandbox.getTpl(tflutterPath) 93 | const response = await sandbox 94 | .execute( 95 | ['link'], 96 | { 97 | cwd: tios 98 | }, 99 | [tflutter], 100 | { 101 | maxTimeout: 30000 102 | } 103 | ) 104 | .catch(e => { 105 | assert(false, `error occured:${e}`) 106 | }) 107 | 108 | assert(response.code == 0) 109 | sd.del(sandbox) 110 | }).timeout(30000) 111 | }) 112 | 113 | describe('test android link', () => { 114 | var sandbox 115 | var tandroid 116 | var tflutter 117 | before(async () => { 118 | sandbox = sd.one() 119 | tandroid = await sandbox.getTpl('tandroid') 120 | tflutter = await sandbox.getTpl(tflutterPath) 121 | androidLinker.setOptions({ 122 | flutterPath: tflutter, 123 | nativePath: tandroid 124 | }) 125 | }) 126 | 127 | it('test injectCompileOptions', () => { 128 | androidLinker.injectCompileOptions() 129 | let buildGradlePath = androidLinker.buildGradle() 130 | let rawdata = fs.readFileSync(buildGradlePath, 'utf8') 131 | assert(rawdata.includes(androidLinker.injectionBuildGradle())) 132 | }) 133 | 134 | it('test injectDependency', () => { 135 | androidLinker.injectDependency() 136 | const dependencyTag = "implementation project(':flutter')" 137 | let buildGradlePath = androidLinker.buildGradle() 138 | let rawdata = fs.readFileSync(buildGradlePath, 'utf8') 139 | assert(rawdata.includes(dependencyTag)) 140 | }) 141 | 142 | it('test injectGradleProperties', () => { 143 | androidLinker.injectGradleProperties() 144 | let gradlePropertiesPath = androidLinker.gradleProperties() 145 | let rawdata = fs.readFileSync(gradlePropertiesPath, 'utf8') 146 | assert(rawdata.includes(androidLinker.injectionGradleProperties())) 147 | }) 148 | 149 | it('test injectGradleSettings', () => { 150 | androidLinker.injectGradleSettings() 151 | let realPath = androidLinker.settingsGradle() 152 | let rawdata = fs.readFileSync(realPath, 'utf8') 153 | assert(rawdata.includes(androidLinker.injectionGradleSettings())) 154 | }) 155 | 156 | after(() => { 157 | sd.del(sandbox) 158 | }) 159 | }) 160 | 161 | describe('link command', () => { 162 | it('test link android and flutter', async () => { 163 | const sandbox = sd.one() 164 | 165 | const tandroid = await sandbox.getTpl('tandroid') 166 | const tflutter = await sandbox.getTpl(tflutterPath) 167 | const response = await sandbox 168 | .execute( 169 | ['link'], 170 | { 171 | cwd: tandroid 172 | }, 173 | [tflutter], 174 | { 175 | maxTimeout: 30000 176 | } 177 | ) 178 | .catch(() => { 179 | assert(false, 'error occured') 180 | }) 181 | 182 | assert(response.code == 0) 183 | if (response.code == 0) { 184 | console.log('android link success') 185 | } 186 | sd.del(sandbox) 187 | }).timeout(30000) 188 | }) 189 | }) 190 | 191 | function getIOSLinker () { 192 | let version = util.getShortFlutterVersion() 193 | if (version.startsWith('1.5')) { 194 | return require('../src/ios/1.5/link') 195 | } else if (version.startsWith('1.9')) { 196 | return require('../src/ios/1.9/link') 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /test/test.remotelink.js: -------------------------------------------------------------------------------- 1 | // const assert = require('assert') 2 | // const fs = require('fs') 3 | // const path = require('path') 4 | // const flutterRecorder = require('../src/utils/flutterRecorder') 5 | // const execSync = require('child_process').execSync 6 | // const sd = require('./sandbox') 7 | // const { cleanFlutterRecord } = require('../src/utils/flutterRecorder') 8 | 9 | // describe('test remotelink', () => { 10 | // before(() => { 11 | // execSync('flutter-boot create -n tflutter -R', { 12 | // cwd: path.join(process.cwd(), 'test', 'tpl') 13 | // }) 14 | // cleanFlutterRecord() 15 | // }) 16 | // describe('link command', () => { 17 | // it('test link ios and flutter', async () => { 18 | // const sandbox = sd.one() 19 | 20 | // const tios = await sandbox.getTpl('tios') 21 | // const tflutter = await sandbox.getTpl('tflutter') 22 | // const response = await sandbox 23 | // .execute( 24 | // ['remotelink'], 25 | // { 26 | // cwd: tios 27 | // }, 28 | // [ 29 | // 'your flutter git', 30 | // `test` 31 | // ], 32 | // { 33 | // maxTimeout: 40000 34 | // } 35 | // ) 36 | // .catch(() => { 37 | // assert(false, 'error occured') 38 | // }) 39 | 40 | // assert(response.code == 1) 41 | // sd.del(sandbox) 42 | // }).timeout(40000) 43 | // }) 44 | // }) 45 | -------------------------------------------------------------------------------- /test/test.use.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const execSync = require('child_process').execSync 5 | const sd = require('./sandbox') 6 | const { cleanFlutterRecord } = require('../src/utils/flutterRecorder') 7 | const androidLinker = require('../src/android/link.js') 8 | const marketConfigHelper = require('../src/utils/marketConfigHelper') 9 | 10 | describe('test use', () => { 11 | before(() => { 12 | cleanFlutterRecord() 13 | }) 14 | 15 | describe('market config helper', () => { 16 | it('test tag match when tagged option is unique', () => { 17 | const mock = { 18 | '1': { 19 | pubspec: { 20 | name: 'flutter_boost', 21 | version: '0.1.52' 22 | } 23 | } 24 | } 25 | assert( 26 | marketConfigHelper.pubspec(mock, { 27 | tag: '1' 28 | }).version == '0.1.52' 29 | ) 30 | }) 31 | it('test tag match when has default option', () => { 32 | const mock = { 33 | '1': { 34 | pubspec: { 35 | name: 'flutter_boost', 36 | version: '0.1.52' 37 | } 38 | }, 39 | pubspec: { 40 | name: 'flutter_boost', 41 | version: '0.1.53' 42 | } 43 | } 44 | 45 | assert( 46 | marketConfigHelper.pubspec(mock, { 47 | tag: '1' 48 | }).version == '0.1.52' 49 | ) 50 | }) 51 | it('no tag match', () => { 52 | const mock = { 53 | '1': { 54 | pubspec: { 55 | name: 'flutter_boost', 56 | version: '0.1.52' 57 | } 58 | }, 59 | pubspec: { 60 | name: 'flutter_boost', 61 | version: '0.1.53' 62 | } 63 | } 64 | assert( 65 | marketConfigHelper.pubspec(mock, { 66 | tag: '2' 67 | }).version == '0.1.53' 68 | ) 69 | }) 70 | }) 71 | describe('use command', () => { 72 | it('use flutterboost in iOS', async () => { 73 | const sandbox = sd.one() 74 | 75 | const tios = await sandbox.getTpl('tios') 76 | const tflutter = await sandbox.getTpl('tflutter_1_9') 77 | await sandbox 78 | .execute( 79 | ['link'], 80 | { 81 | cwd: tios 82 | }, 83 | [tflutter], 84 | { 85 | maxTimeout: 30000 86 | } 87 | ) 88 | .catch((e) => { 89 | assert(false, `error occured`) 90 | }) 91 | const response = await sandbox 92 | .execute( 93 | ['use'], 94 | { 95 | cwd: tios 96 | }, 97 | ['\n'], 98 | { 99 | maxTimeout: 30000 100 | } 101 | ) 102 | .catch(() => { 103 | assert(false, 'error occured') 104 | }) 105 | 106 | assert(response.code == 0) 107 | sd.del(sandbox) 108 | }).timeout(30000) 109 | }) 110 | }) 111 | -------------------------------------------------------------------------------- /test/tpl/tandroid/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /test/tpl/tandroid/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | tandroid 4 | Project tandroid created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/tpl/tandroid/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.example.fbi.tandroid" 7 | minSdkVersion 16 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | } 29 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/androidTest/java/com/example/fbi/tandroid/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.fbi.tandroid; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.fbi.tandroid", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/java/com/example/fbi/tandroid/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.fbi.tandroid; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/java/com/example/fbi/tandroid/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.fbi.tandroid; 2 | 3 | import android.app.Application; 4 | 5 | public class MyApplication extends Application { 6 | @Override 7 | public void onCreate() { 8 | super.onCreate(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | tandroid 3 | 4 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/tpl/tandroid/app/src/test/java/com/example/fbi/tandroid/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.fbi.tandroid; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /test/tpl/tandroid/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.4.2' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /test/tpl/tandroid/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/tpl/tandroid/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XianyuTech/flutter-boot/5c6ab6f0c90d0eff21ace4f875b6f0c815c69226/test/tpl/tandroid/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /test/tpl/tandroid/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 22 20:32:06 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /test/tpl/tandroid/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /test/tpl/tandroid/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /test/tpl/tandroid/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | .idea/ 8 | .vagrant/ 9 | .sconsign.dblite 10 | .svn/ 11 | 12 | *.swp 13 | profile 14 | 15 | DerivedData/ 16 | 17 | .generated/ 18 | 19 | *.pbxuser 20 | *.mode1v3 21 | *.mode2v3 22 | *.perspectivev3 23 | 24 | !default.pbxuser 25 | !default.mode1v3 26 | !default.mode2v3 27 | !default.perspectivev3 28 | 29 | xcuserdata 30 | 31 | *.moved-aside 32 | 33 | *.pyc 34 | *sync/ 35 | Icon? 36 | .tags* 37 | 38 | build/ 39 | .android/ 40 | .ios/ 41 | .flutter-plugins 42 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 68587a0916366e9512a78df22c44163d041dd5f3 8 | channel: stable 9 | 10 | project_type: module 11 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/README.md: -------------------------------------------------------------------------------- 1 | # tflutter_1_5 2 | 3 | A new flutter module project. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.dev/). 9 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | // This widget is the root of your application. 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | title: 'Flutter Demo', 11 | theme: ThemeData( 12 | // This is the theme of your application. 13 | // 14 | // Try running your application with "flutter run". You'll see the 15 | // application has a blue toolbar. Then, without quitting the app, try 16 | // changing the primarySwatch below to Colors.green and then invoke 17 | // "hot reload" (press "r" in the console where you ran "flutter run", 18 | // or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the 19 | // counter didn't reset back to zero; the application is not restarted. 20 | primarySwatch: Colors.blue, 21 | ), 22 | home: MyHomePage(title: 'Flutter Demo Home Page'), 23 | ); 24 | } 25 | } 26 | 27 | class MyHomePage extends StatefulWidget { 28 | MyHomePage({Key key, this.title}) : super(key: key); 29 | 30 | // This widget is the home page of your application. It is stateful, meaning 31 | // that it has a State object (defined below) that contains fields that affect 32 | // how it looks. 33 | 34 | // This class is the configuration for the state. It holds the values (in this 35 | // case the title) provided by the parent (in this case the App widget) and 36 | // used by the build method of the State. Fields in a Widget subclass are 37 | // always marked "final". 38 | 39 | final String title; 40 | 41 | @override 42 | _MyHomePageState createState() => _MyHomePageState(); 43 | } 44 | 45 | class _MyHomePageState extends State { 46 | int _counter = 0; 47 | 48 | void _incrementCounter() { 49 | setState(() { 50 | // This call to setState tells the Flutter framework that something has 51 | // changed in this State, which causes it to rerun the build method below 52 | // so that the display can reflect the updated values. If we changed 53 | // _counter without calling setState(), then the build method would not be 54 | // called again, and so nothing would appear to happen. 55 | _counter++; 56 | }); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | // This method is rerun every time setState is called, for instance as done 62 | // by the _incrementCounter method above. 63 | // 64 | // The Flutter framework has been optimized to make rerunning build methods 65 | // fast, so that you can just rebuild anything that needs updating rather 66 | // than having to individually change instances of widgets. 67 | return Scaffold( 68 | appBar: AppBar( 69 | // Here we take the value from the MyHomePage object that was created by 70 | // the App.build method, and use it to set our appbar title. 71 | title: Text(widget.title), 72 | ), 73 | body: Center( 74 | // Center is a layout widget. It takes a single child and positions it 75 | // in the middle of the parent. 76 | child: Column( 77 | // Column is also a layout widget. It takes a list of children and 78 | // arranges them vertically. By default, it sizes itself to fit its 79 | // children horizontally, and tries to be as tall as its parent. 80 | // 81 | // Invoke "debug painting" (press "p" in the console, choose the 82 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 83 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 84 | // to see the wireframe for each widget. 85 | // 86 | // Column has various properties to control how it sizes itself and 87 | // how it positions its children. Here we use mainAxisAlignment to 88 | // center the children vertically; the main axis here is the vertical 89 | // axis because Columns are vertical (the cross axis would be 90 | // horizontal). 91 | mainAxisAlignment: MainAxisAlignment.center, 92 | children: [ 93 | Text( 94 | 'You have pushed the button this many times:', 95 | ), 96 | Text( 97 | '$_counter', 98 | style: Theme.of(context).textTheme.display1, 99 | ), 100 | ], 101 | ), 102 | ), 103 | floatingActionButton: FloatingActionButton( 104 | onPressed: _incrementCounter, 105 | tooltip: 'Increment', 106 | child: Icon(Icons.add), 107 | ), // This trailing comma makes auto-formatting nicer for build methods. 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.3.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.0.5" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.14.11" 32 | cupertino_icons: 33 | dependency: "direct main" 34 | description: 35 | name: cupertino_icons 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "0.1.2" 39 | flutter: 40 | dependency: "direct main" 41 | description: flutter 42 | source: sdk 43 | version: "0.0.0" 44 | flutter_test: 45 | dependency: "direct dev" 46 | description: flutter 47 | source: sdk 48 | version: "0.0.0" 49 | matcher: 50 | dependency: transitive 51 | description: 52 | name: matcher 53 | url: "https://pub.flutter-io.cn" 54 | source: hosted 55 | version: "0.12.5" 56 | meta: 57 | dependency: transitive 58 | description: 59 | name: meta 60 | url: "https://pub.flutter-io.cn" 61 | source: hosted 62 | version: "1.1.7" 63 | path: 64 | dependency: transitive 65 | description: 66 | name: path 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "1.6.4" 70 | pedantic: 71 | dependency: transitive 72 | description: 73 | name: pedantic 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.8.0+1" 77 | quiver: 78 | dependency: transitive 79 | description: 80 | name: quiver 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "2.0.5" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.5.5" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.9.3" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "2.0.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.0.5" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.1.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "0.2.5" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.1.6" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.0.8" 145 | sdks: 146 | dart: ">=2.2.2 <3.0.0" 147 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: tflutter_1_5 2 | description: A new flutter module project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | # 15 | # This version is used _only_ for the Runner app, which is used if you just do 16 | # a `flutter run` or a `flutter make-host-app-editable`. It has no impact 17 | # on any other native host app that you embed your Flutter project into. 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.1.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # The following adds the Cupertino Icons font to your application. 28 | # Use with the CupertinoIcons class for iOS style icons. 29 | cupertino_icons: ^0.1.2 30 | 31 | dev_dependencies: 32 | flutter_test: 33 | sdk: flutter 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | flutter: 39 | # The following line ensures that the Material Icons font is 40 | # included with your application, so that you can use the icons in 41 | # the material Icons class. 42 | uses-material-design: true 43 | 44 | # To add Flutter specific assets to your application, add an assets section, 45 | # like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add Flutter specific custom fonts to your application, add a fonts 57 | # section here, in this "flutter" section. Each entry in this list should 58 | # have a "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | 76 | 77 | # This section identifies your Flutter project as a module meant for 78 | # embedding in a native host app. These identifiers should _not_ ordinarily 79 | # be changed after generation - they are used to ensure that the tooling can 80 | # maintain consistency when adding or modifying assets and plugins. 81 | # They also do not have any bearing on your native host application's 82 | # identifiers, which may be completely independent or the same as these. 83 | module: 84 | androidX: false 85 | androidPackage: com.example.tflutter_1_5 86 | iosBundleIdentifier: com.example.tflutter15 87 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:tflutter_1_5/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/tflutter_1_5.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_5/tflutter_1_5_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | .idea/ 8 | .vagrant/ 9 | .sconsign.dblite 10 | .svn/ 11 | 12 | *.swp 13 | profile 14 | 15 | DerivedData/ 16 | 17 | .generated/ 18 | 19 | *.pbxuser 20 | *.mode1v3 21 | *.mode2v3 22 | *.perspectivev3 23 | 24 | !default.pbxuser 25 | !default.mode1v3 26 | !default.mode2v3 27 | !default.perspectivev3 28 | 29 | xcuserdata 30 | 31 | *.moved-aside 32 | 33 | *.pyc 34 | *sync/ 35 | Icon? 36 | .tags* 37 | 38 | build/ 39 | .android/ 40 | .ios/ 41 | .flutter-plugins 42 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aedbb1835bd6eb44550293d57d4d124f19901f0 8 | channel: stable 9 | 10 | project_type: module 11 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/README.md: -------------------------------------------------------------------------------- 1 | # tflutter_1_9 2 | 3 | A new flutter module project. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.dev/). 9 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | // This widget is the root of your application. 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | title: 'Flutter Demo', 11 | theme: ThemeData( 12 | // This is the theme of your application. 13 | // 14 | // Try running your application with "flutter run". You'll see the 15 | // application has a blue toolbar. Then, without quitting the app, try 16 | // changing the primarySwatch below to Colors.green and then invoke 17 | // "hot reload" (press "r" in the console where you ran "flutter run", 18 | // or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the 19 | // counter didn't reset back to zero; the application is not restarted. 20 | primarySwatch: Colors.blue, 21 | ), 22 | home: MyHomePage(title: 'Flutter Demo Home Page'), 23 | ); 24 | } 25 | } 26 | 27 | class MyHomePage extends StatefulWidget { 28 | MyHomePage({Key key, this.title}) : super(key: key); 29 | 30 | // This widget is the home page of your application. It is stateful, meaning 31 | // that it has a State object (defined below) that contains fields that affect 32 | // how it looks. 33 | 34 | // This class is the configuration for the state. It holds the values (in this 35 | // case the title) provided by the parent (in this case the App widget) and 36 | // used by the build method of the State. Fields in a Widget subclass are 37 | // always marked "final". 38 | 39 | final String title; 40 | 41 | @override 42 | _MyHomePageState createState() => _MyHomePageState(); 43 | } 44 | 45 | class _MyHomePageState extends State { 46 | int _counter = 0; 47 | 48 | void _incrementCounter() { 49 | setState(() { 50 | // This call to setState tells the Flutter framework that something has 51 | // changed in this State, which causes it to rerun the build method below 52 | // so that the display can reflect the updated values. If we changed 53 | // _counter without calling setState(), then the build method would not be 54 | // called again, and so nothing would appear to happen. 55 | _counter++; 56 | }); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | // This method is rerun every time setState is called, for instance as done 62 | // by the _incrementCounter method above. 63 | // 64 | // The Flutter framework has been optimized to make rerunning build methods 65 | // fast, so that you can just rebuild anything that needs updating rather 66 | // than having to individually change instances of widgets. 67 | return Scaffold( 68 | appBar: AppBar( 69 | // Here we take the value from the MyHomePage object that was created by 70 | // the App.build method, and use it to set our appbar title. 71 | title: Text(widget.title), 72 | ), 73 | body: Center( 74 | // Center is a layout widget. It takes a single child and positions it 75 | // in the middle of the parent. 76 | child: Column( 77 | // Column is also a layout widget. It takes a list of children and 78 | // arranges them vertically. By default, it sizes itself to fit its 79 | // children horizontally, and tries to be as tall as its parent. 80 | // 81 | // Invoke "debug painting" (press "p" in the console, choose the 82 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 83 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 84 | // to see the wireframe for each widget. 85 | // 86 | // Column has various properties to control how it sizes itself and 87 | // how it positions its children. Here we use mainAxisAlignment to 88 | // center the children vertically; the main axis here is the vertical 89 | // axis because Columns are vertical (the cross axis would be 90 | // horizontal). 91 | mainAxisAlignment: MainAxisAlignment.center, 92 | children: [ 93 | Text( 94 | 'You have pushed the button this many times:', 95 | ), 96 | Text( 97 | '$_counter', 98 | style: Theme.of(context).textTheme.display1, 99 | ), 100 | ], 101 | ), 102 | ), 103 | floatingActionButton: FloatingActionButton( 104 | onPressed: _incrementCounter, 105 | tooltip: 'Increment', 106 | child: Icon(Icons.add), 107 | ), // This trailing comma makes auto-formatting nicer for build methods. 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.1.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.14.11" 32 | cupertino_icons: 33 | dependency: "direct main" 34 | description: 35 | name: cupertino_icons 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.1.3" 39 | flutter: 40 | dependency: "direct main" 41 | description: flutter 42 | source: sdk 43 | version: "0.0.0" 44 | flutter_test: 45 | dependency: "direct dev" 46 | description: flutter 47 | source: sdk 48 | version: "0.0.0" 49 | matcher: 50 | dependency: transitive 51 | description: 52 | name: matcher 53 | url: "https://pub.dartlang.org" 54 | source: hosted 55 | version: "0.12.5" 56 | meta: 57 | dependency: transitive 58 | description: 59 | name: meta 60 | url: "https://pub.dartlang.org" 61 | source: hosted 62 | version: "1.1.6" 63 | path: 64 | dependency: transitive 65 | description: 66 | name: path 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "1.6.2" 70 | pedantic: 71 | dependency: transitive 72 | description: 73 | name: pedantic 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.5.0" 77 | quiver: 78 | dependency: transitive 79 | description: 80 | name: quiver 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "2.0.2" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.5.5" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.9.3" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.0.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.0.4" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.2.4" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.1.6" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.0.8" 145 | sdks: 146 | dart: ">=2.2.0 <3.0.0" 147 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: tflutter_1_9 2 | description: A new flutter module project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | # 15 | # This version is used _only_ for the Runner app, which is used if you just do 16 | # a `flutter run` or a `flutter make-host-app-editable`. It has no impact 17 | # on any other native host app that you embed your Flutter project into. 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.1.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # The following adds the Cupertino Icons font to your application. 28 | # Use with the CupertinoIcons class for iOS style icons. 29 | cupertino_icons: ^0.1.2 30 | 31 | dev_dependencies: 32 | flutter_test: 33 | sdk: flutter 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | flutter: 39 | # The following line ensures that the Material Icons font is 40 | # included with your application, so that you can use the icons in 41 | # the material Icons class. 42 | uses-material-design: true 43 | 44 | # To add Flutter specific assets to your application, add an assets section, 45 | # like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add Flutter specific custom fonts to your application, add a fonts 57 | # section here, in this "flutter" section. Each entry in this list should 58 | # have a "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | 76 | 77 | # This section identifies your Flutter project as a module meant for 78 | # embedding in a native host app. These identifiers should _not_ ordinarily 79 | # be changed after generation - they are used to ensure that the tooling can 80 | # maintain consistency when adding or modifying assets and plugins. 81 | # They also do not have any bearing on your native host application's 82 | # identifiers, which may be completely independent or the same as these. 83 | module: 84 | androidX: false 85 | androidPackage: com.example.tflutter_1_9 86 | iosBundleIdentifier: com.example.tflutter19 87 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:tflutter_1_9/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/tflutter_1_9.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/tpl/tflutter_1_9/tflutter_1_9_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/tpl/tios/tios.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/tpl/tios/tios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /test/tpl/tios/tios/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /test/tpl/tios/tios/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /test/tpl/tios/tios/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // tios 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/tpl/tios/tiosTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/tpl/tios/tiosTests/tiosTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // tiosTests.m 3 | // tiosTests 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface tiosTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation tiosTests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | - (void)tearDown { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | - (void)testExample { 26 | // This is an example of a functional test case. 27 | // Use XCTAssert and related functions to verify your tests produce the correct results. 28 | } 29 | 30 | - (void)testPerformanceExample { 31 | // This is an example of a performance test case. 32 | [self measureBlock:^{ 33 | // Put the code you want to measure the time of here. 34 | }]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /test/tpl/tios/tiosUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/tpl/tios/tiosUITests/tiosUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // tiosUITests.m 3 | // tiosUITests 4 | // 5 | // Created by 兴往 on 2019/7/11. 6 | // Copyright © 2019 闲鱼. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface tiosUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation tiosUITests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | 20 | // In UI tests it is usually best to stop immediately when a failure occurs. 21 | self.continueAfterFailure = NO; 22 | 23 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 24 | [[[XCUIApplication alloc] init] launch]; 25 | 26 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 27 | } 28 | 29 | - (void)tearDown { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | } 32 | 33 | - (void)testExample { 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | @end 39 | --------------------------------------------------------------------------------