Browse Source

feat: add @imove/cli

master
smallstonesk 4 years ago
parent
commit
d662f800c6
  1. 1
      packages/cli/.npmignore
  2. 1
      packages/cli/README.md
  3. 13
      packages/cli/index.js
  4. 29
      packages/cli/package.json
  5. 40
      packages/cli/src/cmd/dev/extractCodes.js
  6. 38
      packages/cli/src/cmd/dev/index.js
  7. 29
      packages/cli/src/cmd/dev/simplifyDSL.js
  8. 6
      packages/cli/src/cmd/dev/template/index.js
  9. 103
      packages/cli/src/cmd/dev/template/logic.js
  10. 23
      packages/cli/src/utils/server/index.js

1
packages/cli/.npmignore

@ -0,0 +1 @@
node_modules

1
packages/cli/README.md

@ -0,0 +1 @@
## iMove-cli

13
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')();
}

29
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"
}
}

40
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;

38
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();
};

29
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;

6
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;

103
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;

23
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
};
Loading…
Cancel
Save