automan-cmd 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +56 -0
  3. package/bin/automan +3 -0
  4. package/bin/automan-build +3 -0
  5. package/bin/automan-config +3 -0
  6. package/bin/automan-create +3 -0
  7. package/bin/automan-publish +3 -0
  8. package/lib/automan-build.js +41 -0
  9. package/lib/automan-config.js +82 -0
  10. package/lib/automan-create.js +137 -0
  11. package/lib/automan-publish.js +331 -0
  12. package/lib/index.js +13 -0
  13. package/lib/install.js.tpl +47 -0
  14. package/lib/util.js +174 -0
  15. package/package.json +37 -0
  16. package/tpl/.babelrc +16 -0
  17. package/tpl/.browserslistrc +3 -0
  18. package/tpl/.eslintignore +2 -0
  19. package/tpl/.eslintrc.js +228 -0
  20. package/tpl/.gitignore.ejs +12 -0
  21. package/tpl/.postcssrc.js +12 -0
  22. package/tpl/README.md +1 -0
  23. package/tpl/changelog.md +1 -0
  24. package/tpl/editor/index.vue +45 -0
  25. package/tpl/icon.png +0 -0
  26. package/tpl/jsconfig.json +7 -0
  27. package/tpl/package.json.ejs +66 -0
  28. package/tpl/preview/app.vue +326 -0
  29. package/tpl/preview/attr/Data.vue +69 -0
  30. package/tpl/preview/attr/Resource.vue +79 -0
  31. package/tpl/preview/attr/com.vue +21 -0
  32. package/tpl/preview/attr/index.js +16 -0
  33. package/tpl/preview/components/Attribute.vue +365 -0
  34. package/tpl/preview/components/FitImg.vue +152 -0
  35. package/tpl/preview/components/ImgViewer.vue +80 -0
  36. package/tpl/preview/components/Loading.vue +55 -0
  37. package/tpl/preview/components/Toast.vue +111 -0
  38. package/tpl/preview/index.js +22 -0
  39. package/tpl/preview/index.tpl +13 -0
  40. package/tpl/preview/lib/ESlog.js +46 -0
  41. package/tpl/preview/lib/Util.js +57 -0
  42. package/tpl/preview/lib/fetch.js +139 -0
  43. package/tpl/preview/lib/index.js +15 -0
  44. package/tpl/preview/lib/vue/filters.js +53 -0
  45. package/tpl/preview/lib/vue/index.js +9 -0
  46. package/tpl/preview/lib/vue/mixin.js +166 -0
  47. package/tpl/preview/mint-ui/message-box/index.js +1503 -0
  48. package/tpl/preview/mint-ui/message-box/style.css +159 -0
  49. package/tpl/preview/mint-ui/popup/index.js +1046 -0
  50. package/tpl/preview/mint-ui/popup/style.css +115 -0
  51. package/tpl/preview/mint-ui/spinner/index.js +657 -0
  52. package/tpl/preview/mint-ui/spinner/style.css +227 -0
  53. package/tpl/preview/mint-ui/swipe/index.js +907 -0
  54. package/tpl/preview/mint-ui/swipe/style.css +43 -0
  55. package/tpl/preview/mint-ui/swipe-item/index.js +171 -0
  56. package/tpl/preview/mint-ui/swipe-item/style.css +1 -0
  57. package/tpl/preview/style.css +126 -0
  58. package/tpl/server.config.js +6 -0
  59. package/tpl/src/assets/css/index.scss +29 -0
  60. package/tpl/src/example.vue +165 -0
  61. package/tpl/src/index.vue.ejs +32 -0
  62. package/tpl/webpack.config.js.ejs +267 -0
  63. package/tpl/yarn.lock +6037 -0
@@ -0,0 +1,331 @@
1
+ const util = require('./util')
2
+ const build = require('./automan-build')
3
+ const commander = require('commander')
4
+ const fs = require('fs-extra')
5
+ const path = require('path')
6
+ const glob = require('glob')
7
+ const configUtil = require('./automan-config.js')
8
+ const axios = require('axios')
9
+ const chalk = require('chalk')
10
+ const FormData = require('form-data')
11
+
12
+ commander
13
+ .option('-t, --token [token]', '指定鉴权token')
14
+ .option('-r, --registry [registry]', '指定鉴权registry')
15
+ .option('--skip-build', '跳过构建')
16
+ .parse(process.argv)
17
+
18
+ let skipBuild = commander['skipBuild']
19
+ let token = commander['token']
20
+ // 增加一个host选项
21
+ let registry = commander['registry']
22
+
23
+ // const CWD = '/Users/arnan/Documents/code/ml-tpl'
24
+ const CWD = process.cwd()
25
+ let GET_TOKEN, CLI_KEY
26
+ let FIND_COMPONENT
27
+ let ADD_COMPONENT
28
+
29
+ async function initConfig() {
30
+ let host
31
+
32
+ if(registry){
33
+ host = registry
34
+ }else{
35
+ try {
36
+ host = await configUtil.getConfig('registry')
37
+ } catch (e) {}
38
+ }
39
+
40
+ host = host.replace(/(https?:\/\/[\w.\-:]+)(.*)$/, (m, p1, p2) => p1 + (p2.replace(/\/$/, '') || '/api'))
41
+ GET_TOKEN = `${host}/upload/getToken`
42
+ FIND_COMPONENT = `${host}/component/find`
43
+ ADD_COMPONENT = `${host}/component/add`
44
+ }
45
+
46
+ async function getAccessInfo() {
47
+ let key = ''
48
+ let tokenKey = 'token'
49
+ let from = 'input'
50
+
51
+ util.log('\n')
52
+ if (token) {
53
+ key = token
54
+ from = 'commander'
55
+ } else {
56
+ try {
57
+ key = await configUtil.getConfig(tokenKey)
58
+ if (key) from = 'config'
59
+ } catch (e) {}
60
+ }
61
+ if (from == 'config') {
62
+ from = !(await util.confirm(`取得奥特曼用户鉴权token【${key}】,是否使用?`)) ? 'input' : 'config'
63
+ }
64
+ if (from == 'input') {
65
+ key = await util.quiz('请输入您的奥特曼用户鉴权token (在【奥特曼后台-用户设置-秘钥】页面可获取)')
66
+ }
67
+ key = String(key || '').trim()
68
+
69
+ CLI_KEY = key
70
+ return key
71
+ }
72
+
73
+ async function genComponentJson(ns,visibilitylevel) {
74
+ let PACKAGE_JSON_PATH = path.resolve(CWD, 'package.json')
75
+ let pkg = await fs.readJson(PACKAGE_JSON_PATH)
76
+
77
+ return {
78
+ namespace: util.normalizeName(ns),
79
+ name: util.normalizeName(pkg.name),
80
+ type: +pkg.type || 0,
81
+ description: pkg.description,
82
+ version: pkg.version,
83
+ visibilitylevel: Number(visibilitylevel),
84
+ tags: (pkg.tags || []).filter(t => !isNaN(t)).map(t => ({
85
+ id: t
86
+ })),
87
+ built: pkg.built
88
+ }
89
+ }
90
+
91
+ async function ossUpload({
92
+ credentials,
93
+ COMPONENT_PATH,
94
+ package
95
+ } = {}) {
96
+ let mainFile;
97
+ await (async function () {
98
+ const componentFiles = glob.sync('{dist/**/*.*,README.md,icon.png,cover.png,changelog.md}', {
99
+ cwd: CWD,
100
+ absolute: false
101
+ })
102
+ for (let file of componentFiles) {
103
+ if (/\.tmp$/.test(file)) continue
104
+ util.log(`\n上传文件${file}`)
105
+ let filekey = `${COMPONENT_PATH.split(/[/@]/).join('/')}/${file.replace(/^dist[\/]/, '')}`
106
+ var fileurl = `${credentials.host}/${credentials.dir}${filekey}`
107
+
108
+ try {
109
+ if (/(index|editor)\.js$/.test(file)) {
110
+ if (/index\.js$/.test(file)) mainFile = fileurl
111
+ file = await setConstVar(path.resolve(CWD, file), {
112
+ namespace: package.namespace,
113
+ publicpath: `${credentials.host}/${credentials.dir}`,
114
+ version: package.version,
115
+ name: package.name
116
+ })
117
+ }
118
+
119
+ await upload(filekey, path.resolve(CWD, file), credentials)
120
+ } catch (err) {
121
+ util.logRed(err.message || err)
122
+ process.exit()
123
+ }
124
+ util.log(`上传成功: ${fileurl}`)
125
+ }
126
+ })()
127
+ return mainFile
128
+
129
+ async function setConstVar(filepath, {
130
+ publicpath,
131
+ namespace,
132
+ name,
133
+ version
134
+ }) {
135
+ let str = await fs.readFile(filepath, 'utf-8')
136
+
137
+ const tmp = filepath + '.tmp'
138
+ await fs.writeFile(tmp, str, 'utf-8')
139
+ return tmp
140
+ }
141
+
142
+ function upload(name, filepath, {
143
+ policy,
144
+ dir,
145
+ accessid,
146
+ host,
147
+ signature
148
+ }) {
149
+ let form = new FormData()
150
+ form.append('name',name)
151
+ form.append('key',dir + name)
152
+ form.append('policy',policy)
153
+ form.append('OSSAccessKeyId',accessid)
154
+ form.append('success_action_status','200')
155
+ form.append('signature',signature)
156
+ form.append(
157
+ 'file',
158
+ fs.createReadStream(filepath),
159
+ {
160
+ filename: name,
161
+ })
162
+
163
+ return axios({
164
+ url:host,
165
+ method:'post',
166
+ headers:form.getHeaders(),
167
+ data:form
168
+ }).then((res)=>{
169
+ }).catch((e)=>{
170
+ console.log(e)
171
+ })
172
+ }
173
+ }
174
+
175
+
176
+ async function checkComponent({
177
+ name,
178
+ namespace,
179
+ version
180
+ }) {
181
+
182
+ await axios({
183
+ url:FIND_COMPONENT,
184
+ headers:{
185
+ clikey:CLI_KEY
186
+ },
187
+ method:'POST',
188
+ data:{
189
+ name: `${namespace}/${name}`,
190
+ status: 'all',
191
+ version
192
+ }
193
+ }).then(({data}) => {
194
+ const list = data.data.list
195
+ if(data.code === 1){
196
+ if(list.length>0){
197
+ throw new Error(`请核对您的组件版本或者组件命名(package.json 'version'、'name'字段),组件库已有记录\n${list.map(com => `${com.name}@${com.version}`).join('\n')}`)
198
+ }
199
+ }else{
200
+ throw new Error('系统异常')
201
+ }
202
+ })
203
+ .catch(e => {
204
+ util.logRed(e.message || e)
205
+ process.exit()
206
+ })
207
+ }
208
+
209
+ async function addComponent(mainFile, package,visibilitylevel) {
210
+ await axios({
211
+ url: ADD_COMPONENT,
212
+ headers: {
213
+ clikey: CLI_KEY
214
+ },
215
+ method: 'POST',
216
+ data: {
217
+ name: `${package.namespace}/${package.name}`,
218
+ version: package.version,
219
+ desc: package.description,
220
+ type: package.type,
221
+ tags: package.tags,
222
+ path: mainFile.replace(/^http:/, 'https:'),
223
+ visibilitylevel:Number(visibilitylevel)
224
+ }
225
+ }).then(({data}) => {
226
+ if (data.code !== 1) {
227
+ throw new Error(data && data.msg || '添加组件失败')
228
+ }
229
+ })
230
+ .catch(e => {
231
+ util.logRed(e.message || e)
232
+ process.exit()
233
+ })
234
+ }
235
+
236
+ async function getCredentials() {
237
+ const credentials = await axios({
238
+ url:GET_TOKEN,
239
+ headers:{
240
+ clikey:CLI_KEY
241
+ }
242
+ })
243
+ .then(({data})=>{
244
+ if(data.code===1){
245
+ return data.data
246
+ }else{
247
+ throw new Error(data.msg || '获取oss token 失败')
248
+ }
249
+ })
250
+ .catch(e=>{
251
+ console.log(e)
252
+ process.exit()
253
+ })
254
+
255
+ return credentials
256
+ }
257
+
258
+ async function start() {
259
+ // 询问是否公开组件
260
+ // const visibilitylevel = Number(await util.confirm('是否设置该组件为所有人可见'))
261
+
262
+ //通过输入确定是否公开组件
263
+ async function pleaseAnswerMe () {
264
+ let answer
265
+ while (!(/^[YyNn]$/).test(answer = await getAnswer())) {
266
+ util.logRed('请输入Y/N(不区分大小写)来选择是否公开(官方)组件')
267
+ }
268
+
269
+ async function getAnswer () {
270
+ let _answer= (await util.quiz('请输入Y/N(不区分大小写)来选择是否公开(官方)组件')).trim()
271
+ return _answer
272
+ }
273
+
274
+ if(answer === 'Y' || answer === 'y'){
275
+ answer = true
276
+ }else{
277
+ answer = false
278
+ }
279
+
280
+ return answer
281
+ }
282
+
283
+ const visibilitylevel = await pleaseAnswerMe()
284
+
285
+ await initConfig()
286
+ await getAccessInfo()
287
+
288
+ util.logGreen('\n授权KEY')
289
+ util.log(CLI_KEY)
290
+
291
+ const credentials = await getCredentials()
292
+
293
+ if(visibilitylevel){
294
+ credentials.ns='main'
295
+ }
296
+
297
+ const package = await genComponentJson(credentials.ns,visibilitylevel)
298
+
299
+
300
+ await checkComponent(package)
301
+
302
+ if (!(skipBuild || package.built)) {
303
+ util.log('\n')
304
+ try {
305
+ await build({
306
+ namespace: package.namespace,
307
+ name: package.name,
308
+ version: package.version,
309
+ publicpath: `${credentials.host}/${credentials.dir}`
310
+ })
311
+
312
+ } catch (e) {
313
+ console.error(e)
314
+ process.exit()
315
+ }
316
+ }
317
+
318
+ let COMPONENT_PATH = `${package.namespace}/${package.name}@${package.version}`
319
+
320
+ let mainFile = await ossUpload({
321
+ credentials,
322
+ COMPONENT_PATH,
323
+ package
324
+ })
325
+
326
+ await addComponent(mainFile, package,visibilitylevel)
327
+ util.logGreen('\n发布成功')
328
+ util.logGreen(COMPONENT_PATH)
329
+ }
330
+ // start()
331
+ module.exports = start
package/lib/index.js ADDED
@@ -0,0 +1,13 @@
1
+ const process = require('process')
2
+ const commander = require('commander')
3
+ const package = require('../package.json')
4
+
5
+ commander
6
+ .version(package.version, '-v, --version')
7
+ .description(`奥特曼命令行工具 @${package.version}`)
8
+ .usage('<command> [options]')
9
+ .command('config <key> [val]', '全局配置')
10
+ .command('create <name>', '创建一个组件')
11
+ .command('build', '构建组件')
12
+ .command('publish [options]', '发布组件')
13
+ .parse(process.argv)
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {execSync, spawn} = require('child_process')
4
+ const os = require('os')
5
+
6
+ const versionDiff = (v1, v2) => {
7
+ const vers = [v1, v2].map(String).map(v => v.split('.').map(a => `0000${a}`).join('.'))
8
+ return vers[0] > vers[1] ? 1 : vers[0] == vers[1] ? 0 : -1
9
+ }
10
+
11
+ function asyncExec (cmd, args = []) {
12
+ const ls = spawn(cmd, args, { stdio: 'inherit' })
13
+ return new Promise((resolve, reject) => {
14
+ ls.on('error', reject)
15
+ ls.on('close', resolve)
16
+ })
17
+ }
18
+
19
+ const exist = (() => {
20
+ try {
21
+ execSync(os.platform() !== 'win32' ? 'type gods-pen-publish' : 'where gods-pen-publish')
22
+ return true
23
+ } catch (e) {
24
+ return false
25
+ }
26
+ })()
27
+
28
+ const outOfDate = (() => {
29
+ if (!exist) return false
30
+ let version = '0.0.0'
31
+ try {
32
+ version = execSync('gods-pen -v')
33
+ version = version.toString().trim()
34
+ } catch (e) {
35
+ }
36
+ return versionDiff(version, '1.0.6') <= 0
37
+ })()
38
+
39
+ ;(async () => {
40
+ console.log('开始执行组件发布,请按照提示操作')
41
+ if (outOfDate) {
42
+ console.log('gods-pen 工具版本过低或未安装,将为您升级到最新版本')
43
+ await asyncExec('npm', ['install', 'gods-pen-cli', '-g'])
44
+ }
45
+
46
+ await asyncExec('gods-pen', ['publish'])
47
+ })()
package/lib/util.js ADDED
@@ -0,0 +1,174 @@
1
+ const path = require('path')
2
+ const fs = require('fs-extra')
3
+ const inquirer = require('inquirer')
4
+ const chalk = require('chalk')
5
+ // const request = require('request-promise')
6
+ const axios = require('axios')
7
+ const runScript = require('runscript')
8
+ const exec = require('child_process').exec
9
+
10
+ const IS_WIN = process.platform === 'win32'
11
+ const PROCESS_REGEX = IS_WIN ? /^(.*)\s+(\d+)\s*$/ : /^\s*(\d+)\s+(.*)/
12
+
13
+ function quiz (msg, backup) {
14
+ return inquirer.prompt([{
15
+ name: 'input',
16
+ type: 'input',
17
+ default: backup,
18
+ message: msg,
19
+ }]).then(({input}) => input)
20
+ }
21
+
22
+ function log (msg, color) {
23
+ var msgStr = msg instanceof Error ? msg.message : typeof msg == 'object' ? JSON.stringify(msg, null, 2) : msg
24
+ if (color) {
25
+ msgStr = (chalk[color] || chalk['white'])(msgStr)
26
+ }
27
+ console.log(msgStr)
28
+ }
29
+
30
+ function logGreen (msg) {
31
+ log(msg, 'green')
32
+ }
33
+
34
+ function logRed (msg) {
35
+ log(msg, 'red')
36
+ }
37
+
38
+ function confirm (msg, backup = true) {
39
+ return inquirer.prompt([{
40
+ name: 'yes',
41
+ type: 'confirm',
42
+ default: backup,
43
+ message: msg,
44
+ }]).then(({yes}) => yes)
45
+ }
46
+
47
+ function select (msg, list, backup = 0) {
48
+ return inquirer.prompt([{
49
+ name: 'select',
50
+ type: 'list',
51
+ choices: list,
52
+ default: backup,
53
+ message: msg,
54
+ }]).then(({select}) => select)
55
+ }
56
+
57
+ function checks (msg, list) {
58
+ return inquirer.prompt([{
59
+ name: 'checks',
60
+ type: 'checkbox',
61
+ choices: list,
62
+ message: msg,
63
+ }]).then(({checks}) => checks)
64
+ }
65
+
66
+ function isNullOrUndefined (arg) {
67
+ return arg === null || arg === '' || arg === null
68
+ }
69
+
70
+ function asyncExec (cmd,obj) {
71
+ var ls = exec(cmd,obj,(error, stdout, stderr) => {
72
+ if (error) {
73
+ logRed(`构建异常: ${error}`)
74
+ process.exit(error.code)
75
+ }
76
+ })
77
+
78
+ return new Promise((resolve, reject) => {
79
+ ls.on('error', reject)
80
+ ls.on('close', resolve)
81
+ })
82
+ }
83
+
84
+ function normalizeName (name = '') {
85
+ return name.replace(/[-_]+(\w)/g, (m, p) => p.toUpperCase())
86
+ }
87
+
88
+ function getTags(host){
89
+ return axios.post(
90
+ `${host}/editor/tags/list`,
91
+ {
92
+ data:{
93
+ "categoryId":3,
94
+ "name":""
95
+ }
96
+ }
97
+ )
98
+ .then((data) => {
99
+ return data.data.data
100
+ })
101
+ .catch(e=>[])
102
+ }
103
+
104
+ async function findNodeProcess (filterFn) {
105
+ const command = IS_WIN ?
106
+ 'wmic Path win32_process Where "Name = \'node.exe\'" Get CommandLine,ProcessId' :
107
+ 'ps -eo "pid,args"'
108
+ const stdio = await runScript(command, { stdio: 'pipe' })
109
+ const processList = stdio.stdout.toString().split('\n')
110
+ .reduce((arr, line) => {
111
+ if (!!line && !line.includes('/bin/sh') && line.includes('node')) {
112
+ const m = line.match(PROCESS_REGEX)
113
+ if (m) {
114
+ const item = IS_WIN ? { pid: m[2], cmd: m[1] } : { pid: m[1], cmd: m[2] }
115
+ if (!filterFn || filterFn(item)) {
116
+ arr.push(item)
117
+ }
118
+ }
119
+ }
120
+ return arr
121
+ }, [])
122
+ return processList
123
+ }
124
+
125
+ function killProcess (pids, signal) {
126
+ pids = typeof pids === 'string' ? pids.split(',') : pids
127
+ pids.forEach(pid => {
128
+ try {
129
+ process.kill(pid, signal)
130
+ } catch (err) {
131
+ if (err.code !== 'ESRCH') {
132
+ throw err
133
+ }
134
+ }
135
+ })
136
+ }
137
+
138
+ async function commandExist (cmd) {
139
+ cmd = IS_WIN ? `where ${cmd}` : `type ${cmd}`
140
+ const stdio = await runScript(cmd, { stdio: 'pipe' }).catch(e => ({stderr: e.toString()}))
141
+ return !stdio.stderr
142
+ }
143
+
144
+ async function dirCheck (dir, {
145
+ msg = '%s 已存在,确定要覆盖它么?',
146
+ onlyCheck = false,
147
+ defaultRemove = true
148
+ } = {}) {
149
+ let exist = await fs.pathExists(dir)
150
+ if (!exist) return 'na'
151
+ if (onlyCheck) return 'keep'
152
+ let shouldDelete = await confirm(msg.replace(/%s/g, dir), defaultRemove)
153
+ if (!shouldDelete) return 'keep'
154
+ await fs.remove(dir)
155
+ return 'clear'
156
+ }
157
+
158
+ module.exports = {
159
+ quiz,
160
+ getTags,
161
+ confirm,
162
+ select,
163
+ checks,
164
+ log,
165
+ asyncExec,
166
+ logGreen,
167
+ logRed,
168
+ isNullOrUndefined,
169
+ normalizeName,
170
+ findNodeProcess,
171
+ killProcess,
172
+ commandExist,
173
+ dirCheck
174
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "automan-cmd",
3
+ "version": "2.1.5",
4
+ "description": "奥特曼脚手架",
5
+ "bin": {
6
+ "automan": "bin/automan",
7
+ "automan-build": "bin/automan-build",
8
+ "automan-create": "bin/automan-create",
9
+ "automan-config": "bin/automan-config",
10
+ "automan-publish": "bin/automan-publish"
11
+ },
12
+ "repository": {},
13
+ "engines": {
14
+ "node": ">=7.6.0"
15
+ },
16
+ "keywords": [],
17
+ "author": "GreatJiang",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "archiver": "^3.1.1",
21
+ "axios": "^0.21.4",
22
+ "chalk": "^1.1.3",
23
+ "commander": "^2.20.3",
24
+ "ejs": "^2.7.4",
25
+ "form-data": "^4.0.0",
26
+ "fs-extra": "^6.0.1",
27
+ "glob": "^7.2.3",
28
+ "image-size": "^0.8.3",
29
+ "inquirer": "^6.5.2",
30
+ "open": "^7.4.2",
31
+ "ora": "^3.4.0",
32
+ "qs": "^6.12.3",
33
+ "rimraf": "^2.7.1",
34
+ "runscript": "^1.6.0",
35
+ "unzip": "^0.1.11"
36
+ }
37
+ }
package/tpl/.babelrc ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "presets": [
3
+ ["@babel/preset-env",{
4
+ "modules": false,
5
+ "bugfixes": true,
6
+ "targets": "android >= 4.4, ios >= 8, > 1%"
7
+ }]
8
+ ],
9
+ "plugins": [
10
+ "@babel/plugin-syntax-dynamic-import",
11
+ "@babel/plugin-proposal-optional-chaining",
12
+ "@babel/plugin-proposal-numeric-separator",
13
+ "@babel/plugin-proposal-class-properties",
14
+ "@babel/plugin-proposal-nullish-coalescing-operator"
15
+ ]
16
+ }
@@ -0,0 +1,3 @@
1
+ > 0.5%
2
+ Android > 4.3
3
+ iOS > 7
@@ -0,0 +1,2 @@
1
+ preview/mint-ui/
2
+ preview/lib/