UNPKG

mist-toolkit

Version:

Mist CLI Toolkit

466 lines (391 loc) 13.7 kB
#!/usr/bin/env node 'use strict' const DEFAULT_HTTP_PORT = 10001 const DEFAULT_SIMULATOR_SERVER_PORT = 10002 const DEFAULT_SOCKET_PORT = 10003 const MIST_CONFIG_FILE = './mist_debug_config.json' const PRESETED_DEMOS_PATH = '/demos' const commander = require('commander'), chalk = require('chalk'), httpServer = require('http-server'), os = require('os'), _ = require("underscore"), watch = require('node-watch'), fs = require('fs'), inquirer = require('inquirer'), exec = require('child_process').exec, path = require('path'), Mustache = require('mustache') //port var serverPort = DEFAULT_HTTP_PORT var clientPort = DEFAULT_SIMULATOR_SERVER_PORT // demo folder || preseted demo folder var workingDir // device socket connection var wsConnection // mist -q set true var needQR = false // is serving a mist bundle var serverStarted = false commander .version('1.2.0') .option('-i, init', 'init configuration') .option('-s, server', 'start mist development server') .option('-S, server_sublime', 'start mist development server & open working directory in Sublime') .option('-q, qr', 'start server & show QR-code, scan with MistDebug') .option('-d, demo', 'start server to try out mist demo with Mist Demo App') .option('-p, page', 'start mist page development server') .option('-x, debug', 'debug mist page in Safari') .option('-g, generate_mistpage', 'generate mist-page template project') commander.on('--help', function () { console.log(` Examples: mist -i : init configuration mist -s : debug on simulator, shortcut for Server mist -S : debug on simulator & open working directory in Sublime mist -q : debug on real device with socket connection for live-loading mist -d : try out preseted demos mist -p : start mist page debug toolkit, shortcut for Page mist -x : debug mist page in Safari mist -g : generate mist-page template project `) }) commander.parse(process.argv) //mist (show help) var userArgs = process.argv.slice(2) if (!userArgs.length) { commander.help() } //mist -i if (commander.init) { setupConfig() } //mist -s if (commander.server) { startDebugger() } //mist -S if (commander.server_sublime) { startDebugger() exec(`open ${workingDir} -a 'Sublime Text'`) } function startDebugger() { var configLoaded = loadMistConfig() if (configLoaded) { startServer() } } //mist -q if (commander.qr) { needQR = true var configLoaded = loadMistConfig() if (configLoaded) { startServer() } } //mist -d if (commander.demo) { // preseted demos let demoPath = __dirname + PRESETED_DEMOS_PATH if (fs.existsSync(demoPath)) { workingDir = demoPath needQR = true console.log(chalk.green(`> Loading demo from ${demoPath}. San QR Code with Mist to try out.`)) startServer() exec(`open ${workingDir}`) } else { console.log(chalk.red(`Can\'t find demo in ${demoPath}. Please try "npm install -g mist-toolkit" to update`)) } } //mist -p if (commander.page) { mistPageDev(false) } //mist -x if (commander.debug) { mistPageDev(true) } // mist -g if (commander.generate_mistpage) { const questions = [{ type: "input", name: "pageName", message: "Input your page's name(i.e: In UserDetailPage please input \"UserDetail\"):" }, { type: "confirm", name: "isList", message: "Is't a List page?:" }] inquirer.prompt(questions).then((answers) => { const pageName = answers['pageName'] const iPageName = pageName.substr(0, 1).toLowerCase() + pageName.substr(1, pageName.length - 1) const isList = answers['isList'] let folderName = isList ? pageName.toLowerCase() + '_list_page' : pageName.toLowerCase() + '_page' let pageFileName = isList ? pageName.toLowerCase() + '_list_page.ts' : pageName.toLowerCase() + '_page.ts' let modelFileName = isList ? pageName.toLowerCase() + '_list_model.ts' : pageName.toLowerCase() + '_model.ts' const templateData = { PAGE_NAME: pageName, i_PAGE_NAME: iPageName, MODEL_FILE_NAME: modelFileName.substr(0, modelFileName.length - 3) } let pageTemplate let modelTemplate if (isList) { pageTemplate = fs.readFileSync(__dirname + '/template/tpl_listpage.ts', 'utf8') modelTemplate = fs.readFileSync(__dirname + '/template/tpl_listmodel.ts', 'utf8') } else { pageTemplate = fs.readFileSync(__dirname + '/template/tpl_page.ts', 'utf8') modelTemplate = fs.readFileSync(__dirname + '/template/tpl_model.ts', 'utf8') } const pageOutput = Mustache.render(pageTemplate, templateData) const modelOutput = Mustache.render(modelTemplate, templateData) if (!fs.existsSync(folderName)) { fs.mkdirSync(folderName) } fs.writeSync(fs.openSync(folderName + '/' + pageFileName, 'w+'), pageOutput) fs.writeSync(fs.openSync(folderName + '/' + modelFileName, 'w+'), modelOutput) console.log(`\n代码已生成在${folderName},在工程目录执行 "mist -p" 进入调试模式\n`) }) } function mistPageDev(isDebug) { if (!fs.existsSync(MIST_CONFIG_FILE)) { console.log(chalk.red(`Can't find mist config file: ${MIST_CONFIG_FILE}. You can create like:\n`) + chalk.cyan(` {\n "MIST_RESOURCES_BUNDLE": "Your mist resource folder here"\n }\n`)) return false } let config = JSON.parse(fs.readFileSync(MIST_CONFIG_FILE)) let mistPagePath = path.resolve(config.MIST_PAGE_ROOT_DIR) let mistPageOutput = path.resolve(config.MIST_PAGE_OUTPUT_DIR) let mistPageEntries = config.MIST_PAGE_ENTRIES if (!(mistPagePath.length && mistPageOutput.length && mistPageEntries)) { console.log(` Wrong MistPage config, please config like below: "MIST_PAGE_ROOT_DIR": "Resources/mistpage", "MIST_PAGE_ENTRIES": { //KEY: will be output file's name //VALUE: relative path based on MIST_PAGE_ROOT_DIR "my_comment": "/user_comments_list/my_comment_page.ts", }, "MIST_PAGE_OUTPUT_DIR": "Resources/DynamicView.bundle" ` ) return; } startDebugger() let webpack = require('webpack') let plugins = [new webpack.NoEmitOnErrorsPlugin()] if (!isDebug) { plugins.push(new webpack.optimize.UglifyJsPlugin()) } let entries = mistPageEntries for (let item in mistPageEntries) { entries[item] = path.join(mistPagePath, mistPageEntries[item]) } let compiler = webpack({ entry: entries, output: { path: `${mistPageOutput}`, filename: '[name].js' }, resolve: { extensions: ['.ts', '.js'] }, externals: { '@alipay/mist-page/lib': 'MistPageFramework' }, module: { loaders: [ { test: /\.ts$/, loader: loadModule('ts-loader') }, { test: /\.mist/, loader: loadModule('raw-loader') } ] }, devtool: isDebug ? "inline-source-map" : "", plugins: plugins }) const watching = compiler.watch({ }, (err, stats) => { console.log(stats.toString({ colors: true, modules: false, hash: false, version: false, timings: false })) }) } function loadModule(moduleName) { try { let modulePath = require.resolve(moduleName) return modulePath.slice(0, modulePath.lastIndexOf(moduleName) + moduleName.length) } catch (e) { return moduleName } } //handle user input process.stdin.setEncoding('utf8') process.stdin.on('data', function (command) { if (!serverStarted) { return } command = command.replace(/(\r\n|\n|\r)/gm, ""); if (command == '') { return } //open working directory in Finder else if (command == 'd') { exec(`open ${workingDir}/\`ls ${workingDir} | head -n 1\``) } //open working directory in sublime else if (command == 's') { exec(`open ${workingDir} -a 'Sublime Text'`) } //show qr code else if (command == 'qr') { startWebSocket() showQR() } //simulator else if (command == 'sim') { exec('open -a Simulator.app') } else { console.log(chalk.red('\n Command not supported.\n') + chalk.green(' d: open working directory in Finder\n') + chalk.green(' s: open working directory in Sublime\n') + chalk.green(' qr: show QR-code for real device debugging\n') + chalk.green(' sim: launch Simulator\n')) } }) //port in-use exception process.on('uncaughtException', function (err) { if (err.code == 'EADDRINUSE') { console.log(chalk.red(`\n Port ${serverPort} already in use.\n\n`) + chalk.green(` 1.Use <lsof -i tcp:${serverPort}> then <kill $PID> to free.\n 2.Custom server port & client port both in ${MIST_CONFIG_FILE} and MistDebug: \n In ${MIST_CONFIG_FILE}:\n "CLIENT_PORT": 7538,\n "SERVER_PORT": 2631 \n In MistDebug:\n [MSTDebugger defaultDebugger].clientPort = 7538\n [MSTDebugger defaultDebugger].serverPort = 2631\n`)) } }) function setupConfig() { if (fs.existsSync(MIST_CONFIG_FILE)) { return } let question = { type: 'input', name: 'path', message: 'Path of mist files (i.e Resources/MistDebug.bundle):', } inquirer.prompt([question]).then(function (answers) { let config = { MIST_RESOURCES_BUNDLE: answers['path'] } fs.writeFile(MIST_CONFIG_FILE, JSON.stringify(config)) console.log(chalk.green(`Config file '${require('path').resolve(MIST_CONFIG_FILE)}' was created`)) }) } function loadMistConfig() { if (!fs.existsSync(MIST_CONFIG_FILE)) { console.log(chalk.red(`Can't find mist config file: ${MIST_CONFIG_FILE}. You can create like:\n`) + chalk.cyan(` {\n "MIST_RESOURCES_BUNDLE": "Your mist resource folder here"\n }\n`)) return false } var configuration = JSON.parse(fs.readFileSync(MIST_CONFIG_FILE)) var mistResourceBundle = configuration.MIST_RESOURCES_BUNDLE workingDir = mistResourceBundle //custom port var sPort = configuration.SERVER_PORT if (sPort) { serverPort = sPort } var cPort = configuration.CLIENT_PORT if (cPort) { clientPort = cPort } return true } function startServer() { serverStarted = true let options = { root: workingDir, logFn: (req, res, err) => { console.log(chalk.green('> GET\t') + chalk.gray(`${req.url}`)) } } let server = httpServer.createServer(options) server.listen(serverPort, "0.0.0.0", function () { console.log(chalk.green(`> Start mist debug server at 127.0.0.1:${serverPort}`)) if (needQR) { startWebSocket() showQR() } watchForRefresh() }) } function startWebSocket() { var server = require('net').createServer() server.listen(DEFAULT_SOCKET_PORT, '0.0.0.0') let IP = getPublicIP() let url = `http://${IP}:${DEFAULT_SOCKET_PORT}` console.log(chalk.green(`> Listening socket connection on ${url}`)) server.on('connection', function (sock) { exec('pkill -f QRViewer') wsConnection = sock console.log(chalk.cyan('\n> SOCKET CONNECTED\n')) }) server.on('close', function (data) { console.log('socket close') sock.write('close') }) } function watchForRefresh() { console.log(chalk.green(`> Watching ${workingDir} changes`)) watch(workingDir, function (fileName) { // watch mist & json change if (!fileName.match('.*?\.(mist|json|js)')) { return } console.log(chalk.cyan('\n> CHANGED\n\t') + chalk.gray(`${fileName}\n`)) // for device refresh if (wsConnection && wsConnection.server._connections) { try { wsConnection.write('refresh') } catch (er) { console.log(chalk.red('SOCKET NOT CONNECTED')) } return } // for simulator refresh. var options = { hostname: '0.0.0.0', port: clientPort, method: 'GET', path: '/refresh' } var req = require('http').request(options, null) req.on('error', (e) => { console.log(chalk.red(`SIMULATOR NOT RESPONSE: ${e.message}\n`)) }) req.end() }) } // Helper function showQR() { let info = `{\\"ip\\":\\"${getPublicIP()}\\",\\"port\\":\\"${serverPort}\\",\\"wsport\\":\\"${DEFAULT_SOCKET_PORT}\\"\\}` exec(`${__dirname}/QRViewer.app/Contents/MacOS/QRViewer 'Scan with MistDebug. ESC to close.' ${info}`) } function getPublicIP() { var publicIP = "127.0.0.1" var ifaces = os.networkInterfaces() var address = _.flatten(_.values(ifaces)) address = _.filter(address, function (ifObj) { return ifObj.family == "IPv4" && !/^127\./.test(ifObj.address) }); if (address.length > 0) { publicIP = address[0].address } return publicIP }