mist-toolkit
Version:
Mist CLI Toolkit
466 lines (391 loc) • 13.7 kB
JavaScript
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
}