diff --git a/packages/cli/.npmignore b/packages/cli/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/packages/cli/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..dddb8ab --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1 @@ +## iMove-cli \ No newline at end of file diff --git a/packages/cli/index.js b/packages/cli/index.js new file mode 100755 index 0000000..e3c8d59 --- /dev/null +++ b/packages/cli/index.js @@ -0,0 +1,13 @@ +#!/usr/bin/env node +const path = require('path'); +const program = require('commander'); +const pkg = require(path.join(__dirname, './package.json')); + +program + .version(pkg.version) + .option('-d, --dev', '本地在线开发') + .parse(process.argv); + +if(program.dev) { + require('./src/cmd/dev')(); +} diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..50dc2df --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,29 @@ +{ + "name": "@imove/cli", + "version": "0.0.1", + "description": "imove client", + "main": "index.js", + "bin": { + "imove-cli": "index.js" + }, + "keywords": [ + "imove", + "compile" + ], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/suanmei/iMove.git" + }, + "author": "smallstonesk", + "license": "MIT", + "dependencies": { + "body-parser": "^1.19.0", + "commander": "^6.0.0", + "cors": "^2.8.5", + "express": "^4.17.1", + "fs-extra": "^9.0.1" + } +} diff --git a/packages/cli/src/cmd/dev/extractCodes.js b/packages/cli/src/cmd/dev/extractCodes.js new file mode 100644 index 0000000..ed1e744 --- /dev/null +++ b/packages/cli/src/cmd/dev/extractCodes.js @@ -0,0 +1,40 @@ +const path = require('path'); +const fs = require('fs-extra'); + +const writeEntryFile = async (basePath, fileIds) => { + const imports = []; + const funcMaps = []; + fileIds.forEach((id, idx) => { + const funcName = `fn_${idx}`; + imports.push(`import ${funcName} from './${id}';`); + funcMaps.push(`'${id}': ${funcName}`); + }); + const fileContent = [ + imports.join('\n'), + `const nodeFns = {\n ${funcMaps.join(',\n ')}\n};`, + `export default nodeFns;` + ].join('\n'); + const entryFilePath = path.join(basePath, 'index.js'); + await fs.writeFile(entryFilePath, fileContent, {encoding: 'utf8', flag:'w'}); +}; + +const writeNodeCodes = async (basePath, dsl) => { + const fileIds = []; + const {cells = []} = dsl; + const nodes = cells.filter(cell => cell.shape !== 'edge'); + for(const {id, data: {code}} of nodes) { + fileIds.push(id); + const filePath = path.join(basePath, id + '.js'); + await fs.writeFile(filePath, code, {encoding: 'utf8', flag:'w'}); + } + return fileIds; +}; + +const setup = async (dsl, logicBasePath) => { + const nodeCodesPath = path.join(logicBasePath, 'nodeFns'); + await fs.ensureDir(nodeCodesPath); + const fileIds = await writeNodeCodes(nodeCodesPath, dsl); + await writeEntryFile(nodeCodesPath, fileIds); +}; + +module.exports = setup; diff --git a/packages/cli/src/cmd/dev/index.js b/packages/cli/src/cmd/dev/index.js new file mode 100644 index 0000000..c12cfe5 --- /dev/null +++ b/packages/cli/src/cmd/dev/index.js @@ -0,0 +1,38 @@ +const path = require('path'); +const fs = require('fs-extra'); +const simplifyDSL = require('./simplifyDSL'); +const extractCodes = require('./extractCodes'); +const {createServer} = require('../../utils/server'); + +const TPL_PATH = path.join(__dirname, './template'); +const LOGIC_BASE_PATH = path.join(process.cwd(), './src/logic'); + +const setup = () => { + const app = createServer(); + app.post('/api/save', async (req, res) => { + + // check dsl whether existed + if (!req.body || !req.body.dsl) { + res.status(500).json({isCompiled: false}).end(); + return; + } + + // compile + try { + const {dsl} = req.body; + await simplifyDSL(dsl, LOGIC_BASE_PATH); + await extractCodes(dsl, LOGIC_BASE_PATH); + await fs.copy(TPL_PATH, LOGIC_BASE_PATH); + res.status(200).json({isCompiled: true}).end(); + console.log('compile successfully!'); + } catch(err) { + res.status(500).json({isCompiled: false}).end(); + console.log('compile failed! the error is:', err.message); + } + }); +} + +module.exports = function() { + fs.ensureDirSync(LOGIC_BASE_PATH); + setup(); +}; diff --git a/packages/cli/src/cmd/dev/simplifyDSL.js b/packages/cli/src/cmd/dev/simplifyDSL.js new file mode 100644 index 0000000..46e7a3b --- /dev/null +++ b/packages/cli/src/cmd/dev/simplifyDSL.js @@ -0,0 +1,29 @@ +const path = require('path'); +const fs = require('fs-extra'); + +const extractObj = (obj = {}, keys = []) => { + const ret = {}; + keys.forEach(key => ret[key] = obj[key]); + return ret; +}; + +const simpily = (dsl) => { + const {cells = []} = dsl; + return { + cells: cells.map(cell => { + if(cell.shape === 'edge') { + return extractObj(cell, ['id', 'shape', 'source', 'target']); + } else { + return extractObj(cell, ['id', 'shape', 'data']); + } + }) + }; +}; + +const setup = async (dsl, logicBasePath) => { + const simpilifiedDSL = simpily(dsl); + const dslFilePath = path.join(logicBasePath, 'dsl.json'); + await fs.writeFile(dslFilePath, JSON.stringify(simpilifiedDSL, null, 2)); +}; + +module.exports = setup; diff --git a/packages/cli/src/cmd/dev/template/index.js b/packages/cli/src/cmd/dev/template/index.js new file mode 100644 index 0000000..3f60d68 --- /dev/null +++ b/packages/cli/src/cmd/dev/template/index.js @@ -0,0 +1,6 @@ +import Logic from './logic'; +import dsl from './dsl.json'; + +const logic = new Logic({dsl}); + +export default logic; diff --git a/packages/cli/src/cmd/dev/template/logic.js b/packages/cli/src/cmd/dev/template/logic.js new file mode 100644 index 0000000..e0a93b0 --- /dev/null +++ b/packages/cli/src/cmd/dev/template/logic.js @@ -0,0 +1,103 @@ +import nodeFns from './nodeFns'; + +class Context { + + constructor(opts) { + this.curNode = null; + } + + transitionToNode(node) { + this.curNode = node; + } + + getConfig() { + + } + +} + +const SHAPES = { + START: 'imove-start', + BRANCH: 'imove-branch', + BEHAVIOR: 'imove-behavior' +} + +class Logic { + + constructor(opts = {}) { + this.ctx = null; + this.dsl = opts.dsl; + } + + get cells() { + return this.dsl.cells; + } + + get nodes() { + return this.cells.filter(cell => cell.shape !== 'edge'); + } + + get startNodes() { + return this.cells.filter(cell => cell.shape === SHAPES.START); + } + + get edges() { + return this.cells.filter(cell => cell.shape === 'edge'); + } + + _getStartNode(trigger) { + for(const cell of this.startNodes) { + if(cell.data.trigger === trigger) { + return cell; + } + } + } + + _getNextNode(curNode, lastRet) { + for(const edge of this.edges) { + let isMatched = edge.source.cell === curNode.id; + // NOTE: if it is a imove-branch node, each port's condition should be tested whether it is matched + if(curNode.shape === SHAPES.BRANCH) { + let matchedPort = ''; + const {ports} = curNode.data; + for(const key in ports) { + const {condition} = ports[key]; + const ret = new Function('ctx', 'return ' + condition)(this.ctx); + if(ret === lastRet) { + matchedPort = key; + break; + } + } + isMatched = isMatched && edge.source.port === matchedPort; + } + if(isMatched) { + const nextNodeId = edge.target.cell; + return this.nodes.find(item => item.id === nextNodeId); + } + } + } + + _initCtx(data) { + + } + + _prepareCtx() { + + } + + async invoke(trigger, data) { + let curNode = this._getStartNode(trigger); + if(!curNode) { + return Promise.reject(`Invoke failed, because no logic-start named ${trigger} found!`); + } + this._initCtx(data); + while(curNode) { + this._prepareCtx(); + const fn = nodeFns[curNode.id]; + const ret = await fn(this.ctx); + curNode = this._getNextNode(curNode, ret); + } + } +} + +module.exports = Logic; diff --git a/packages/cli/src/utils/server/index.js b/packages/cli/src/utils/server/index.js new file mode 100644 index 0000000..a72a4c4 --- /dev/null +++ b/packages/cli/src/utils/server/index.js @@ -0,0 +1,23 @@ +const http = require('http'); +const cors = require('cors'); +const express = require('express'); +const bodyParser = require('body-parser'); + +const createServer = (port = 3456) => { + const app = express(); + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({extended: false})); + app.use((req, res, next) => { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); + }); + app.listen(port, () => { + console.log(`server starts successfully at ${port}!`); + }); + return app; +}; + +module.exports = { + createServer +};