├── .gitignore ├── LICENSE ├── README.md ├── bin └── weexbox-cli.js ├── lib ├── create │ └── index.js ├── doctor │ ├── android │ │ ├── android-sdk.js │ │ ├── android-studio.js │ │ └── android-workflow.js │ ├── base │ │ └── process.js │ ├── doctor.js │ ├── index.js │ └── ios │ │ └── ios-workflow.js ├── index.js └── refimg │ ├── generate.js │ └── index.js ├── package.json ├── src ├── create │ └── index.ts ├── doctor │ ├── android │ │ ├── android-sdk.ts │ │ ├── android-studio.ts │ │ └── android-workflow.ts │ ├── base │ │ └── process.ts │ ├── doctor.ts │ ├── index.ts │ └── ios │ │ └── ios-workflow.ts └── refimg │ ├── generate.ts │ └── index.ts ├── template ├── App.vue └── index.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WeexBox 项目初始化工具 https://aygtech.github.io/weexbox/guide/#weexbox-cli 2 | 3 | # 介绍 4 | 5 | WeexBox 致力于打造一套简单、高效的基于 [weex](https://weex-project.io/cn/) 的APP混合开发解决方案。 6 | 7 | # 安装 8 | 9 | ``` 10 | npm install -g @weexbox/cli 11 | ``` 12 | # 使用 13 | 1. 快速创建 weex 项目 14 | 15 | ``` 16 | weexbox create 17 | ``` 18 | 19 | 1. 快速创建页面 20 | 21 | ``` 22 | weexbox page --template 23 | ``` 24 | 25 | ** 更多命令详情可通过``` weexbox -h```快速查看 ** 26 | 27 | ## 开发 WeexBox 的初衷 28 | 29 | weex给了vue开发者一条全新的道路,让前端开发者在APP中大放异彩。 30 | 然而,weex也给前端开发者一个错觉,误以为整个APP都可以用weex来做,而不需要原生的支持。 31 | 事实是,想要开发出优秀体验的APP,前端是离不开原生的,而且是重度依赖的。 32 | 所以,前端需要与原生端紧密配合,我们称之为大前端的紧紧拥抱... 33 | weex的重心放在了js渲染UI的能力上,对原生的扩展并不多。 34 | 于是我们想通过 WeexBox 35 | 36 | - 扩展 weex 的能力 37 | - 把最佳实践带入进来,提供大前端正确拥抱的姿势 38 | - 开发一些实用工具,带来更棒的开发体验 39 | - 填掉 weex 的坑 40 | 41 | 最终,开发者能够专注写bug了~~~ 42 | -------------------------------------------------------------------------------- /bin/weexbox-cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const program = require('commander') 3 | const fs = require('fs-extra') 4 | const path = require('path') 5 | const { Create } = require('../lib/create') 6 | const { Doctor } = require('../lib/doctor') 7 | const { Refimg } = require('../lib/refimg') 8 | 9 | program 10 | .version(fs.readJsonSync(path.join(__dirname, '../package.json')).version, '-v, --version') 11 | 12 | program 13 | .command('create ') 14 | .description('使用 weexbox 创建工程') 15 | .option('-f, --flutter', '含flutter的工程') 16 | .action((projectName, options) => { 17 | Create.createProject(projectName, options) 18 | }) 19 | 20 | program 21 | .command('doctor') 22 | .description('检查') 23 | .action(() => { 24 | console.log('检查环境中 ...') 25 | const doctor = new Doctor() 26 | console.log(doctor.diagnose()) 27 | }) 28 | 29 | program 30 | .command('page ') 31 | .description('在 src 目录下快速创建页面,支持多级路径,示例:weexbox page personCenter/profile') 32 | .option('-t, --template ', '自定义模板文件所在目录') 33 | .action((pageName, options) => { 34 | Create.createPage(pageName, options.template) 35 | }) 36 | program 37 | .command('refimg [path]') 38 | .description('刷新flutter图片配置; 可选参数 weex refimg d 默认生成【./lib/util/image_path_config.dart】 weex refimg ./lib/src/custom.dart 【生成自定义文件】') 39 | .action((path) => { 40 | Refimg.start(path) 41 | }) 42 | program.parse(process.argv) 43 | -------------------------------------------------------------------------------- /lib/create/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const path_1 = require("path"); 4 | const fs_extra_1 = require("fs-extra"); 5 | const chalk_1 = require("chalk"); 6 | const ora_1 = require("ora"); 7 | const validateProjectName = require("validate-npm-package-name"); 8 | const download = require("download-git-repo"); 9 | class Create { 10 | static createProject(projectName, options) { 11 | const result = validateProjectName(projectName); 12 | if (!result.validForNewPackages) { 13 | console.error(chalk_1.default.red(`无效的项目名称: '${projectName}'`)); 14 | const errors = result.errors; 15 | if (errors) { 16 | errors.forEach(err => { 17 | console.error(chalk_1.default.red(err)); 18 | }); 19 | } 20 | process.exit(1); 21 | } 22 | const targetDir = path_1.resolve(process.cwd(), projectName); 23 | if (fs_extra_1.existsSync(targetDir)) { 24 | console.error(chalk_1.default.red(`目录 ${chalk_1.default.cyan(targetDir)} 已存在`)); 25 | process.exit(1); 26 | } 27 | let templatePath = 'aygtech/weexbox-template'; 28 | if (options.flutter) { 29 | templatePath += '#flutter'; 30 | } 31 | const spinner = ora_1.default(`正在从 https://github.com/${templatePath} 下载模板\n如果您的网络不好,可以手动下载`).start(); 32 | download(templatePath, targetDir, (err) => { 33 | spinner.stop(); 34 | console.log(err ? err : `${projectName} 创建成功`); 35 | }); 36 | } 37 | static async createPage(pageName, templatePath) { 38 | try { 39 | await fs_extra_1.access('./src', fs_extra_1.constants.R_OK | fs_extra_1.constants.W_OK); 40 | const pagePath = path_1.resolve(process.cwd(), 'src', pageName); 41 | try { 42 | await fs_extra_1.access(pagePath); 43 | console.log(chalk_1.default.red(`页面: ${pageName}在 src 目录中已存在,请修改页面名称`)); 44 | } 45 | catch (e) { 46 | await fs_extra_1.ensureDir(pagePath); 47 | let templateDir = ''; 48 | if (templatePath) { 49 | templateDir = path_1.resolve(process.cwd(), templatePath); 50 | } 51 | else { 52 | templateDir = path_1.resolve(__dirname, '../../template'); 53 | } 54 | await fs_extra_1.copy(templateDir, pagePath, { recursive: true }); 55 | console.log(chalk_1.default.cyan('页面创建成功,路径:') + chalk_1.default.green(`${pagePath}`)); 56 | } 57 | } 58 | catch (e) { 59 | console.log(chalk_1.default.red(`页面创建过程出错:${e}`)); 60 | } 61 | } 62 | } 63 | exports.Create = Create; 64 | -------------------------------------------------------------------------------- /lib/doctor/android/android-sdk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const platform_1 = require("@weex-cli/utils/lib/platform/platform"); 6 | const process_1 = require("../base/process"); 7 | const version_1 = require("@weex-cli/utils/lib/base/version"); 8 | const android_studio_1 = require("./android-studio"); 9 | exports.kAndroidHome = 'ANDROID_HOME'; 10 | const numberedAndroidPlatformRe = new RegExp('^android-([0-9P]+)$'); 11 | const javaHomeEnvironmentVariable = 'JAVA_HOME'; 12 | exports.mustAndroidSdkVersion = 26; 13 | class AndroidSdkVersion { 14 | constructor(sdk, sdkLevel, platformName, buildToolsVersion) { 15 | this.sdk = sdk; 16 | this.sdkLevel = sdkLevel; 17 | this.platformName = platformName; 18 | this.buildToolsVersion = buildToolsVersion; 19 | this.sdk = sdk; 20 | this.sdkLevel = sdkLevel; 21 | this.platformName = platformName; 22 | this.buildToolsVersion = buildToolsVersion; 23 | } 24 | get buildToolsVersionName() { 25 | if (!this.buildToolsVersion) { 26 | return ''; 27 | } 28 | return `${this.buildToolsVersion.major}.${this.buildToolsVersion.minor}.${this.buildToolsVersion.patch}`; 29 | } 30 | get androidJarPath() { 31 | return this.getPlatformsPath('android.jar'); 32 | } 33 | get aaptPath() { 34 | return this.getBuildToolsPath('aapt'); 35 | } 36 | getPlatformsPath(itemName) { 37 | return path.join(this.sdk.directory, 'platforms', this.platformName, itemName); 38 | } 39 | getBuildToolsPath(binaryName) { 40 | if (!this.buildToolsVersionName) { 41 | return ''; 42 | } 43 | return path.join(this.sdk.directory, 'build-tools', this.buildToolsVersionName, binaryName); 44 | } 45 | validateSdkWellFormed() { 46 | if (this.exists(this.androidJarPath) !== null) { 47 | return [this.exists(this.androidJarPath)]; 48 | } 49 | if (this.canRun(this.aaptPath, ['v']) !== null) { 50 | return [this.canRun(this.aaptPath, ['v'])]; 51 | } 52 | return []; 53 | } 54 | exists(path) { 55 | if (!fs.existsSync(path)) { 56 | return `Android SDK file not found: ${path}.`; 57 | } 58 | return null; 59 | } 60 | canRun(path, args = []) { 61 | if (!process_1.canRunSync(path, args)) { 62 | return `Android SDK file not found: ${path}.`; 63 | } 64 | return null; 65 | } 66 | } 67 | exports.AndroidSdkVersion = AndroidSdkVersion; 68 | class AndroidSdk { 69 | constructor() { 70 | this.sdkVersions = []; 71 | this.androidStudio = new android_studio_1.AndroidStudio(); 72 | this.isMustAndroidSdkVersion = false; 73 | this.init(); 74 | } 75 | get adbPath() { 76 | return this.getPlatformToolsPath('adb'); 77 | } 78 | get sdkManagerPath() { 79 | return path.join(this.directory, 'tools', 'bin', 'sdkmanager'); 80 | } 81 | findJavaBinary() { 82 | if (this.androidStudio.javaPath) { 83 | return path.join(this.androidStudio.javaPath, 'bin', 'java'); 84 | } 85 | const javaHomeEnv = process.env[`${javaHomeEnvironmentVariable}`]; 86 | if (javaHomeEnv) { 87 | return path.join(javaHomeEnv, 'bin', 'java'); 88 | } 89 | } 90 | getPlatformToolsPath(binaryName) { 91 | return path.join(this.directory, 'platform-tools', binaryName); 92 | } 93 | validateSdkWellFormed() { 94 | if (!process_1.canRunSync(this.adbPath, ['version'])) { 95 | return [`Android SDK file not found: ${this.adbPath}.`]; 96 | } 97 | if (!this.sdkVersions.length || !this.latestVersion) { 98 | return [`Android SDK is missing command line tools; download from https://goo.gl/XxQghQ`]; 99 | } 100 | return this.latestVersion.validateSdkWellFormed(); 101 | } 102 | locateAndroidSdk() { 103 | this.directory = this.findAndroidHomeDir(); 104 | } 105 | findAndroidHomeDir() { 106 | let androidHomeDir; 107 | if (process.env[`${exports.kAndroidHome}`]) { 108 | androidHomeDir = process.env[`${exports.kAndroidHome}`]; 109 | } 110 | else if (platform_1.homedir) { 111 | if (platform_1.isLinux) { 112 | androidHomeDir = path.join(platform_1.homedir, 'Android', 'Sdk'); 113 | } 114 | else if (platform_1.isMacOS) { 115 | androidHomeDir = path.join(platform_1.homedir, 'Library', 'Android', 'sdk'); 116 | } 117 | else if (platform_1.isWindows) { 118 | androidHomeDir = path.join(platform_1.homedir, 'AppData', 'Local', 'Android', 'sdk'); 119 | } 120 | } 121 | if (androidHomeDir) { 122 | if (this.validSdkDirectory(androidHomeDir)) { 123 | return androidHomeDir; 124 | } 125 | if (this.validSdkDirectory(path.join(androidHomeDir, 'sdk'))) { 126 | return path.join(androidHomeDir, 'sdk'); 127 | } 128 | } 129 | const aaptBins = process_1.which('aapt'); 130 | for (let aaptBin in aaptBins) { 131 | const dir = path.resolve(aaptBin, '../../'); 132 | if (this.validSdkDirectory(dir)) { 133 | return dir; 134 | } 135 | } 136 | const adbBins = process_1.which('adb'); 137 | for (let adbBin in adbBins) { 138 | const dir = path.resolve(adbBin, '../../'); 139 | if (this.validSdkDirectory(dir)) { 140 | return dir; 141 | } 142 | } 143 | return null; 144 | } 145 | validSdkDirectory(dir) { 146 | const dirPath = path.join(dir, 'platform-tools'); 147 | if (fs.existsSync(dirPath)) { 148 | return fs.statSync(dirPath).isDirectory(); 149 | } 150 | return false; 151 | } 152 | init() { 153 | this.locateAndroidSdk(); 154 | if (!this.directory) { 155 | return; 156 | } 157 | let platforms = []; 158 | const platformsDir = path.join(this.directory, 'platforms'); 159 | let buildTools = []; 160 | const buildToolsDir = path.join(this.directory, 'build-tools'); 161 | if (fs.existsSync(platformsDir)) { 162 | platforms = fs.readdirSync(platformsDir); 163 | } 164 | if (fs.existsSync(buildToolsDir)) { 165 | buildTools = fs.readdirSync(buildToolsDir); 166 | } 167 | platforms.map(platformName => { 168 | let matchVersion = platformName.match(numberedAndroidPlatformRe); 169 | if (Array.isArray(matchVersion) && matchVersion.length > 1) { 170 | matchVersion = matchVersion[1]; 171 | } 172 | else { 173 | return null; 174 | } 175 | if (matchVersion === 'P') { 176 | matchVersion = '28'; 177 | } 178 | const platformVersion = Number(matchVersion); 179 | if (exports.mustAndroidSdkVersion === platformVersion) { 180 | this.isMustAndroidSdkVersion = true; 181 | } 182 | let buildToolsVersion; 183 | buildTools.forEach(version => { 184 | const versionOption = version_1.versionParse(version); 185 | if (versionOption && versionOption.major === platformVersion) { 186 | buildToolsVersion = version_1.versionParse(version); 187 | } 188 | }); 189 | if (!buildTools) { 190 | return null; 191 | } 192 | this.sdkVersions.push(new AndroidSdkVersion(this, platformVersion, platformName, buildToolsVersion)); 193 | }); 194 | if (this.sdkVersions.length) { 195 | this.latestVersion = this.sdkVersions[this.sdkVersions.length - 1]; 196 | } 197 | } 198 | } 199 | exports.AndroidSdk = AndroidSdk; 200 | -------------------------------------------------------------------------------- /lib/doctor/android/android-studio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const platform_1 = require("@weex-cli/utils/lib/platform/platform"); 4 | const path = require("path"); 5 | const fs = require("fs"); 6 | const ios_workflow_1 = require("../ios/ios-workflow"); 7 | const plist_utils_1 = require("@weex-cli/utils/lib/ios/plist-utils"); 8 | const version_1 = require("@weex-cli/utils/lib/base/version"); 9 | const process_1 = require("../base/process"); 10 | const debug = require("debug"); 11 | const DEBUG = debug('plugin:doctor:android-studio'); 12 | const _dotHomeStudioVersionMatcher = new RegExp('^.AndroidStudio([^d]*)([d.]+)'); 13 | class AndroidStudioValid { 14 | constructor(directory, option) { 15 | this.directory = directory; 16 | this.option = option; 17 | this.isValid = true; 18 | this.validationMessages = []; 19 | this.directory = directory; 20 | this.configured = this.option.configured; 21 | this.version = this.option.version; 22 | this.init(); 23 | } 24 | init() { 25 | if (this.configured) { 26 | this.validationMessages.push(`android-studio-dir = ${this.configured}`); 27 | } 28 | if (!fs.existsSync(this.directory)) { 29 | this.validationMessages.push(`Android Studio not found at ${this.directory}`); 30 | return; 31 | } 32 | let javaPath = platform_1.isMacOS 33 | ? path.join(this.directory, 'jre', 'jdk', 'Contents', 'Home') 34 | : path.join(this.directory, 'jre'); 35 | const javaExecutable = path.join(javaPath, 'bin', 'java'); 36 | if (!process_1.canRunSync(javaExecutable, ['-version'])) { 37 | this.validationMessages.push(`Unable to find bundled Java version.`); 38 | } 39 | else { 40 | const result = process_1.runSync(javaExecutable, ['-version']); 41 | if (result && result.status === 0) { 42 | const versionLines = result.stderr.toString().split('\n'); 43 | const javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; 44 | this.validationMessages.push(`Java version ${javaVersion}`); 45 | this.isValid = true; 46 | this.javaPath = javaPath; 47 | } 48 | else { 49 | this.validationMessages.push('Unable to determine bundled Java version.'); 50 | } 51 | } 52 | } 53 | } 54 | exports.AndroidStudioValid = AndroidStudioValid; 55 | class AndroidStudio { 56 | constructor() { 57 | this.iosWorkflow = new ios_workflow_1.IOSWorkflow(); 58 | this.latestValid(); 59 | } 60 | latestValid() { 61 | const studios = this.allInstalled(); 62 | if (studios.length) { 63 | this.javaPath = studios[studios.length - 1].javaPath; 64 | } 65 | } 66 | allInstalled() { 67 | return platform_1.isMacOS ? this.allMacOS() : this.allLinuxOrWindows(); 68 | } 69 | allMacOS() { 70 | let directories = []; 71 | this.checkForStudio('/Applications').forEach(name => { 72 | directories.push(`/Applications/${name}`); 73 | }); 74 | this.checkForStudio(path.join(platform_1.homedir, 'Applications')).forEach(name => { 75 | directories.push(path.join(platform_1.homedir, 'Applications', name)); 76 | }); 77 | return directories.map(path => this.fromMacOSBundle(path)); 78 | } 79 | checkForStudio(path) { 80 | if (!fs.existsSync(path)) { 81 | return []; 82 | } 83 | const candidatePaths = []; 84 | try { 85 | const directories = fs.readdirSync(path); 86 | for (let name of directories) { 87 | if (name.startsWith('Android Studio') && name.endsWith('.app')) { 88 | candidatePaths.push(name); 89 | } 90 | } 91 | } 92 | catch (e) { 93 | console.error(e); 94 | } 95 | return candidatePaths; 96 | } 97 | fromMacOSBundle(bundlePath) { 98 | const studioPath = path.join(bundlePath, 'Contents'); 99 | const plistFile = path.join(studioPath, 'Info.plist'); 100 | const versionString = this.iosWorkflow.getPlistValueFromFile(plistFile, plist_utils_1.kCFBundleShortVersionStringKey); 101 | let version; 102 | if (versionString) { 103 | version = version_1.versionParse(versionString); 104 | } 105 | return new AndroidStudioValid(studioPath, { version: version }); 106 | } 107 | fromHomeDot(homeDotDir) { 108 | const versionMatch = path.basename(homeDotDir).match(_dotHomeStudioVersionMatcher)[1]; 109 | if (versionMatch.length !== 3) { 110 | return null; 111 | } 112 | const version = version_1.versionParse(versionMatch[2]); 113 | if (!version) { 114 | return null; 115 | } 116 | let installPath; 117 | if (fs.existsSync(path.join(homeDotDir, 'system', '.home'))) { 118 | installPath = path.join(homeDotDir, 'system', '.home'); 119 | } 120 | if (installPath) { 121 | return new AndroidStudioValid(installPath, { version: version }); 122 | } 123 | return null; 124 | } 125 | allLinuxOrWindows() { 126 | let studios = []; 127 | function hasStudioAt(path, newerThan) { 128 | return studios.every(studio => { 129 | if (studio.directory !== path) { 130 | return false; 131 | } 132 | if (newerThan) { 133 | return version_1.compareVersion(studio.version, newerThan); 134 | } 135 | return true; 136 | }); 137 | } 138 | if (fs.existsSync(platform_1.homedir)) { 139 | for (let entity of fs.readdirSync(platform_1.homedir)) { 140 | const homeDotDir = path.join(platform_1.homedir, entity); 141 | try { 142 | let homeDotDirType = fs.statSync(homeDotDir); 143 | if (homeDotDirType && homeDotDirType.isDirectory() && entity.startsWith('.AndroidStudio')) { 144 | const studio = this.fromHomeDot(homeDotDir); 145 | if (studio && !hasStudioAt(studio.directory, studio.version)) { 146 | studios = studios.filter(other => other.directory !== studio.directory); 147 | studios.push(studio); 148 | } 149 | } 150 | } 151 | catch (error) { 152 | DEBUG(error, homeDotDir); 153 | } 154 | } 155 | } 156 | function checkWellKnownPath(path) { 157 | if (fs.existsSync(path) && !hasStudioAt(path)) { 158 | studios.push(new AndroidStudioValid(path)); 159 | } 160 | } 161 | if (platform_1.isLinux) { 162 | checkWellKnownPath('/opt/android-studio'); 163 | checkWellKnownPath(`${platform_1.homedir}/android-studio`); 164 | } 165 | return studios; 166 | } 167 | } 168 | exports.AndroidStudio = AndroidStudio; 169 | -------------------------------------------------------------------------------- /lib/doctor/android/android-workflow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const doctor_1 = require("../doctor"); 4 | const android_sdk_1 = require("./android-sdk"); 5 | const process_1 = require("../base/process"); 6 | const jdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/'; 7 | class AndroidWorkflow { 8 | get appliesToHostPlatform() { 9 | return true; 10 | } 11 | } 12 | exports.AndroidWorkflow = AndroidWorkflow; 13 | class AndroidValidator { 14 | constructor() { 15 | this.messages = []; 16 | this.androidSdk = new android_sdk_1.AndroidSdk(); 17 | this.title = 'Android toolchain - develop for Android devices'; 18 | } 19 | validate() { 20 | if (!this.androidSdk.directory) { 21 | if (process.env[`${android_sdk_1.kAndroidHome}`]) { 22 | const androidHomeDir = process.env[`${android_sdk_1.kAndroidHome}`]; 23 | this.messages.push(new doctor_1.ValidationMessage(`${android_sdk_1.kAndroidHome} = ${androidHomeDir} 24 | but Android SDK not found at this location.`, true)); 25 | } 26 | else { 27 | this.messages.push(new doctor_1.ValidationMessage(`Unable to locate Android SDK. 28 | Install Android Studio from: https://developer.android.com/studio/index.html 29 | On first launch it will assist you in installing the Android SDK components. 30 | If Android SDK has been installed to a custom location, set ${android_sdk_1.kAndroidHome} to that location.`, true)); 31 | } 32 | return new doctor_1.ValidationResult(0, this.messages); 33 | } 34 | if (!this.androidSdk.isMustAndroidSdkVersion) { 35 | this.messages.push(new doctor_1.ValidationMessage(`There is no required version SDK plaform android-${android_sdk_1.mustAndroidSdkVersion}.`, false, true)); 36 | } 37 | this.messages.push(new doctor_1.ValidationMessage(`Android SDK at ${this.androidSdk.directory}`)); 38 | let sdkVersionText; 39 | if (this.androidSdk.latestVersion) { 40 | sdkVersionText = `Android SDK ${this.androidSdk.latestVersion.buildToolsVersionName}`; 41 | this.messages.push(new doctor_1.ValidationMessage(`Platform ${this.androidSdk.latestVersion.platformName}, build-tools ${this.androidSdk.latestVersion.buildToolsVersionName}`)); 42 | } 43 | if (process.env[`${android_sdk_1.kAndroidHome}`]) { 44 | const androidHomeDir = process.env[`${android_sdk_1.kAndroidHome}`]; 45 | this.messages.push(new doctor_1.ValidationMessage(`${android_sdk_1.kAndroidHome} = ${androidHomeDir}\n`)); 46 | } 47 | const validationResult = this.androidSdk.validateSdkWellFormed(); 48 | if (validationResult.length) { 49 | this.messages.push(new doctor_1.ValidationMessage(`Try re-installing or updating your Android SDK.`)); 50 | return new doctor_1.ValidationResult(1, this.messages, sdkVersionText); 51 | } 52 | const javaBinary = this.androidSdk.findJavaBinary(); 53 | if (!javaBinary) { 54 | this.messages.push(new doctor_1.ValidationMessage(`No Java Development Kit (JDK) found; You must have the environment 55 | variable JAVA_HOME set and the java binary in your PATH. 56 | You can download the JDK from ${jdkDownload}.`, true)); 57 | } 58 | if (!this.checkJavaVersion(javaBinary)) { 59 | return new doctor_1.ValidationResult(1, this.messages, sdkVersionText); 60 | } 61 | return new doctor_1.ValidationResult(2, this.messages, sdkVersionText); 62 | } 63 | checkJavaVersion(javaBinary) { 64 | if (!process_1.canRunSync(javaBinary, ['-version'])) { 65 | this.messages.push(new doctor_1.ValidationMessage(`Cannot execute ${javaBinary} to determine the version.`, true)); 66 | return false; 67 | } 68 | let javaVersion; 69 | const result = process_1.runSync(javaBinary, ['-version']); 70 | if (result.status === 0) { 71 | const versionLines = result.stderr.toString().split('\n'); 72 | javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; 73 | } 74 | if (!javaVersion) { 75 | this.messages.push(new doctor_1.ValidationMessage(`Could not determine java version.`, true)); 76 | return false; 77 | } 78 | this.messages.push(new doctor_1.ValidationMessage(`Java version ${javaVersion}.`)); 79 | return true; 80 | } 81 | licensesAccepted() { 82 | } 83 | } 84 | exports.AndroidValidator = AndroidValidator; 85 | -------------------------------------------------------------------------------- /lib/doctor/base/process.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const platform_1 = require("@weex-cli/utils/lib/platform/platform"); 4 | const child_process_1 = require("child_process"); 5 | const fs = require("fs"); 6 | function runAsync(command, args = []) { 7 | return new Promise((resolve, reject) => { 8 | let result; 9 | try { 10 | result = child_process_1.spawnSync(command, args); 11 | resolve(result); 12 | } 13 | catch (e) { 14 | reject(`Exit code ${result.status} from: ${command}:\n${result}`); 15 | } 16 | }); 17 | } 18 | exports.runAsync = runAsync; 19 | function cleanInput(s) { 20 | if (/[^A-Za-z0-9_\/:=-]/.test(s)) { 21 | s = "'" + s.replace(/'/g, "'\\''") + "'"; 22 | s = s 23 | .replace(/^(?:'')+/g, '') 24 | .replace(/\\'''/g, "\\'"); 25 | } 26 | return s; 27 | } 28 | function commandExistsWindowsSync(commandName, cleanedCommandName, callback) { 29 | try { 30 | const stdout = child_process_1.execSync('where ' + cleanedCommandName, { stdio: [] }); 31 | return !!stdout; 32 | } 33 | catch (error) { 34 | return false; 35 | } 36 | } 37 | function fileNotExistsSync(commandName) { 38 | try { 39 | fs.accessSync(commandName, fs.constants.F_OK); 40 | return false; 41 | } 42 | catch (e) { 43 | return true; 44 | } 45 | } 46 | function localExecutableSync(commandName) { 47 | try { 48 | fs.accessSync(commandName, fs.constants.F_OK | fs.constants.X_OK); 49 | return false; 50 | } 51 | catch (e) { 52 | return true; 53 | } 54 | } 55 | function commandExistsUnixSync(commandName, cleanedCommandName, callback) { 56 | if (fileNotExistsSync(commandName)) { 57 | try { 58 | const stdout = child_process_1.execSync('command -v ' + cleanedCommandName + ' 2>/dev/null' + ' && { echo >&1 ' + cleanedCommandName + '; exit 0; }'); 59 | return !!stdout; 60 | } 61 | catch (error) { 62 | return false; 63 | } 64 | } 65 | return localExecutableSync(commandName); 66 | } 67 | function commandExistsSync(commandName) { 68 | const cleanedCommandName = cleanInput(commandName); 69 | if (platform_1.isWindows) { 70 | return commandExistsWindowsSync(commandName, cleanedCommandName); 71 | } 72 | else { 73 | return commandExistsUnixSync(commandName, cleanedCommandName); 74 | } 75 | } 76 | exports.commandExistsSync = commandExistsSync; 77 | function which(execName, args = []) { 78 | const spawnArgs = [execName, ...args]; 79 | const result = child_process_1.spawnSync('which', spawnArgs); 80 | if (result.status !== 0) { 81 | return []; 82 | } 83 | const lines = result.stdout 84 | .toString() 85 | .trim() 86 | .split('\n'); 87 | return lines; 88 | } 89 | exports.which = which; 90 | function runSync(commandName, args = []) { 91 | let result; 92 | try { 93 | result = child_process_1.spawnSync(commandName, args); 94 | return result; 95 | } 96 | catch (e) { 97 | return null; 98 | } 99 | } 100 | exports.runSync = runSync; 101 | function canRunSync(commandName, args = []) { 102 | let result; 103 | try { 104 | result = child_process_1.spawnSync(commandName, args); 105 | if (result.status === 0) { 106 | return true; 107 | } 108 | return false; 109 | } 110 | catch (e) { 111 | return false; 112 | } 113 | } 114 | exports.canRunSync = canRunSync; 115 | -------------------------------------------------------------------------------- /lib/doctor/doctor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const android_workflow_1 = require("./android/android-workflow"); 4 | const ios_workflow_1 = require("./ios/ios-workflow"); 5 | const platform_1 = require("@weex-cli/utils/lib/platform/platform"); 6 | const colors = require("colors"); 7 | var ValidationType; 8 | (function (ValidationType) { 9 | ValidationType[ValidationType["missing"] = 0] = "missing"; 10 | ValidationType[ValidationType["partial"] = 1] = "partial"; 11 | ValidationType[ValidationType["installed"] = 2] = "installed"; 12 | })(ValidationType = exports.ValidationType || (exports.ValidationType = {})); 13 | class ValidatorTask { 14 | constructor(validator, result) { 15 | this.validator = validator; 16 | this.result = result; 17 | this.validator = validator; 18 | this.result = result; 19 | } 20 | } 21 | class ValidationResult { 22 | constructor(type, messages, statusInfo) { 23 | this.type = type; 24 | this.messages = messages; 25 | this.statusInfo = statusInfo; 26 | this.type = type; 27 | this.messages = messages; 28 | } 29 | get leadingBox() { 30 | switch (this.type) { 31 | case 0: 32 | return '[✗]'; 33 | case 2: 34 | return '[✓]'; 35 | case 1: 36 | return '[!]'; 37 | } 38 | return null; 39 | } 40 | } 41 | exports.ValidationResult = ValidationResult; 42 | class Doctor { 43 | constructor() { 44 | this.validators = []; 45 | this.iosWorkflow = new ios_workflow_1.IOSWorkflow(); 46 | this.androidWorkflow = new android_workflow_1.AndroidWorkflow(); 47 | this.getValidators(); 48 | } 49 | getValidators() { 50 | if (this.androidWorkflow.appliesToHostPlatform) { 51 | this.validators.push(new android_workflow_1.AndroidValidator()); 52 | } 53 | if (!platform_1.isWindows && this.iosWorkflow.appliesToHostPlatform) { 54 | this.validators.push(new ios_workflow_1.IOSValidator()); 55 | } 56 | } 57 | startValidatorTasks() { 58 | const tasks = []; 59 | for (let validator of this.validators) { 60 | tasks.push(new ValidatorTask(validator, validator.validate())); 61 | } 62 | return tasks; 63 | } 64 | diagnose() { 65 | const taskList = this.startValidatorTasks(); 66 | let messageResult = ''; 67 | for (let validatorTask of taskList) { 68 | const validator = validatorTask.validator; 69 | const results = []; 70 | let result; 71 | let color; 72 | results.push(validatorTask.result); 73 | result = this.mergeValidationResults(results); 74 | color = 75 | result.type === 0 76 | ? colors.red 77 | : result.type === 2 78 | ? colors.green 79 | : colors.yellow; 80 | messageResult += `${color(`\n${result.leadingBox} ${validator.title}\n`)}`; 81 | for (let message of result.messages) { 82 | const text = message.message.replace('\n', '\n '); 83 | if (message.isError) { 84 | messageResult += `${colors.red(` ✗ ${text}`)}\n`; 85 | } 86 | else if (message.isWaring) { 87 | messageResult += `${colors.yellow(` ! ${text}`)}\n`; 88 | } 89 | else { 90 | messageResult += ` • ${text}\n`; 91 | } 92 | } 93 | } 94 | return messageResult; 95 | } 96 | mergeValidationResults(results) { 97 | let mergedType = results[0].type; 98 | const mergedMessages = []; 99 | for (let result of results) { 100 | switch (result.type) { 101 | case 2: 102 | if (mergedType === 0) { 103 | mergedType = 1; 104 | } 105 | break; 106 | case 1: 107 | mergedType = 1; 108 | break; 109 | case 0: 110 | if (mergedType === 2) { 111 | mergedType = 1; 112 | } 113 | break; 114 | default: 115 | break; 116 | } 117 | mergedMessages.push(...result.messages); 118 | } 119 | return new ValidationResult(mergedType, mergedMessages, results[0].statusInfo); 120 | } 121 | } 122 | exports.Doctor = Doctor; 123 | class Workflow { 124 | } 125 | exports.Workflow = Workflow; 126 | class DoctorValidator { 127 | } 128 | exports.DoctorValidator = DoctorValidator; 129 | class ValidationMessage { 130 | constructor(message, isError = false, isWaring = false) { 131 | this.message = message; 132 | this.isError = isError; 133 | this.isWaring = isWaring; 134 | this.message = message; 135 | this.isError = isError; 136 | this.isWaring = isWaring; 137 | } 138 | } 139 | exports.ValidationMessage = ValidationMessage; 140 | -------------------------------------------------------------------------------- /lib/doctor/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var doctor_1 = require("./doctor"); 4 | exports.Doctor = doctor_1.Doctor; 5 | -------------------------------------------------------------------------------- /lib/doctor/ios/ios-workflow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const doctor_1 = require("../doctor"); 4 | const mac_1 = require("@weex-cli/utils/lib/ios/mac"); 5 | const cocoapods_1 = require("@weex-cli/utils/lib/ios/cocoapods"); 6 | const process_1 = require("@weex-cli/utils/lib/process/process"); 7 | const child_process_1 = require("child_process"); 8 | const version_1 = require("@weex-cli/utils/lib/base/version"); 9 | const plist = require("@weex-cli/utils/lib/ios/plist-utils"); 10 | class IOSWorkflow { 11 | get appliesToHostPlatform() { 12 | return process.platform === 'darwin'; 13 | } 14 | getPlistValueFromFile(path, key) { 15 | return plist.getValueFromFile(path, key); 16 | } 17 | } 18 | exports.IOSWorkflow = IOSWorkflow; 19 | class IOSValidator { 20 | constructor() { 21 | this.messages = []; 22 | this.xcodeStatus = 0; 23 | this.brewStatus = 0; 24 | this.cocoaPods = new cocoapods_1.CocoaPods(); 25 | this.xcode = new mac_1.Xcode(); 26 | this.title = 'iOS toolchain - develop for iOS devices'; 27 | } 28 | get hasHomebrew() { 29 | return !!process_1.which('brew').length; 30 | } 31 | get hasIDeviceInstaller() { 32 | try { 33 | return child_process_1.spawnSync('ideviceinstaller', ['-h']).status === 0; 34 | } 35 | catch (e) { 36 | return false; 37 | } 38 | } 39 | get hasIosDeploy() { 40 | try { 41 | return child_process_1.spawnSync('ios-deploy', ['--version']).status === 0; 42 | } 43 | catch (e) { 44 | return false; 45 | } 46 | } 47 | get iosDeployVersionText() { 48 | try { 49 | return child_process_1.spawnSync('ios-deploy', ['--version']) 50 | .stdout.toString() 51 | .replace('\n', ''); 52 | } 53 | catch (e) { 54 | return ''; 55 | } 56 | } 57 | get iosDeployMinimumVersion() { 58 | return '1.9.2'; 59 | } 60 | get iosDeployIsInstalledAndMeetsVersionCheck() { 61 | if (!this.hasIosDeploy) { 62 | return false; 63 | } 64 | const version = version_1.versionParse(this.iosDeployVersionText); 65 | return version_1.compareVersion(version, version_1.versionParse(this.iosDeployMinimumVersion)); 66 | } 67 | validate() { 68 | if (this.xcode.isInstalled) { 69 | this.xcodeStatus = 2; 70 | this.messages.push(new doctor_1.ValidationMessage(`Xcode at ${this.xcode.xcodeSelectPath}`)); 71 | this.xcodeVersionInfo = this.xcode.versionText; 72 | if (this.xcodeVersionInfo && this.xcodeVersionInfo.includes(',')) { 73 | this.xcodeVersionInfo = this.xcodeVersionInfo.substring(0, this.xcodeVersionInfo.indexOf(',')); 74 | this.messages.push(new doctor_1.ValidationMessage(this.xcodeVersionInfo)); 75 | } 76 | if (!this.xcode.isInstalledAndMeetsVersionCheck) { 77 | this.xcodeStatus = 1; 78 | this.messages.push(new doctor_1.ValidationMessage(`Weex requires a minimum Xcode version of ${mac_1.XcodeRequiredVersionMajor}.${mac_1.XcodeRequiredVersionMinor}.0.\n 79 | Download the latest version or update via the Mac App Store.`, true)); 80 | } 81 | if (!this.xcode.eulaSigned) { 82 | this.xcodeStatus = 1; 83 | this.messages.push(new doctor_1.ValidationMessage("Xcode end user license agreement not signed; open Xcode or run the command 'sudo xcodebuild -license'.", true)); 84 | } 85 | if (!this.xcode.isSimctlInstalled) { 86 | this.xcodeStatus = 1; 87 | this.messages.push(new doctor_1.ValidationMessage(`Xcode requires additional components to be installed in order to run.\n' 88 | Launch Xcode and install additional required components when prompted.`, true)); 89 | } 90 | } 91 | else { 92 | this.xcodeStatus = 0; 93 | if (!this.xcode.xcodeSelectPath) { 94 | this.messages.push(new doctor_1.ValidationMessage(`Xcode not installed; this is necessary for iOS development.\n 95 | Download at https://developer.apple.com/xcode/download/.`, true)); 96 | } 97 | else { 98 | this.messages.push(new doctor_1.ValidationMessage(`Xcode installation is incomplete; a full installation is necessary for iOS development.\n 99 | Download at: https://developer.apple.com/xcode/download/\n 100 | Or install Xcode via the App Store.\n 101 | Once installed, run:\n 102 | sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`, true)); 103 | } 104 | } 105 | if (this.hasHomebrew) { 106 | this.brewStatus = 2; 107 | const cocoaPodsStatus = this.cocoaPods.evaluateCocoaPodsInstallation; 108 | if (cocoaPodsStatus === cocoapods_1.CocoaPodsStatus.recommended) { 109 | if (this.cocoaPods.isCocoaPodsInitialized) { 110 | this.messages.push(new doctor_1.ValidationMessage(`CocoaPods version ${this.cocoaPods.cocoaPodsVersionText}`)); 111 | } 112 | else { 113 | this.brewStatus = 1; 114 | this.messages.push(new doctor_1.ValidationMessage(`CocoaPods installed but not initialized.\n 115 | ${cocoapods_1.noCocoaPodsConsequence}\n 116 | To initialize CocoaPods, run:\n 117 | pod setup\n 118 | once to finalize CocoaPods\' installation.`, true)); 119 | } 120 | } 121 | else { 122 | this.brewStatus = 1; 123 | if (cocoaPodsStatus === cocoapods_1.CocoaPodsStatus.notInstalled) { 124 | this.messages.push(new doctor_1.ValidationMessage(`CocoaPods not installed.\n 125 | ${cocoapods_1.noCocoaPodsConsequence}\n 126 | To install: 127 | ${cocoapods_1.cocoaPodsInstallInstructions}`, true)); 128 | } 129 | else { 130 | this.messages.push(new doctor_1.ValidationMessage(`CocoaPods out of date (${this.cocoaPods.cocoaPodsRecommendedVersion} is recommended).\n 131 | ${cocoapods_1.noCocoaPodsConsequence}\n 132 | To upgrade:\n 133 | ${cocoapods_1.cocoaPodsUpgradeInstructions}`, true)); 134 | } 135 | } 136 | } 137 | else { 138 | this.brewStatus = 0; 139 | this.messages.push(new doctor_1.ValidationMessage(`Brew not installed; use this to install tools for iOS device development.\n 140 | Download brew at https://brew.sh/.`, true)); 141 | } 142 | return new doctor_1.ValidationResult([this.xcodeStatus, this.brewStatus].reduce(this.mergeValidationTypes), this.messages, this.xcodeVersionInfo); 143 | } 144 | mergeValidationTypes(t1, t2) { 145 | return t1 === t2 ? t1 : 1; 146 | } 147 | } 148 | exports.IOSValidator = IOSValidator; 149 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-extra') 3 | const chalk = require('chalk') 4 | const validateProjectName = require('validate-npm-package-name') 5 | const download = require('download-git-repo') 6 | 7 | function create(projectName) { 8 | const result = validateProjectName(projectName) 9 | if (!result.validForNewPackages) { 10 | console.error(chalk.red(`Invalid project name: '${projectName}'`)) 11 | const errors = result.errors 12 | if (errors) { 13 | errors.forEach(err => { 14 | console.error(chalk.red(err)) 15 | }) 16 | } 17 | process.exit(1) 18 | } 19 | const targetDir = path.resolve(process.cwd(), projectName) 20 | if (fs.existsSync(targetDir)) { 21 | console.error(chalk.red(`Target directory ${chalk.cyan(targetDir)} already exists.`)) 22 | process.exit(1) 23 | } 24 | console.log('正在从https://github.com/aygtech/weexbox-template下载模板\n如果您的网络不好,可以手动下载') 25 | download('aygtech/weexbox-template', targetDir, function(err) { 26 | console.log(err ? err : `${projectName} is created!`) 27 | }) 28 | } 29 | 30 | /** 31 | * 在 projectRoot/src下创建页面 32 | * @param {*} pageNmae 页面名称,支持多级页面 33 | */ 34 | async function createPage(pageName, templatePath){ 35 | try { 36 | await fs.access('./src', fs.constants.R_OK|fs.constants.W_OK) 37 | 38 | const pagePath = path.resolve(process.cwd(), 'src', pageName) 39 | try { 40 | await fs.access(pagePath) 41 | console.log(chalk.red(`页面: ${pageName}在 src 目录中已存在,请修改页面名称`)) 42 | } catch(e) { 43 | await fs.mkdir(pagePath, {recursive: true}) 44 | 45 | const templateDir = templatePath ? templatePath : path.resolve(__dirname, '../template') 46 | await fs.writeFile(path.resolve(pagePath, 'index.js'), await fs.readFile(path.resolve(templateDir, 'index.js'))) 47 | await fs.writeFile(path.resolve(pagePath, 'App.vue'), await fs.readFile(path.resolve(templateDir, 'App.vue'))) 48 | console.log(chalk.cyan('页面创建成功,路径:')+ chalk.green(`${pagePath}`)); 49 | } 50 | } catch(e) { 51 | console.log(chalk.red(`页面创建过程出错:${e}`)) 52 | } 53 | } 54 | 55 | module.exports = { 56 | create, 57 | createPage 58 | } 59 | -------------------------------------------------------------------------------- /lib/refimg/generate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fs_extra_1 = require("fs-extra"); 4 | class Generate { 5 | static start(dartPath, images) { 6 | const attributes = []; 7 | images.forEach(image => { 8 | const attName = this.getImgKey(image); 9 | const attribute = ' static const ' + attName + ' = ' + '"images/' + image + '";'; 10 | attributes.push(attribute); 11 | }); 12 | const fileName = this.getFlieName(dartPath); 13 | const className = this.getClasseName(fileName); 14 | const content = attributes.join('\n'); 15 | const classContent = ' // 由weexbox自动生成,无需手动编辑\n class ' + className + ' {\n' + content + '\n}'; 16 | this.createDartFile(dartPath, classContent); 17 | } 18 | static getFlieName(path) { 19 | let modalName = ''; 20 | const att = path.split('/'); 21 | if (att.length > 1) { 22 | path = att[att.length - 1]; 23 | } 24 | modalName = path.replace('.dart', ''); 25 | return modalName; 26 | } 27 | static createDartFile(fileName, content) { 28 | fs_extra_1.writeFile(fileName, content, 'utf8', (error) => { 29 | if (error) { 30 | console.log('\x1B[31m%s\x1B[0m', 'error: ' + error + '\n'); 31 | } 32 | else { 33 | console.log('\x1B[36m%s\x1B[0m', 'succeed: dart文件创建成功' + fileName + '\n'); 34 | } 35 | }); 36 | } 37 | static getImgKey(key) { 38 | key = key.replace(/.png/g, ''); 39 | key = key.replace(/.jpg/g, ''); 40 | key = key.toUpperCase(); 41 | return key; 42 | } 43 | static getClasseName(className) { 44 | let i = 0; 45 | const newStrs = []; 46 | let underlineIndex = -1; 47 | for (i = 0; i < className.length; i++) { 48 | const str = className.charAt(i); 49 | let needToUpper = false; 50 | if (i === 0 || i === underlineIndex + 1) { 51 | needToUpper = true; 52 | } 53 | if (str.indexOf('_') !== -1) { 54 | underlineIndex = i; 55 | } 56 | else { 57 | const chart = needToUpper === true ? str.toUpperCase() : str; 58 | newStrs.push(chart); 59 | } 60 | } 61 | return newStrs.join(''); 62 | } 63 | } 64 | exports.Generate = Generate; 65 | -------------------------------------------------------------------------------- /lib/refimg/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fs_extra_1 = require("fs-extra"); 4 | const readline_1 = require("readline"); 5 | const generate_1 = require("./generate"); 6 | class Refimg { 7 | static start(path) { 8 | const pathIsOK = this.setConfigPath(); 9 | if (pathIsOK === true) { 10 | this.dartPath = path; 11 | this.synImageConfig(); 12 | } 13 | else { 14 | console.log('\x1B[31m%s\x1B[0m', 'error:请检查工程目录是否存在images,pubspec.yaml\n'); 15 | } 16 | } 17 | static synImageConfig() { 18 | this.readdirImage(this.imagesDir).then(images => { 19 | this.imageNames = images; 20 | return this.writeImageForConfig(images, this.flutterPubspecName); 21 | }).then(conetnt => { 22 | return this.updateConfig(this.flutterPubspecName, conetnt); 23 | }).then(_ => { 24 | this.createModel(); 25 | }); 26 | } 27 | static updateConfig(config, content) { 28 | return new Promise(reslove => { 29 | fs_extra_1.writeFile(config, content, 'utf8', (error) => { 30 | if (error) { 31 | console.log('\x1B[31m%s\x1B[0m', 'error: ' + error + '\n'); 32 | } 33 | else { 34 | console.log('\x1B[36m%s\x1B[0m', 'succeed: images目录的图片已同步到pubspec.yaml\n'); 35 | reslove(); 36 | } 37 | }); 38 | }); 39 | } 40 | static createModel() { 41 | if (this.dartPath !== undefined) { 42 | const defaultPath = this.flutterPubspecName.replace('pubspec.yaml', '') + 'lib/util/image_path_config.dart'; 43 | this.dartPath = this.dartPath === 'd' ? defaultPath : this.dartPath; 44 | generate_1.Generate.start(this.dartPath, this.imageNames); 45 | } 46 | } 47 | static writeImageForConfig(files, configPath) { 48 | return new Promise(reslove => { 49 | const rl = readline_1.createInterface({ 50 | input: fs_extra_1.createReadStream(configPath), 51 | }); 52 | let canAdd = true; 53 | let tasking = false; 54 | const lines = []; 55 | rl.on('line', (line) => { 56 | if (this.lineIsAssets(line)) { 57 | canAdd = false; 58 | tasking = true; 59 | lines.push(line); 60 | files.forEach(file => { 61 | const newfile = ` - images/${file}`; 62 | lines.push(newfile); 63 | }); 64 | } 65 | if (tasking === true && line.indexOf('#') !== -1) { 66 | tasking = false; 67 | canAdd = true; 68 | } 69 | if (canAdd === true) { 70 | lines.push(line); 71 | } 72 | }); 73 | rl.on('close', () => { 74 | reslove(lines.join('\n')); 75 | }); 76 | }); 77 | } 78 | static readdirImage(dir) { 79 | return new Promise(reslove => { 80 | fs_extra_1.readdir(dir, (_, files) => { 81 | reslove(this.getImageFiles(files)); 82 | }); 83 | }); 84 | } 85 | static getImageFiles(files) { 86 | const images = []; 87 | files.forEach(file => { 88 | if (file.indexOf('.png') !== -1 || file.indexOf('.jpg') !== -1) { 89 | images.push(file); 90 | } 91 | }); 92 | return images; 93 | } 94 | static setConfigPath() { 95 | this.convenientDir('.'); 96 | const hasDir = fs_extra_1.pathExistsSync(this.imagesDir); 97 | const hasPub = fs_extra_1.pathExistsSync(this.flutterPubspecName); 98 | return hasDir && hasPub; 99 | } 100 | static convenientDir(path) { 101 | let imagesDirPath = null; 102 | let pubFilePath = null; 103 | if (fs_extra_1.existsSync(path) === true) { 104 | const files = fs_extra_1.readdirSync(path); 105 | for (const file of files) { 106 | const currentPath = path + '/' + file; 107 | if (currentPath.indexOf('pubspec.yaml') !== -1 && pubFilePath === null) { 108 | pubFilePath = currentPath; 109 | this.flutterPubspecName = pubFilePath; 110 | } 111 | if (currentPath.indexOf('images') !== -1 && fs_extra_1.statSync(currentPath).isDirectory() === true && imagesDirPath === null) { 112 | imagesDirPath = currentPath; 113 | this.imagesDir = imagesDirPath; 114 | } 115 | if (fs_extra_1.statSync(currentPath).isDirectory() === true && currentPath.indexOf('node_modules') === -1 116 | && currentPath.indexOf('git') === -1 117 | && currentPath.indexOf('.ios') === -1 118 | && currentPath.indexOf('.android') === -1) { 119 | this.convenientDir(currentPath); 120 | } 121 | } 122 | } 123 | } 124 | static lineIsAssets(line) { 125 | return (line.indexOf('assets:') !== -1 && line.indexOf('#') === -1); 126 | } 127 | } 128 | exports.Refimg = Refimg; 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@weexbox/cli", 3 | "version": "2.0.6", 4 | "description": "weexbox init cli", 5 | "bin": { 6 | "weexbox": "./bin/weexbox-cli.js" 7 | }, 8 | "scripts": { 9 | "build": "tsc" 10 | }, 11 | "author": "myeveryheart@qq.com", 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/aygtech/weexbox-cli.git" 16 | }, 17 | "keywords": [ 18 | "weexbox", 19 | "weex" 20 | ], 21 | "dependencies": { 22 | "commander": "^2.20.0", 23 | "download-git-repo": "^2.0.0", 24 | "fs-extra": "^8.1.0", 25 | "validate-npm-package-name": "^3.0.0", 26 | "chalk": "^2.4.2", 27 | "typescript": "^3.5.2", 28 | "@types/node": "^12.0.12", 29 | "@types/fs-extra": "^8.0.0", 30 | "tslint": "^5.18.0", 31 | "@weex-cli/utils": "^2.0.0-beta.2", 32 | "colors": "^1.3.3", 33 | "ora": "^3.4.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/create/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { existsSync, access, ensureDir, constants, copy } from 'fs-extra' 3 | import chalk from 'chalk' 4 | import ora from 'ora' 5 | import validateProjectName = require('validate-npm-package-name') 6 | import download = require('download-git-repo') 7 | 8 | export class Create { 9 | 10 | static createProject(projectName: string, options: any) { 11 | const result = validateProjectName(projectName) 12 | if (!result.validForNewPackages) { 13 | console.error(chalk.red(`无效的项目名称: '${projectName}'`)) 14 | const errors = result.errors 15 | if (errors) { 16 | errors.forEach(err => { 17 | console.error(chalk.red(err)) 18 | }) 19 | } 20 | process.exit(1) 21 | } 22 | const targetDir = resolve(process.cwd(), projectName) 23 | if (existsSync(targetDir)) { 24 | console.error(chalk.red(`目录 ${chalk.cyan(targetDir)} 已存在`)) 25 | process.exit(1) 26 | } 27 | let templatePath = 'aygtech/weexbox-template' 28 | if (options.flutter) { 29 | templatePath += '#flutter' 30 | } 31 | const spinner = ora(`正在从 https://github.com/${templatePath} 下载模板\n如果您的网络不好,可以手动下载`).start() 32 | download(templatePath, targetDir, (err) => { 33 | spinner.stop() 34 | console.log(err ? err : `${projectName} 创建成功`) 35 | }) 36 | } 37 | 38 | /** 39 | * 根据模板创建页面 40 | * @param pageName 页面名称,支持带多级路径 41 | * @param templatePath 模板文件目录,不传使用内置默认模板 42 | */ 43 | static async createPage(pageName: string, templatePath?: string) { 44 | try { 45 | await access('./src', constants.R_OK | constants.W_OK) 46 | 47 | const pagePath = resolve(process.cwd(), 'src', pageName) 48 | try { 49 | await access(pagePath) 50 | console.log(chalk.red(`页面: ${pageName}在 src 目录中已存在,请修改页面名称`)) 51 | } catch (e) { 52 | await ensureDir(pagePath) 53 | 54 | let templateDir = '' 55 | if (templatePath) { 56 | templateDir = resolve(process.cwd(), templatePath) 57 | } else { 58 | templateDir = resolve(__dirname, '../../template') 59 | } 60 | await copy(templateDir, pagePath, {recursive: true}) 61 | console.log(chalk.cyan('页面创建成功,路径:') + chalk.green(`${pagePath}`)) 62 | } 63 | } catch (e) { 64 | console.log(chalk.red(`页面创建过程出错:${e}`)) 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/doctor/android/android-sdk.ts: -------------------------------------------------------------------------------- 1 | // Android SDK layout: 2 | 3 | // $ANDROID_HOME/platform-tools/adb 4 | 5 | // $ANDROID_HOME/build-tools/19.1.0/aapt, dx, zipalign 6 | // $ANDROID_HOME/build-tools/22.0.1/aapt 7 | // $ANDROID_HOME/build-tools/23.0.2/aapt 8 | // $ANDROID_HOME/build-tools/24.0.0-preview/aapt 9 | // $ANDROID_HOME/build-tools/25.0.2/apksigner 10 | 11 | // $ANDROID_HOME/platforms/android-22/android.jar 12 | // $ANDROID_HOME/platforms/android-23/android.jar 13 | // $ANDROID_HOME/platforms/android-N/android.jar 14 | 15 | import * as path from 'path' 16 | import * as fs from 'fs' 17 | import { isLinux, isMacOS, isWindows, homedir } from '@weex-cli/utils/lib/platform/platform' 18 | import { which, canRunSync } from '../base/process' 19 | import { versionParse, VersionOption } from '@weex-cli/utils/lib/base/version' 20 | import { AndroidStudio } from './android-studio' 21 | 22 | export const kAndroidHome: String = 'ANDROID_HOME' 23 | const numberedAndroidPlatformRe: RegExp = new RegExp('^android-([0-9P]+)$') 24 | // const sdkVersionRe: RegExp = new RegExp('^ro.build.version.sdk=([0-9]+)$') 25 | const javaHomeEnvironmentVariable: String = 'JAVA_HOME' 26 | // const javaExecutable: String = 'java' 27 | 28 | // The minimum Android SDK version we support. 29 | // const minimumAndroidSdkVersion: number = 26 30 | export const mustAndroidSdkVersion: number = 26 31 | 32 | export class AndroidSdkVersion { 33 | constructor( 34 | public sdk: AndroidSdk, 35 | public sdkLevel: number, 36 | public platformName: string, 37 | public buildToolsVersion: VersionOption, 38 | ) { 39 | this.sdk = sdk 40 | this.sdkLevel = sdkLevel 41 | this.platformName = platformName 42 | this.buildToolsVersion = buildToolsVersion 43 | } 44 | 45 | get buildToolsVersionName(): string { 46 | if (!this.buildToolsVersion) { 47 | return '' 48 | } 49 | return `${this.buildToolsVersion.major}.${this.buildToolsVersion.minor}.${this.buildToolsVersion.patch}` 50 | } 51 | 52 | get androidJarPath() { 53 | return this.getPlatformsPath('android.jar') 54 | } 55 | 56 | get aaptPath() { 57 | return this.getBuildToolsPath('aapt') 58 | } 59 | 60 | public getPlatformsPath(itemName: string) { 61 | return path.join(this.sdk.directory, 'platforms', this.platformName, itemName) 62 | } 63 | 64 | public getBuildToolsPath(binaryName: string) { 65 | if (!this.buildToolsVersionName) { 66 | return '' 67 | } 68 | return path.join(this.sdk.directory, 'build-tools', this.buildToolsVersionName, binaryName) 69 | } 70 | 71 | public validateSdkWellFormed(): string[] { 72 | if (this.exists(this.androidJarPath) !== null) { 73 | return [this.exists(this.androidJarPath)] 74 | } 75 | if (this.canRun(this.aaptPath, ['v']) !== null) { 76 | return [this.canRun(this.aaptPath, ['v'])] 77 | } 78 | 79 | return [] 80 | } 81 | 82 | public exists(path: string) { 83 | if (!fs.existsSync(path)) { 84 | return `Android SDK file not found: ${path}.` 85 | } 86 | return null 87 | } 88 | 89 | public canRun(path: string, args: string[] = []) { 90 | if (!canRunSync(path, args)) { 91 | return `Android SDK file not found: ${path}.` 92 | } 93 | return null 94 | } 95 | } 96 | 97 | export class AndroidSdk { 98 | public directory: string 99 | public sdkVersions: AndroidSdkVersion[] = [] 100 | public latestVersion: AndroidSdkVersion 101 | public androidStudio: AndroidStudio = new AndroidStudio() 102 | public isMustAndroidSdkVersion: boolean = false 103 | 104 | constructor() { 105 | this.init() 106 | } 107 | 108 | get adbPath() { 109 | return this.getPlatformToolsPath('adb') 110 | } 111 | 112 | get sdkManagerPath() { 113 | return path.join(this.directory, 'tools', 'bin', 'sdkmanager') 114 | } 115 | 116 | public findJavaBinary() { 117 | if (this.androidStudio.javaPath) { 118 | return path.join(this.androidStudio.javaPath, 'bin', 'java') 119 | } 120 | const javaHomeEnv = process.env[`${javaHomeEnvironmentVariable}`] 121 | if (javaHomeEnv) { 122 | // Trust JAVA_HOME. 123 | return path.join(javaHomeEnv, 'bin', 'java') 124 | } 125 | } 126 | 127 | public getPlatformToolsPath(binaryName: string) { 128 | return path.join(this.directory, 'platform-tools', binaryName) 129 | } 130 | 131 | public validateSdkWellFormed(): string[] { 132 | if (!canRunSync(this.adbPath, ['version'])) { 133 | return [`Android SDK file not found: ${this.adbPath}.`] 134 | } 135 | if (!this.sdkVersions.length || !this.latestVersion) { 136 | return [`Android SDK is missing command line tools; download from https://goo.gl/XxQghQ`] 137 | } 138 | 139 | return this.latestVersion.validateSdkWellFormed() 140 | } 141 | 142 | public locateAndroidSdk() { 143 | this.directory = this.findAndroidHomeDir() 144 | } 145 | 146 | public findAndroidHomeDir() { 147 | let androidHomeDir: string 148 | if (process.env[`${kAndroidHome}`]) { 149 | androidHomeDir = process.env[`${kAndroidHome}`] 150 | } else if (homedir) { 151 | if (isLinux) { 152 | androidHomeDir = path.join(homedir, 'Android', 'Sdk') 153 | } else if (isMacOS) { 154 | androidHomeDir = path.join(homedir, 'Library', 'Android', 'sdk') 155 | } else if (isWindows) { 156 | androidHomeDir = path.join(homedir, 'AppData', 'Local', 'Android', 'sdk') 157 | } 158 | } 159 | 160 | if (androidHomeDir) { 161 | if (this.validSdkDirectory(androidHomeDir)) { 162 | return androidHomeDir 163 | } 164 | if (this.validSdkDirectory(path.join(androidHomeDir, 'sdk'))) { 165 | return path.join(androidHomeDir, 'sdk') 166 | } 167 | } 168 | 169 | const aaptBins = which('aapt') 170 | 171 | for (let aaptBin in aaptBins) { 172 | const dir = path.resolve(aaptBin, '../../') 173 | if (this.validSdkDirectory(dir)) { 174 | return dir 175 | } 176 | } 177 | 178 | const adbBins = which('adb') 179 | for (let adbBin in adbBins) { 180 | const dir = path.resolve(adbBin, '../../') 181 | if (this.validSdkDirectory(dir)) { 182 | return dir 183 | } 184 | } 185 | return null 186 | } 187 | 188 | public validSdkDirectory(dir) { 189 | const dirPath = path.join(dir, 'platform-tools') 190 | if (fs.existsSync(dirPath)) { 191 | return fs.statSync(dirPath).isDirectory() 192 | } 193 | return false 194 | } 195 | 196 | public init() { 197 | this.locateAndroidSdk() 198 | if (!this.directory) { 199 | return 200 | } 201 | let platforms: string[] = [] // android-23 android-25 android-26 android-27... 202 | const platformsDir: string = path.join(this.directory, 'platforms') 203 | 204 | let buildTools: string[] = [] // 23.0.1 25.0.3 26.0.0 26.0.2 27.0.3... 205 | const buildToolsDir: string = path.join(this.directory, 'build-tools') 206 | 207 | if (fs.existsSync(platformsDir)) { 208 | platforms = fs.readdirSync(platformsDir) 209 | } 210 | 211 | if (fs.existsSync(buildToolsDir)) { 212 | buildTools = fs.readdirSync(buildToolsDir) 213 | } 214 | 215 | platforms.map(platformName => { 216 | let matchVersion: any = platformName.match(numberedAndroidPlatformRe) 217 | if (Array.isArray(matchVersion) && matchVersion.length > 1) { 218 | matchVersion = matchVersion[1] 219 | } else { 220 | return null 221 | } 222 | if (matchVersion === 'P') { 223 | matchVersion = '28' 224 | } 225 | const platformVersion = Number(matchVersion) 226 | if (mustAndroidSdkVersion === platformVersion) { 227 | this.isMustAndroidSdkVersion = true 228 | } 229 | let buildToolsVersion 230 | buildTools.forEach(version => { 231 | const versionOption = versionParse(version) 232 | if (versionOption && versionOption.major === platformVersion) { 233 | buildToolsVersion = versionParse(version) 234 | } 235 | }) 236 | 237 | if (!buildTools) { 238 | return null 239 | } 240 | this.sdkVersions.push(new AndroidSdkVersion(this, platformVersion, platformName, buildToolsVersion)) 241 | }) 242 | if (this.sdkVersions.length) { 243 | this.latestVersion = this.sdkVersions[this.sdkVersions.length - 1] 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/doctor/android/android-studio.ts: -------------------------------------------------------------------------------- 1 | import { isMacOS, isLinux, homedir } from '@weex-cli/utils/lib/platform/platform' 2 | import * as path from 'path' 3 | import * as fs from 'fs' 4 | import { IOSWorkflow } from '../ios/ios-workflow' 5 | import { kCFBundleShortVersionStringKey } from '@weex-cli/utils/lib/ios/plist-utils' 6 | import { versionParse, VersionOption, compareVersion } from '@weex-cli/utils/lib/base/version' 7 | import { canRunSync, runSync } from '../base/process' 8 | 9 | import * as debug from 'debug' 10 | const DEBUG = debug('plugin:doctor:android-studio') 11 | 12 | // Android Studio layout: 13 | 14 | // Linux/Windows: 15 | // $HOME/.AndroidStudioX.Y/system/.home 16 | 17 | // macOS: 18 | // /Applications/Android Studio.app/Contents/ 19 | // $HOME/Applications/Android Studio.app/Contents/ 20 | 21 | const _dotHomeStudioVersionMatcher = new RegExp('^.AndroidStudio([^d]*)([d.]+)') 22 | 23 | interface ValidOption { 24 | configured?: string 25 | version?: VersionOption 26 | } 27 | 28 | export class AndroidStudioValid { 29 | public isValid: boolean = true 30 | public validationMessages: string[] = [] 31 | public configured: string 32 | public javaPath: string 33 | public version: VersionOption 34 | 35 | constructor(public directory: string, public option?: ValidOption) { 36 | this.directory = directory 37 | this.configured = this.option.configured 38 | this.version = this.option.version 39 | this.init() 40 | } 41 | 42 | public init() { 43 | if (this.configured) { 44 | this.validationMessages.push(`android-studio-dir = ${this.configured}`) 45 | } 46 | 47 | if (!fs.existsSync(this.directory)) { 48 | this.validationMessages.push(`Android Studio not found at ${this.directory}`) 49 | return 50 | } 51 | 52 | let javaPath = isMacOS 53 | ? path.join(this.directory, 'jre', 'jdk', 'Contents', 'Home') 54 | : path.join(this.directory, 'jre') 55 | const javaExecutable = path.join(javaPath, 'bin', 'java') 56 | if (!canRunSync(javaExecutable, ['-version'])) { 57 | this.validationMessages.push(`Unable to find bundled Java version.`) 58 | } else { 59 | const result = runSync(javaExecutable, ['-version']) 60 | if (result && result.status === 0) { 61 | const versionLines = result.stderr.toString().split('\n') 62 | const javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0] 63 | this.validationMessages.push(`Java version ${javaVersion}`) 64 | this.isValid = true 65 | this.javaPath = javaPath 66 | } else { 67 | this.validationMessages.push('Unable to determine bundled Java version.') 68 | } 69 | } 70 | } 71 | } 72 | 73 | export class AndroidStudio { 74 | public javaPath: string 75 | public iosWorkflow = new IOSWorkflow() 76 | 77 | constructor() { 78 | this.latestValid() 79 | } 80 | 81 | // Locates the newest, valid version of Android Studio. 82 | public latestValid() { 83 | const studios = this.allInstalled() 84 | if (studios.length) { 85 | this.javaPath = studios[studios.length - 1].javaPath 86 | } 87 | // for (let i = 0; i < studios.length; i++) { 88 | 89 | // } 90 | } 91 | 92 | public allInstalled(): AndroidStudioValid[] { 93 | return isMacOS ? this.allMacOS() : this.allLinuxOrWindows() 94 | } 95 | 96 | public allMacOS(): AndroidStudioValid[] { 97 | let directories = [] 98 | this.checkForStudio('/Applications').forEach(name => { 99 | directories.push(`/Applications/${name}`) 100 | }) 101 | this.checkForStudio(path.join(homedir, 'Applications')).forEach(name => { 102 | directories.push(path.join(homedir, 'Applications', name)) 103 | }) 104 | return directories.map(path => this.fromMacOSBundle(path)) 105 | } 106 | 107 | public checkForStudio(path: string): string[] { 108 | if (!fs.existsSync(path)) { 109 | return [] 110 | } 111 | const candidatePaths = [] 112 | 113 | try { 114 | const directories = fs.readdirSync(path) 115 | for (let name of directories) { 116 | // An exact match, or something like 'Android Studio 3.0 Preview.app'. 117 | if (name.startsWith('Android Studio') && name.endsWith('.app')) { 118 | candidatePaths.push(name) 119 | } 120 | } 121 | } catch (e) { 122 | console.error(e) 123 | } 124 | return candidatePaths 125 | } 126 | 127 | public fromMacOSBundle(bundlePath: string): AndroidStudioValid { 128 | const studioPath = path.join(bundlePath, 'Contents') 129 | const plistFile: any = path.join(studioPath, 'Info.plist') 130 | const versionString = this.iosWorkflow.getPlistValueFromFile(plistFile, kCFBundleShortVersionStringKey) 131 | let version: VersionOption 132 | if (versionString) { 133 | version = versionParse(versionString) 134 | } 135 | return new AndroidStudioValid(studioPath, { version: version }) 136 | } 137 | 138 | public fromHomeDot(homeDotDir) { 139 | const versionMatch = path.basename(homeDotDir).match(_dotHomeStudioVersionMatcher)[1] 140 | if (versionMatch.length !== 3) { 141 | return null 142 | } 143 | const version: VersionOption = versionParse(versionMatch[2]) 144 | if (!version) { 145 | return null 146 | } 147 | let installPath 148 | if (fs.existsSync(path.join(homeDotDir, 'system', '.home'))) { 149 | installPath = path.join(homeDotDir, 'system', '.home') 150 | } 151 | if (installPath) { 152 | return new AndroidStudioValid(installPath, { version: version }) 153 | } 154 | return null 155 | } 156 | 157 | public allLinuxOrWindows() { 158 | let studios: AndroidStudioValid[] = [] 159 | 160 | function hasStudioAt(path: string, newerThan?: VersionOption): boolean { 161 | return studios.every(studio => { 162 | if (studio.directory !== path) { 163 | return false 164 | } 165 | if (newerThan) { 166 | return compareVersion(studio.version, newerThan) 167 | } 168 | return true 169 | }) 170 | } 171 | 172 | // Read all $HOME/.AndroidStudio*/system/.home files. There may be several 173 | // pointing to the same installation, so we grab only the latest one. 174 | if (fs.existsSync(homedir)) { 175 | for (let entity of fs.readdirSync(homedir)) { 176 | const homeDotDir: any = path.join(homedir, entity) 177 | try { 178 | let homeDotDirType = fs.statSync(homeDotDir) 179 | if (homeDotDirType && homeDotDirType.isDirectory() && entity.startsWith('.AndroidStudio')) { 180 | const studio = this.fromHomeDot(homeDotDir) 181 | if (studio && !hasStudioAt(studio.directory, studio.version)) { 182 | studios = studios.filter(other => other.directory !== studio.directory) 183 | studios.push(studio) 184 | } 185 | } 186 | } catch (error) { 187 | DEBUG(error, homeDotDir) 188 | } 189 | } 190 | } 191 | 192 | function checkWellKnownPath(path: string) { 193 | if (fs.existsSync(path) && !hasStudioAt(path)) { 194 | studios.push(new AndroidStudioValid(path)) 195 | } 196 | } 197 | 198 | if (isLinux) { 199 | // Add /opt/android-studio and $HOME/android-studio, if they exist. 200 | checkWellKnownPath('/opt/android-studio') 201 | checkWellKnownPath(`${homedir}/android-studio`) 202 | } 203 | 204 | return studios 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/doctor/android/android-workflow.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, ValidationType, ValidationMessage, ValidationResult, DoctorValidator } from '../doctor' 2 | import { kAndroidHome, AndroidSdk, mustAndroidSdkVersion } from './android-sdk' 3 | import { canRunSync, runSync } from '../base/process' 4 | 5 | // const licenseAccepted = new RegExp('All SDK package licenses accepted.') 6 | const jdkDownload: String = 'https://www.oracle.com/technetwork/java/javase/downloads/' 7 | 8 | // enum LicensesAccepted { 9 | // none, 10 | // some, 11 | // all, 12 | // unknown, 13 | // } 14 | 15 | export class AndroidWorkflow implements Workflow { 16 | get appliesToHostPlatform(): boolean { 17 | return true 18 | } 19 | } 20 | 21 | export class AndroidValidator implements DoctorValidator { 22 | public title: string 23 | public messages: ValidationMessage[] = [] 24 | public androidSdk: AndroidSdk = new AndroidSdk() 25 | constructor() { 26 | this.title = 'Android toolchain - develop for Android devices' 27 | } 28 | 29 | public validate() { 30 | // android-sdk 31 | if (!this.androidSdk.directory) { 32 | // No Android SDK found. 33 | if (process.env[`${kAndroidHome}`]) { 34 | const androidHomeDir: string = process.env[`${kAndroidHome}`] 35 | this.messages.push( 36 | new ValidationMessage( 37 | `${kAndroidHome} = ${androidHomeDir} 38 | but Android SDK not found at this location.`, 39 | true /* isError */, 40 | ), 41 | ) 42 | } else { 43 | this.messages.push( 44 | new ValidationMessage( 45 | `Unable to locate Android SDK. 46 | Install Android Studio from: https://developer.android.com/studio/index.html 47 | On first launch it will assist you in installing the Android SDK components. 48 | If Android SDK has been installed to a custom location, set ${kAndroidHome} to that location.`, 49 | true /* isError */, 50 | ), 51 | ) 52 | } 53 | return new ValidationResult(ValidationType.missing, this.messages) 54 | } 55 | if (!this.androidSdk.isMustAndroidSdkVersion) { 56 | this.messages.push( 57 | new ValidationMessage( 58 | `There is no required version SDK plaform android-${mustAndroidSdkVersion}.`, 59 | false /* isError */, 60 | true /* isWaring */, 61 | ), 62 | ) 63 | } 64 | 65 | this.messages.push(new ValidationMessage(`Android SDK at ${this.androidSdk.directory}`)) 66 | 67 | let sdkVersionText: string 68 | if (this.androidSdk.latestVersion) { 69 | sdkVersionText = `Android SDK ${this.androidSdk.latestVersion.buildToolsVersionName}` 70 | this.messages.push( 71 | new ValidationMessage( 72 | `Platform ${this.androidSdk.latestVersion.platformName}, build-tools ${ 73 | this.androidSdk.latestVersion.buildToolsVersionName 74 | }`, 75 | ), 76 | ) 77 | } 78 | 79 | if (process.env[`${kAndroidHome}`]) { 80 | const androidHomeDir: string = process.env[`${kAndroidHome}`] 81 | this.messages.push(new ValidationMessage(`${kAndroidHome} = ${androidHomeDir}\n`)) 82 | } 83 | 84 | const validationResult = this.androidSdk.validateSdkWellFormed() 85 | 86 | if (validationResult.length) { 87 | // Android SDK is not functional. 88 | // validationResult.forEach(message => { 89 | // this.messages.push(new ValidationMessage(message, true /* isError */)) 90 | // }) 91 | this.messages.push(new ValidationMessage(`Try re-installing or updating your Android SDK.`)) 92 | return new ValidationResult(ValidationType.partial, this.messages, sdkVersionText) 93 | } 94 | 95 | // Now check for the JDK. 96 | const javaBinary = this.androidSdk.findJavaBinary() 97 | if (!javaBinary) { 98 | this.messages.push( 99 | new ValidationMessage( 100 | `No Java Development Kit (JDK) found; You must have the environment 101 | variable JAVA_HOME set and the java binary in your PATH. 102 | You can download the JDK from ${jdkDownload}.`, 103 | true /* isError */, 104 | ), 105 | ) 106 | } 107 | 108 | // Check JDK version. 109 | if (!this.checkJavaVersion(javaBinary)) { 110 | return new ValidationResult(ValidationType.partial, this.messages, sdkVersionText) 111 | } 112 | 113 | // Check for licenses. 114 | 115 | // Success. 116 | return new ValidationResult(ValidationType.installed, this.messages, sdkVersionText) 117 | } 118 | 119 | public checkJavaVersion(javaBinary) { 120 | if (!canRunSync(javaBinary, ['-version'])) { 121 | this.messages.push( 122 | new ValidationMessage(`Cannot execute ${javaBinary} to determine the version.`, true /* isError */), 123 | ) 124 | return false 125 | } 126 | let javaVersion: string 127 | const result = runSync(javaBinary, ['-version']) 128 | if (result.status === 0) { 129 | const versionLines = result.stderr.toString().split('\n') 130 | javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0] 131 | } 132 | if (!javaVersion) { 133 | this.messages.push(new ValidationMessage(`Could not determine java version.`, true /* isError */)) 134 | return false 135 | } 136 | this.messages.push(new ValidationMessage(`Java version ${javaVersion}.`)) 137 | return true 138 | } 139 | 140 | public licensesAccepted() { 141 | // let status: LicensesAccepted 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/doctor/base/process.ts: -------------------------------------------------------------------------------- 1 | import { isWindows } from '@weex-cli/utils/lib/platform/platform' 2 | import { spawnSync, execSync } from 'child_process' 3 | import * as fs from 'fs' 4 | 5 | export function runAsync(command: string, args: string[] = []): Promise { 6 | return new Promise((resolve, reject) => { 7 | let result 8 | try { 9 | result = spawnSync(command, args) 10 | resolve(result) 11 | } catch (e) { 12 | reject(`Exit code ${result.status} from: ${command}:\n${result}`) 13 | } 14 | }) 15 | } 16 | 17 | function cleanInput(s) { 18 | if (/[^A-Za-z0-9_\/:=-]/.test(s)) { 19 | s = "'" + s.replace(/'/g, "'\\''") + "'" 20 | s = s 21 | .replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning 22 | .replace(/\\'''/g, "\\'") // remove non-escaped single-quote if there are enclosed between 2 escaped 23 | } 24 | return s 25 | } 26 | 27 | function commandExistsWindowsSync(commandName, cleanedCommandName, callback?) { 28 | try { 29 | const stdout = execSync('where ' + cleanedCommandName, { stdio: [] }) 30 | return !!stdout 31 | } catch (error) { 32 | return false 33 | } 34 | } 35 | 36 | function fileNotExistsSync(commandName) { 37 | try { 38 | fs.accessSync(commandName, fs.constants.F_OK) 39 | return false 40 | } catch (e) { 41 | return true 42 | } 43 | } 44 | 45 | function localExecutableSync(commandName) { 46 | try { 47 | fs.accessSync(commandName, fs.constants.F_OK | fs.constants.X_OK) 48 | return false 49 | } catch (e) { 50 | return true 51 | } 52 | } 53 | 54 | function commandExistsUnixSync(commandName, cleanedCommandName, callback?): boolean { 55 | if (fileNotExistsSync(commandName)) { 56 | try { 57 | const stdout = execSync( 58 | 'command -v ' + cleanedCommandName + ' 2>/dev/null' + ' && { echo >&1 ' + cleanedCommandName + '; exit 0; }', 59 | ) 60 | return !!stdout 61 | } catch (error) { 62 | return false 63 | } 64 | } 65 | return localExecutableSync(commandName) 66 | } 67 | 68 | export function commandExistsSync(commandName): boolean { 69 | const cleanedCommandName = cleanInput(commandName) 70 | if (isWindows) { 71 | return commandExistsWindowsSync(commandName, cleanedCommandName) 72 | } else { 73 | return commandExistsUnixSync(commandName, cleanedCommandName) 74 | } 75 | } 76 | 77 | export function which(execName, args = []): string[] { 78 | const spawnArgs = [execName, ...args] 79 | const result = spawnSync('which', spawnArgs) 80 | if (result.status !== 0) { 81 | return [] 82 | } 83 | const lines = result.stdout 84 | .toString() 85 | .trim() 86 | .split('\n') 87 | return lines 88 | } 89 | 90 | export function runSync(commandName, args: string[] = []) { 91 | let result 92 | try { 93 | result = spawnSync(commandName, args) 94 | return result 95 | } catch (e) { 96 | return null 97 | } 98 | } 99 | 100 | export function canRunSync(commandName, args: string[] = []): boolean { 101 | let result 102 | try { 103 | result = spawnSync(commandName, args) 104 | if (result.status === 0) { 105 | return true 106 | } 107 | return false 108 | } catch (e) { 109 | return false 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/doctor/doctor.ts: -------------------------------------------------------------------------------- 1 | import { AndroidWorkflow, AndroidValidator } from './android/android-workflow' 2 | import { IOSWorkflow, IOSValidator } from './ios/ios-workflow' 3 | import { isWindows } from '@weex-cli/utils/lib/platform/platform' 4 | import * as colors from 'colors' 5 | 6 | export const enum ValidationType { 7 | missing, 8 | partial, 9 | installed, 10 | } 11 | 12 | class ValidatorTask { 13 | constructor(public validator: DoctorValidator, public result: ValidationResult) { 14 | this.validator = validator 15 | this.result = result 16 | } 17 | } 18 | 19 | export class ValidationResult { 20 | /// [ValidationResult.type] should only equal [ValidationResult.installed] 21 | /// if no [messages] are hints or errors. 22 | constructor(public type: ValidationType, public messages: ValidationMessage[], public statusInfo?: string) { 23 | this.type = type 24 | this.messages = messages 25 | } 26 | 27 | get leadingBox(): String { 28 | switch (this.type) { 29 | case ValidationType.missing: 30 | return '[✗]' 31 | case ValidationType.installed: 32 | return '[✓]' 33 | case ValidationType.partial: 34 | return '[!]' 35 | } 36 | return null 37 | } 38 | } 39 | 40 | export class Doctor { 41 | public validators: DoctorValidator[] = [] 42 | public iosWorkflow = new IOSWorkflow() 43 | public androidWorkflow = new AndroidWorkflow() 44 | 45 | constructor() { 46 | this.getValidators() 47 | } 48 | 49 | public getValidators() { 50 | if (this.androidWorkflow.appliesToHostPlatform) { 51 | this.validators.push(new AndroidValidator()) 52 | } 53 | if (!isWindows && this.iosWorkflow.appliesToHostPlatform) { 54 | this.validators.push(new IOSValidator()) 55 | } 56 | } 57 | 58 | public startValidatorTasks() { 59 | const tasks = [] 60 | for (let validator of this.validators) { 61 | tasks.push(new ValidatorTask(validator, validator.validate())) 62 | } 63 | return tasks 64 | } 65 | 66 | /** 67 | * diagnose 68 | */ 69 | public diagnose(): string { 70 | const taskList: ValidatorTask[] = this.startValidatorTasks() 71 | let messageResult: string = '' 72 | 73 | for (let validatorTask of taskList) { 74 | const validator: DoctorValidator = validatorTask.validator 75 | const results: ValidationResult[] = [] 76 | let result: ValidationResult 77 | let color: any 78 | results.push(validatorTask.result) 79 | result = this.mergeValidationResults(results) 80 | color = 81 | result.type === ValidationType.missing 82 | ? colors.red 83 | : result.type === ValidationType.installed 84 | ? colors.green 85 | : colors.yellow 86 | // console.log(this.androidSdk.latestVersion.AndroidSdkVersion.sdkLevel) 87 | messageResult += `${color(`\n${result.leadingBox} ${validator.title}\n`)}` 88 | // console.log(`${result.leadingBox} ${validator.title} is`) 89 | for (let message of result.messages) { 90 | const text = message.message.replace('\n', '\n ') 91 | if (message.isError) { 92 | messageResult += `${colors.red(` ✗ ${text}`)}\n` 93 | // console.log(` ✗ ${text}`); 94 | } else if (message.isWaring) { 95 | messageResult += `${colors.yellow(` ! ${text}`)}\n` 96 | // console.log(` ! ${text}`); 97 | } else { 98 | messageResult += ` • ${text}\n` 99 | // console.log(` • ${text}`); 100 | } 101 | } 102 | } 103 | 104 | return messageResult 105 | } 106 | 107 | public mergeValidationResults(results: ValidationResult[]): ValidationResult { 108 | let mergedType: ValidationType = results[0].type 109 | const mergedMessages: ValidationMessage[] = [] 110 | 111 | for (let result of results) { 112 | switch (result.type) { 113 | case ValidationType.installed: 114 | if (mergedType === ValidationType.missing) { 115 | mergedType = ValidationType.partial 116 | } 117 | break 118 | case ValidationType.partial: 119 | mergedType = ValidationType.partial 120 | break 121 | case ValidationType.missing: 122 | if (mergedType === ValidationType.installed) { 123 | mergedType = ValidationType.partial 124 | } 125 | break 126 | default: 127 | break 128 | } 129 | mergedMessages.push(...result.messages) 130 | } 131 | return new ValidationResult(mergedType, mergedMessages, results[0].statusInfo) 132 | } 133 | } 134 | 135 | // A series of tools and required install steps for a target platform (iOS or Android). 136 | export abstract class Workflow { 137 | // Whether the workflow applies to this platform (as in, should we ever try and use it). 138 | abstract get appliesToHostPlatform(): boolean 139 | 140 | // Are we functional enough to list devices? 141 | // abstract canListDevices(): boolean; 142 | 143 | // Could this thing launch *something*? It may still have minor issues. 144 | // abstract canLaunchDevices(): boolean; 145 | 146 | // Are we functional enough to list emulators? 147 | // abstract canListEmulators(): boolean; 148 | } 149 | 150 | export abstract class DoctorValidator { 151 | public title: string 152 | abstract validate() 153 | } 154 | 155 | export class ValidationMessage { 156 | constructor(public message: string, public isError = false, public isWaring = false) { 157 | this.message = message 158 | this.isError = isError 159 | this.isWaring = isWaring 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/doctor/index.ts: -------------------------------------------------------------------------------- 1 | export { Doctor } from './doctor' 2 | -------------------------------------------------------------------------------- /src/doctor/ios/ios-workflow.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, ValidationType, ValidationMessage, ValidationResult, DoctorValidator } from '../doctor' 2 | import { Xcode, XcodeRequiredVersionMajor, XcodeRequiredVersionMinor } from '@weex-cli/utils/lib/ios/mac' 3 | import { 4 | CocoaPods, 5 | CocoaPodsStatus, 6 | noCocoaPodsConsequence, 7 | cocoaPodsInstallInstructions, 8 | cocoaPodsUpgradeInstructions, 9 | } from '@weex-cli/utils/lib/ios/cocoapods' 10 | import { which } from '@weex-cli/utils/lib/process/process' 11 | import { spawnSync } from 'child_process' 12 | import { versionParse, compareVersion } from '@weex-cli/utils/lib/base/version' 13 | import * as plist from '@weex-cli/utils/lib/ios/plist-utils' 14 | 15 | // import IosEnv from '@weex-cli/utils/lib/ios/ios-env' 16 | 17 | export class IOSWorkflow implements Workflow { 18 | get appliesToHostPlatform(): boolean { 19 | return process.platform === 'darwin' 20 | } 21 | 22 | public getPlistValueFromFile(path: string, key: string): string { 23 | return plist.getValueFromFile(path, key) 24 | } 25 | } 26 | 27 | export class IOSValidator implements DoctorValidator { 28 | public messages: ValidationMessage[] = [] 29 | public xcodeStatus = ValidationType.missing 30 | public brewStatus = ValidationType.missing 31 | public xcodeVersionInfo: string 32 | public title: string 33 | public cocoaPods: CocoaPods = new CocoaPods() 34 | public xcode: Xcode = new Xcode() 35 | 36 | // private iosEnv: IosEnv = new IosEnv() 37 | constructor() { 38 | this.title = 'iOS toolchain - develop for iOS devices' 39 | } 40 | 41 | get hasHomebrew(): boolean { 42 | return !!which('brew').length 43 | } 44 | 45 | get hasIDeviceInstaller(): boolean { 46 | try { 47 | return spawnSync('ideviceinstaller', ['-h']).status === 0 48 | } catch (e) { 49 | return false 50 | } 51 | } 52 | 53 | get hasIosDeploy(): boolean { 54 | try { 55 | return spawnSync('ios-deploy', ['--version']).status === 0 56 | } catch (e) { 57 | return false 58 | } 59 | } 60 | 61 | get iosDeployVersionText(): string { 62 | try { 63 | return spawnSync('ios-deploy', ['--version']) 64 | .stdout.toString() 65 | .replace('\n', '') 66 | } catch (e) { 67 | return '' 68 | } 69 | } 70 | 71 | get iosDeployMinimumVersion() { 72 | return '1.9.2' 73 | } 74 | 75 | get iosDeployIsInstalledAndMeetsVersionCheck(): boolean { 76 | if (!this.hasIosDeploy) { 77 | return false 78 | } 79 | 80 | const version = versionParse(this.iosDeployVersionText) 81 | return compareVersion(version, versionParse(this.iosDeployMinimumVersion)) 82 | } 83 | 84 | public validate() { 85 | if (this.xcode.isInstalled) { 86 | this.xcodeStatus = ValidationType.installed 87 | 88 | this.messages.push(new ValidationMessage(`Xcode at ${this.xcode.xcodeSelectPath}`)) 89 | this.xcodeVersionInfo = this.xcode.versionText 90 | if (this.xcodeVersionInfo && this.xcodeVersionInfo.includes(',')) { 91 | this.xcodeVersionInfo = this.xcodeVersionInfo.substring(0, this.xcodeVersionInfo.indexOf(',')) 92 | this.messages.push(new ValidationMessage(this.xcodeVersionInfo)) 93 | } 94 | 95 | /** 96 | * installed and check xcode version 97 | */ 98 | if (!this.xcode.isInstalledAndMeetsVersionCheck) { 99 | this.xcodeStatus = ValidationType.partial 100 | this.messages.push( 101 | new ValidationMessage( 102 | `Weex requires a minimum Xcode version of ${XcodeRequiredVersionMajor}.${XcodeRequiredVersionMinor}.0.\n 103 | Download the latest version or update via the Mac App Store.`, 104 | true /* isError */, 105 | ), 106 | ) 107 | } 108 | 109 | /** 110 | * get admin 111 | */ 112 | if (!this.xcode.eulaSigned) { 113 | this.xcodeStatus = ValidationType.partial 114 | this.messages.push( 115 | new ValidationMessage( 116 | "Xcode end user license agreement not signed; open Xcode or run the command 'sudo xcodebuild -license'.", 117 | true /* isError */, 118 | ), 119 | ) 120 | } 121 | 122 | if (!this.xcode.isSimctlInstalled) { 123 | this.xcodeStatus = ValidationType.partial 124 | this.messages.push( 125 | new ValidationMessage( 126 | `Xcode requires additional components to be installed in order to run.\n' 127 | Launch Xcode and install additional required components when prompted.`, 128 | true /* isError */, 129 | ), 130 | ) 131 | } 132 | } else { 133 | this.xcodeStatus = ValidationType.missing 134 | if (!this.xcode.xcodeSelectPath) { 135 | this.messages.push( 136 | new ValidationMessage( 137 | `Xcode not installed; this is necessary for iOS development.\n 138 | Download at https://developer.apple.com/xcode/download/.`, 139 | true /* isError */, 140 | ), 141 | ) 142 | } else { 143 | this.messages.push( 144 | new ValidationMessage( 145 | `Xcode installation is incomplete; a full installation is necessary for iOS development.\n 146 | Download at: https://developer.apple.com/xcode/download/\n 147 | Or install Xcode via the App Store.\n 148 | Once installed, run:\n 149 | sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`, 150 | true /* isError */, 151 | ), 152 | ) 153 | } 154 | } 155 | 156 | // brew installed 157 | if (this.hasHomebrew) { 158 | this.brewStatus = ValidationType.installed 159 | 160 | // if (!iMobileDevice.isInstalled) { 161 | // this.brewStatus = ValidationType.partial 162 | // this.messages.push( 163 | // new ValidationMessage( 164 | // `libimobiledevice and ideviceinstaller are not installed. To install, run:\n 165 | // brew install --HEAD libimobiledevice\n 166 | // brew install ideviceinstaller`, 167 | // true /* isError */, 168 | // ), 169 | // ) 170 | // } else if (!iMobileDevice.isWorking) { 171 | // this.brewStatus = ValidationType.partial 172 | // this.messages.push( 173 | // new ValidationMessage( 174 | // `Verify that all connected devices have been paired with this computer in Xcode.\n 175 | // If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.\n 176 | // To update, run:\n 177 | // brew uninstall --ignore-dependencies libimobiledevice\n 178 | // brew install --HEAD libimobiledevice\n 179 | // brew install ideviceinstaller`, 180 | // true /* isError */, 181 | // ), 182 | // ) 183 | // } else if (!this.hasIDeviceInstaller) { 184 | // this.brewStatus = ValidationType.partial 185 | // this.messages.push( 186 | // new ValidationMessage( 187 | // `ideviceinstaller is not installed; this is used to discover connected iOS devices.\n 188 | // To install, run:\n 189 | // brew install --HEAD libimobiledevice\n 190 | // brew install ideviceinstaller`, 191 | // true /* isError */, 192 | // ), 193 | // ) 194 | // } 195 | 196 | // if (this.hasIosDeploy) { 197 | // this.messages.push(new ValidationMessage(`ios-deploy ${this.iosDeployVersionText}`)) 198 | // } 199 | 200 | // if (!this.iosDeployIsInstalledAndMeetsVersionCheck) { 201 | // this.brewStatus = ValidationType.partial 202 | // if (this.hasIosDeploy) { 203 | // this.messages.push( 204 | // new ValidationMessage( 205 | // `ios-deploy out of date (${this.iosDeployMinimumVersion} is required). To upgrade:\n 206 | // brew upgrade ios-deploy`, 207 | // true /* isError */, 208 | // ), 209 | // ); 210 | // } else { 211 | // this.messages.push( 212 | // new ValidationMessage( 213 | // `ios-deploy not installed. To install:\n 214 | // brew install ios-deploy`, 215 | // true /* isError */, 216 | // ), 217 | // ) 218 | // } 219 | 220 | // } 221 | 222 | const cocoaPodsStatus = this.cocoaPods.evaluateCocoaPodsInstallation 223 | 224 | if (cocoaPodsStatus === CocoaPodsStatus.recommended) { 225 | if (this.cocoaPods.isCocoaPodsInitialized) { 226 | this.messages.push(new ValidationMessage(`CocoaPods version ${this.cocoaPods.cocoaPodsVersionText}`)) 227 | } else { 228 | this.brewStatus = ValidationType.partial 229 | this.messages.push( 230 | new ValidationMessage( 231 | `CocoaPods installed but not initialized.\n 232 | ${noCocoaPodsConsequence}\n 233 | To initialize CocoaPods, run:\n 234 | pod setup\n 235 | once to finalize CocoaPods\' installation.`, 236 | true /* isError */, 237 | ), 238 | ) 239 | } 240 | } else { 241 | this.brewStatus = ValidationType.partial 242 | if (cocoaPodsStatus === CocoaPodsStatus.notInstalled) { 243 | this.messages.push( 244 | new ValidationMessage( 245 | `CocoaPods not installed.\n 246 | ${noCocoaPodsConsequence}\n 247 | To install: 248 | ${cocoaPodsInstallInstructions}`, 249 | true /* isError */, 250 | ), 251 | ) 252 | } else { 253 | this.messages.push( 254 | new ValidationMessage( 255 | `CocoaPods out of date (${this.cocoaPods.cocoaPodsRecommendedVersion} is recommended).\n 256 | ${noCocoaPodsConsequence}\n 257 | To upgrade:\n 258 | ${cocoaPodsUpgradeInstructions}`, 259 | true /* isError */, 260 | ), 261 | ) 262 | } 263 | } 264 | } else { 265 | this.brewStatus = ValidationType.missing 266 | this.messages.push( 267 | new ValidationMessage( 268 | `Brew not installed; use this to install tools for iOS device development.\n 269 | Download brew at https://brew.sh/.`, 270 | true /* isError */, 271 | ), 272 | ) 273 | } 274 | return new ValidationResult( 275 | [this.xcodeStatus, this.brewStatus].reduce(this.mergeValidationTypes), 276 | this.messages, 277 | this.xcodeVersionInfo, 278 | ) 279 | } 280 | 281 | private mergeValidationTypes(t1: ValidationType, t2: ValidationType): ValidationType { 282 | return t1 === t2 ? t1 : ValidationType.partial 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/refimg/generate.ts: -------------------------------------------------------------------------------- 1 | import { writeFile} from 'fs-extra' 2 | export class Generate { 3 | static start(dartPath, images) { 4 | const attributes = [] 5 | images.forEach(image => { 6 | const attName = this.getImgKey(image) 7 | const attribute = ' static const ' + attName + ' = ' + '"images/' + image + '";' 8 | attributes.push(attribute) 9 | }) 10 | const fileName = this.getFlieName(dartPath) 11 | const className = this.getClasseName(fileName) 12 | const content = attributes.join('\n') 13 | const classContent = ' // 由weexbox自动生成,无需手动编辑\n class ' + className + ' {\n' + content + '\n}' 14 | this.createDartFile(dartPath, classContent) 15 | } 16 | // 获取文件名称 17 | static getFlieName(path) { 18 | let modalName = '' 19 | const att = path.split('/') 20 | if (att.length > 1) { 21 | path = att[att.length - 1] 22 | } 23 | modalName = path.replace('.dart', '') 24 | return modalName 25 | } 26 | /// 生成dart文件 27 | static createDartFile(fileName, content) { 28 | writeFile(fileName, content, 'utf8', (error) => { 29 | if (error) { 30 | console.log('\x1B[31m%s\x1B[0m', 'error: ' + error + '\n') 31 | } else { 32 | console.log('\x1B[36m%s\x1B[0m', 'succeed: dart文件创建成功' + fileName + '\n') 33 | } 34 | }) 35 | } 36 | static getImgKey(key) { 37 | key = key.replace(/.png/g, '') 38 | key = key.replace(/.jpg/g, '') 39 | key = key.toUpperCase() 40 | return key 41 | } 42 | // 类名变大驼峰 43 | static getClasseName(className) { 44 | let i = 0 45 | const newStrs = [] 46 | // 下划线下标 47 | let underlineIndex = -1 48 | for ( i = 0; i < className.length; i++) { 49 | const str = className.charAt(i) 50 | // 需要大写 51 | let needToUpper = false 52 | // 首字母或下划线的后一位需要大写 53 | if (i === 0 || i === underlineIndex + 1) { 54 | needToUpper = true 55 | } 56 | // 出现下划线 57 | if (str.indexOf('_') !== -1) { 58 | underlineIndex = i 59 | } else { 60 | const chart = needToUpper === true ? str.toUpperCase() : str 61 | newStrs.push(chart) 62 | } 63 | } 64 | return newStrs.join('') 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/refimg/index.ts: -------------------------------------------------------------------------------- 1 | import { readdir, createReadStream, writeFile, pathExistsSync, existsSync, readdirSync, statSync } from 'fs-extra' 2 | import { createInterface } from 'readline' 3 | import { Generate } from './generate' 4 | export class Refimg { 5 | // 图片目录 6 | static imagesDir: string 7 | // 配置文件名称 8 | static flutterPubspecName: string 9 | // model名称 10 | static modelName: string 11 | // dart文件路径 12 | static dartPath: string 13 | // 图片数组 14 | static imageNames: any[] 15 | static start(path) { 16 | const pathIsOK = this.setConfigPath() 17 | if (pathIsOK === true) { 18 | this.dartPath = path 19 | this.synImageConfig() 20 | } else { 21 | console.log('\x1B[31m%s\x1B[0m', 'error:请检查工程目录是否存在images,pubspec.yaml\n') 22 | } 23 | } 24 | 25 | // 同步图片配置 26 | static synImageConfig() { 27 | this.readdirImage(this.imagesDir).then(images => { 28 | this.imageNames = images 29 | return this.writeImageForConfig(images, this.flutterPubspecName) 30 | }).then(conetnt => { 31 | return this.updateConfig(this.flutterPubspecName, conetnt) 32 | }).then(_ => { 33 | this.createModel() 34 | }) 35 | } 36 | // 更新配置文件。 37 | static updateConfig(config: string, content: string) { 38 | return new Promise(reslove => { 39 | writeFile(config, content, 'utf8', (error) => { 40 | if (error) { 41 | console.log('\x1B[31m%s\x1B[0m', 'error: ' + error + '\n') 42 | } else { 43 | console.log('\x1B[36m%s\x1B[0m', 'succeed: images目录的图片已同步到pubspec.yaml\n') 44 | reslove() 45 | } 46 | }) 47 | }) 48 | } 49 | // 生成model 50 | static createModel() { 51 | if (this.dartPath !== undefined) { 52 | const defaultPath = this.flutterPubspecName.replace('pubspec.yaml', '') + 'lib/util/image_path_config.dart' 53 | this.dartPath = this.dartPath === 'd' ? defaultPath : this.dartPath 54 | Generate.start(this.dartPath, this.imageNames) 55 | } 56 | } 57 | // 图片写入配置。 58 | static writeImageForConfig(files: any[], configPath: string) { 59 | return new Promise(reslove => { 60 | const rl = createInterface({ 61 | input: createReadStream(configPath), 62 | }) 63 | // 是否可添加 64 | let canAdd = true 65 | // 任务执行中。 66 | let tasking = false 67 | const lines = [] 68 | rl.on('line', (line) => { 69 | // 找到 assets 标识,开始插入新的图片。 70 | if (this.lineIsAssets(line)) { 71 | canAdd = false 72 | tasking = true 73 | // 当前行是assets需要加入。 74 | lines.push(line) 75 | // 加入新的图片 76 | files.forEach(file => { 77 | const newfile = ` - images/${file}` 78 | lines.push(newfile) 79 | }) 80 | } 81 | // 任务已经开始且当前行是#时,过滤任务结束。 82 | if (tasking === true && line.indexOf('#') !== -1) { 83 | tasking = false 84 | canAdd = true 85 | } 86 | if (canAdd === true) { 87 | lines.push(line) 88 | } 89 | }) 90 | // 任务结束 91 | rl.on('close', () => { 92 | reslove(lines.join('\n')) 93 | }) 94 | }) 95 | } 96 | // 获取文件夹下的图片。 97 | static readdirImage(dir: string) { 98 | return new Promise(reslove => { 99 | readdir(dir, (_, files) => { 100 | reslove(this.getImageFiles(files)) 101 | }) 102 | }) 103 | } 104 | // 确保文件夹内是图片。 105 | static getImageFiles(files) { 106 | const images = [] 107 | files.forEach(file => { 108 | if (file.indexOf('.png') !== -1 || file.indexOf('.jpg') !== -1) { 109 | images.push(file) 110 | } 111 | }) 112 | return images 113 | } 114 | // 设置路径 115 | static setConfigPath() { 116 | this.convenientDir('.') 117 | const hasDir = pathExistsSync(this.imagesDir) 118 | const hasPub = pathExistsSync(this.flutterPubspecName) 119 | return hasDir && hasPub 120 | } 121 | // 便利文件夹 122 | static convenientDir(path) { 123 | let imagesDirPath = null 124 | let pubFilePath = null 125 | if (existsSync(path) === true) { 126 | const files = readdirSync(path) 127 | for (const file of files) { 128 | const currentPath = path + '/' + file 129 | // 配置文件 130 | if (currentPath.indexOf('pubspec.yaml') !== -1 && pubFilePath === null) { 131 | pubFilePath = currentPath 132 | this.flutterPubspecName = pubFilePath 133 | } 134 | // 图片路径 135 | if (currentPath.indexOf('images') !== -1 && statSync(currentPath).isDirectory() === true && imagesDirPath === null ) { 136 | imagesDirPath = currentPath 137 | this.imagesDir = imagesDirPath 138 | } 139 | if (statSync(currentPath).isDirectory() === true && currentPath.indexOf('node_modules') === -1 140 | && currentPath.indexOf('git') === -1 141 | && currentPath.indexOf('.ios') === -1 142 | && currentPath.indexOf('.android') === -1) { 143 | this.convenientDir(currentPath) 144 | } 145 | } 146 | } 147 | } 148 | // 当前行是 assets 149 | static lineIsAssets(line) { 150 | return (line.indexOf('assets:') !== -1 && line.indexOf('#') === -1) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /template/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /template/index.js: -------------------------------------------------------------------------------- 1 | import App from './App.vue' 2 | 3 | App.el = '#root' 4 | new Vue(App) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": false, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "outDir": "./lib", 8 | "target": "esnext" 9 | }, 10 | "include": [ 11 | "src/**/*" 12 | ], 13 | "exclude": [ 14 | "node_modules" 15 | ] 16 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false, 16 | "semicolon": [true, "never"], 17 | "no-console": false 18 | }, 19 | "rulesDirectory": [] 20 | } 21 | --------------------------------------------------------------------------------