├── .yarnrc ├── .gitignore ├── .babelrc ├── .script ├── prebuild.sh ├── mapPackage.js ├── getPackage.sh ├── buildManifest.js └── generateI18n.js ├── modules └── index.js ├── Manager.js ├── utils ├── createExternalAlias.js └── index.js ├── .eslintrc ├── README.md ├── lib ├── index.js ├── step.js └── parse.js ├── CHANGELOG.md ├── server.prod.js ├── package.json ├── webpack.dev.config.js ├── webpack.production.config.js ├── server.js └── bin └── codingIde.js /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org/" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.bak 4 | .idea 5 | packages 6 | dist 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", {"modules": false}], 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /.script/prebuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ ${PACKAGE_DIR} ]; then 3 | cd $PACKAGE_DIR && yarn 4 | fi 5 | -------------------------------------------------------------------------------- /modules/index.js: -------------------------------------------------------------------------------- 1 | const { CodingSDK } = window; 2 | 3 | 4 | const modules = new CodingSDK({}).modules; 5 | 6 | 7 | module.exports = modules; 8 | -------------------------------------------------------------------------------- /Manager.js: -------------------------------------------------------------------------------- 1 | import { mobx } from './modules'; 2 | 3 | export default class { 4 | pluginWillMount() { 5 | 6 | } 7 | pluginWillUnmount() { 8 | 9 | } 10 | autoRun() { 11 | 12 | } 13 | // 60fs doing something 14 | // render() { 15 | // } 16 | } 17 | -------------------------------------------------------------------------------- /.script/mapPackage.js: -------------------------------------------------------------------------------- 1 | const mapPackage = (config) => { 2 | const newPackage = {}; 3 | const keyValue = ['name', 'version', 'description', 'author', 'displayName'] 4 | newPackage.meta = keyValue.reduce((p, v) => { 5 | p[v] = config.codingIdePackage[v] || config[v] || '' 6 | return p 7 | }, {}) 8 | newPackage.codingIdePackage = Object.assign(config.codingIdePackage, newPackage.meta) 9 | return newPackage 10 | } 11 | 12 | module.exports = mapPackage; 13 | -------------------------------------------------------------------------------- /.script/getPackage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ ${PACKAGE_DIR} ]; then 3 | echo "pluginUrl:"${PACKAGE_DIR} 4 | echo "pluginDir: node_modules/codingIdePlugin/${PACKAGE_DIR}" 5 | echo "===remove origin====" 6 | if [ -d "node_modules/codingIdePlugin" ]; then 7 | rm -rf node_modules/codingIdePlugin 8 | fi 9 | echo "===git clone====" 10 | git clone ${PACKAGE_DIR} node_modules/codingIdePlugin 11 | echo "===install package dependency====" 12 | cd node_modules/codingIdePlugin && yarn install 13 | echo "===start build package====" 14 | fi 15 | -------------------------------------------------------------------------------- /utils/createExternalAlias.js: -------------------------------------------------------------------------------- 1 | function createExternalAlias(map) { 2 | return (context, request, callback) => { 3 | if (map[request]) { 4 | return callback(null, `root ${map[request].replace(/\//g, '.')}`); 5 | } 6 | return callback(); 7 | }; 8 | } 9 | 10 | const generalExtenalAlias = createExternalAlias({ 11 | mobx: 'lib/mobx', 12 | 'react-dom': 'lib/reactDom', 13 | 'mobx-react': 'lib/mobxReact', 14 | react: 'lib/react', 15 | }); 16 | 17 | exports.createExternalAlias = createExternalAlias; 18 | // // some general used packages updading 19 | exports.generalExtenalAlias = generalExtenalAlias; 20 | -------------------------------------------------------------------------------- /.script/buildManifest.js: -------------------------------------------------------------------------------- 1 | console.log("start generate manifest") 2 | const fs = require('fs'); 3 | 4 | const buildEntryFromEnv = process.env.PACKAGE_DIR; 5 | const VERSION = process.env.VERSION 6 | 7 | const packageConfig = require(`${buildEntryFromEnv}/package.json`); 8 | const mapPackage = require('./mapPackage'); 9 | 10 | packageConfig.codingIdePackage.version = VERSION || packageConfig.codingIdePackage.version || packageConfig.version 11 | const version = packageConfig.codingIdePackage.version 12 | const newPackage = mapPackage(packageConfig) 13 | fs.writeFile(`dist/${version}/manifest.json`, JSON.stringify(newPackage, null, 4), 14 | function(err) { 15 | if(err) { 16 | return console.log(err); 17 | } 18 | console.log("generate manifest json success"); 19 | }); 20 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | # We use _ to define private variables and methods in clases 5 | "no-underscore-dangle": 0, 6 | # This seems to be buggy we don't want eslint to check this 7 | "import/no-extraneous-dependencies": 0, 8 | # This is a depricated rule. So we turned off it. 9 | "react/require-extension": 0, 10 | # We can write JSX in anyfile we want. 11 | "react/jsx-filename-extension": 0, 12 | # We don't like this rule. 13 | "arrow-body-style": 0, 14 | # We don't like this rule. We write arrow functions only when we needed. 15 | "prefer-arrow-callback": 0, 16 | # We don't need to write function names always. 17 | "func-names": 0, 18 | # propTypes can be object 19 | "react/forbid-prop-types": 0, 20 | }, 21 | "globals": { 22 | window: false, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | const { CodingSDK } = window; 2 | 3 | // registery appRegistry 4 | export const appRegistry = (obj, callback) => { 5 | window.codingPackageJsonp(obj); 6 | if (callback) { 7 | callback(); 8 | } 9 | }; 10 | 11 | export default class { 12 | constructor(options) { 13 | this.sdk = new CodingSDK(options) || ''; 14 | this.inializeData = this.sdk.getData() || {}; 15 | } 16 | 17 | get injectComponent() { 18 | return this.sdk.injectComponent; 19 | } 20 | 21 | get request() { 22 | return this.sdk.utils.request; 23 | } 24 | get i18n() { 25 | const i18n = this.sdk.i18n.i18nComponent; 26 | i18n.get = this.sdk.i18n.getCache; 27 | i18n.language = this.sdk.language; 28 | return i18n; 29 | } 30 | get sdk() { 31 | return this._sdk; 32 | } 33 | set sdk(sdk) { 34 | this._sdk = sdk; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WebIDE-Plugin-SDK 2 | - build your plugin 3 | - offer sdk apis for coding webide plugins 4 | - dev your apps with remote hotReload 5 | 6 | ## dev plugin with main project 7 | config PACKAGE_SERVER env when running main project 8 | run yarn run dev in main project 9 | 10 | ## dev sdk with plugin 11 | link the project to the plugin node modules 12 | 13 | ## build 14 | you can use this sdk to build the plugin to required format 15 | ```PACKAGE_DIR=ssh://XXX.git yarn run build``` 16 | output to the ./dist 17 | ``` 18 | main 19 | ├── dist 20 | │   ├── 0.0.1-alpha.04 // 版本号 21 | │   ├── ├── index.js // jsbundle 22 | │   ├── ├── manifest.json // 插件描述 23 | │   └── ... // 其他文件 24 | └── 25 | ``` 26 | 27 | ## sdk api 28 | 29 | ## todo 30 | - [ ] improve basic sdks 31 | - [ ] build in local env 32 | - [ ] readme 33 | - [ ] tests 34 | 35 | 36 | ## changelog 37 | 38 | * [changelog](https://github.com/Coding/WebIDE-Plugin-SDK/blob/develop/CHANGELOG.md) -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const parse = require('./parse'); 2 | 3 | /** 4 | * Converts a string to command line args, in particular 5 | */ 6 | function stringToArgs(string) { 7 | const args = []; 8 | 9 | const parts = string.split(' '); 10 | const length = parts.length; 11 | let i = 0; 12 | let open = false; 13 | let grouped = ''; 14 | let lead = ''; 15 | 16 | for (; i < length; i++) { 17 | lead = parts[i].substring(0, 1); 18 | if (lead === '"' || lead === '\'') { 19 | open = lead; 20 | grouped = parts[i].substring(1); 21 | } else if (open && parts[i].slice(-1) === open) { 22 | open = false; 23 | grouped += ` ${parts[i].slice(0, -1)}`; 24 | args.push(grouped); 25 | } else if (open) { 26 | grouped += ` ${parts[i]}`; 27 | } else { 28 | args.push(parts[i]); 29 | } 30 | } 31 | 32 | return args; 33 | } 34 | 35 | module.exports = { 36 | parse(argv) { 37 | if (typeof argv === 'string') { 38 | argv = stringToArgs(argv); 39 | } 40 | return parse(argv); 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 更新日志 4 | toc: false 5 | timeline: true 6 | --- 7 | # Changelog 8 | All notable changes to this project will be documented in this file. 9 | 10 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 11 | and this project adheres to [Semantic Versioning](http://semver.org/). 12 | 13 | ## 1.1.3 14 | 15 | ADD: 16 | - 提供 extenalAlias 与 generalExtenalAlias 支持,一些非常通用的库运行时自动转换 17 | 18 | ## 1.1.1 - 1.1.2 19 | FIX: 20 | - 修改 npm script,更新编译方式 21 | 22 | ## 1.1.0-alpha.04 23 | ADD: 24 | - 插件 dev socket 支持传递插件信息区分package 25 | - 插件 更新编译方法,插件更新sdk形式 26 | - 增加 socket data区分 27 | - 增加自 link 脚本 28 | 29 | ## 1.1.0-alpha.03 30 | ADD: 31 | - 插件 dev socket 支持传递插件信息区分package 32 | - 插件 33 | 34 | 35 | ## 1.1.0-alpha.01 36 | ADD: 37 | - 增加 Manager 生命周期继承类,提供默认的空实现 38 | 39 | 40 | ## 1.0.0-alpha.04 41 | ADD: 42 | - 用户插件根目录下的 `config/webpack.dev.config.js` 以及 `config/webpack.production.config.js` 会被 merge 进 SDK 内的主配置里 43 | 44 | ## 1.0.0-alpha.03 45 | ADD: 46 | - 增加从环境变量获取插件并编译方法 47 | 48 | 49 | ## 1.0.0-alpha.02 50 | ADD: 51 | - webpack 增加 externals 配置,使得在 api 设计不够完善的时候可以像 import 主项目的元素一样调用 window.app下的文件,对现有方案做一个 fallback 52 | 53 | ## 1.0.0-alpha.01 54 | REMOVE: 55 | - 去除 redux 内嵌支持 56 | - 提供 modules 运行时加载所需要的基础类库 57 | 58 | ## 0.0.1-alpha.10 59 | ADD: 60 | - 增加 i18n 支持 61 | 62 | ## 0.0.1-alpha.09 63 | FIXED: 64 | - 修改 fetchPackage 逻辑 65 | 66 | ## 0.0.1-alpha.08 67 | FIXED: 68 | - 修改 fetchPackage 逻辑 69 | 70 | ## 0.0.1-alpha.07 71 | FIXED: 72 | - 修改 fetchPackage 的 git pull后参数 73 | 74 | ## 0.0.1-alpha.06 75 | FIXED: 76 | - 修改 mapPackage 77 | 78 | ## 0.0.1-alpha.05 79 | ADDED: 80 | - injectComponent 支持 81 | 82 | ## 0.0.1 83 | 84 | ### Main 85 | ADDED: 增加基础的sdk与编译逻辑 86 | -------------------------------------------------------------------------------- /server.prod.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const app = express(); 4 | const bodyParser = require('body-parser'); 5 | const cors = require('cors'); 6 | const server = require('http').createServer(app); 7 | const mapPackage = require('./.script/mapPackage'); 8 | 9 | const packagePath = path.resolve(process.env.PACKAGE_DIR, `dist/${process.env.VERSION}/manifest.json`); 10 | const codingIdePackage = require(packagePath); 11 | const mappedPackage = mapPackage(codingIdePackage); 12 | const PORT = process.env.PORT || 4000; 13 | 14 | const corsOptions = { 15 | origin: 'http://localhost:8060', 16 | optionsSuccessStatus: 200, 17 | credentials: true, 18 | }; 19 | const codingPackage = mappedPackage.codingIdePackage; 20 | 21 | 22 | app.use(cors(corsOptions)); 23 | app.use(bodyParser.json()); 24 | app.use((req, res, next) => { 25 | if (process.env.VERSION === 'platform') { 26 | res.set( 27 | 'Content-Type', 'application/vnd.coding.v2+json;charset=UTF-8' 28 | ); 29 | } 30 | return next(); 31 | }); 32 | 33 | 34 | app.get('/', (req, res) => { 35 | res.send('it works'); 36 | }); 37 | 38 | 39 | app.get('/packages', (req, res) => { 40 | res.json([{ 41 | author: codingPackage.author || '', 42 | description: codingPackage.description, 43 | displayName: codingPackage.displayName, 44 | enabled: true, 45 | id: 0, 46 | name: codingPackage.name, 47 | requirement: 'Required', 48 | status: 'Available', 49 | version: codingPackage.version, 50 | loadType: codingPackage.loadType, 51 | }]); 52 | }); 53 | 54 | app.get('/packages/:pkgName/:version/manifest.json', (req, res) => { 55 | res.json(mappedPackage); 56 | }); 57 | 58 | app.get('/packages/:pkgName/:version/index.js', (req, res) => { 59 | res.sendFile(`${__dirname}/dist/${req.params.version}/index.js`); 60 | }); 61 | 62 | server.listen(PORT, () => { 63 | console.log(`plugin script folder served at /packages/${codingPackage.name}/${codingPackage.version}`); 64 | console.log(`plugin list api served at localhost:${PORT}/packages`); 65 | }); 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebIDE-Plugin-SDK", 3 | "version": "1.1.6", 4 | "description": "WebIDE-Plugin-SDK for Coding WebIDE", 5 | "repository": { 6 | "url": "git@github.com:Coding/WebIDE-Plugin-SDK.git" 7 | }, 8 | "license": "ISC", 9 | "author": "candy ", 10 | "main": "server.js", 11 | "bin": { 12 | "coding-ide": "bin/codingIde.js" 13 | }, 14 | "scripts": { 15 | "linkSDK": "ln -s $(pwd) ${PROJECT}/node_modules/coding-webide-sdk" 16 | }, 17 | "devDependencies": { 18 | "babel-cli": "^6.14.0", 19 | "babel-core": "^6.23.1", 20 | "babel-eslint": "^6.1.2", 21 | "babel-loader": "^6.2.5", 22 | "babel-plugin-transform-runtime": "^6.15.0", 23 | "babel-polyfill": "^6.23.0", 24 | "babel-preset-react": "^6.23.0", 25 | "babel-preset-stage-0": "^6.22.0", 26 | "chai": "^3.5.0", 27 | "css-loader": "^0.26.1", 28 | "enzyme": "^2.2.0", 29 | "eslint": "^3.6.0", 30 | "eslint-config-airbnb": "^12.0.0", 31 | "eslint-plugin-import": "^1.16.0", 32 | "eslint-plugin-jsx-a11y": "^2.2.2", 33 | "eslint-plugin-react": "^6.3.0", 34 | "express": "^4.14.0", 35 | "extract-text-webpack-plugin": "^2.0.0-rc.3", 36 | "git-url-parse": "^6.0.1", 37 | "jsdom": "^9.5.0", 38 | "mocha": "^3.0.2", 39 | "sinon": "^1.17.6", 40 | "stylus": "^0.54.5", 41 | "stylus-loader": "^2.5.0", 42 | "webpack": "^2.2.1", 43 | "webpack-cleanup-plugin": "^0.4.1", 44 | "webpack-dev-middleware": "^1.10.1", 45 | "webpack-dev-server": "^2.4.1", 46 | "webpack-hot-middleware": "^2.17.0" 47 | }, 48 | "peerDependencies": { 49 | "react": "^0.14.7 || ^15.0.0" 50 | }, 51 | "dependencies": { 52 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 53 | "babel-preset-env": "^1.1.8", 54 | "babel-runtime": "^6.11.6", 55 | "body-parser": "^1.17.2", 56 | "cors": "^2.8.1", 57 | "cross-env": "^5.0.5", 58 | "nodemon": "^1.11.0", 59 | "rimraf": "^2.6.1", 60 | "socket.io": "^2.0.4", 61 | "style-loader": "^0.13.2", 62 | "webpack": "^2.2.1", 63 | "webpack-cleanup-plugin": "^0.4.1", 64 | "webpack-merge": "^4.1.0" 65 | }, 66 | "engines": { 67 | "npm": "^3.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const WebpackCleanupPlugin = require('webpack-cleanup-plugin'); 5 | const merge = require('webpack-merge'); 6 | 7 | const { generalExtenalAlias } = require('./utils/createExternalAlias'); 8 | 9 | const defaultConfig = { 10 | entry: './src', 11 | output: { 12 | path: path.join(__dirname, 'dist', ''), 13 | filename: 'index.js', 14 | }, 15 | resolve: { 16 | extensions: ['*', '.js', '.jsx'], 17 | modules: [ 18 | 'src/', 19 | 'node_modules', 20 | ], 21 | alias: { 22 | home: path.join(process.env.PACKAGE_DIR, 'src'), 23 | }, 24 | }, 25 | resolveLoader: { 26 | modules: [path.resolve(__dirname, 'loaders/'), 'node_modules'], 27 | }, 28 | module: { 29 | rules: [ 30 | { test: /\.jsx?$/, use: ['babel-loader'] }, 31 | { test: /\.styl$/, 32 | use: [ 33 | 'style-loader', 34 | 'css-loader', 35 | 'stylus-loader', 36 | ], 37 | }, 38 | { 39 | test: /\.css$/, 40 | use: ['style-loader', 'css-loader'], 41 | }, 42 | ], 43 | }, 44 | plugins: [ 45 | new WebpackCleanupPlugin(), 46 | new webpack.DefinePlugin({ 47 | 'process.env': { 48 | NODE_ENV: '"production"', 49 | }, 50 | __DEV__: true, 51 | }), 52 | new ExtractTextPlugin({ 53 | disable: false, 54 | allChunks: true, 55 | }), 56 | ], 57 | externals: [ 58 | (context, request, callback) => { 59 | if (/^app\/.+/.test(request) || /^lib\/.+/.test(request)) { 60 | const newRequest = request 61 | .replace(/\//g, '.') 62 | .replace(/-(.)/g, (__, m) => m.toUpperCase()); 63 | return callback(null, `root ${newRequest}`); 64 | } 65 | callback(); 66 | }, 67 | generalExtenalAlias, 68 | ], 69 | 70 | }; 71 | 72 | let userConfig = {}; 73 | try { 74 | userConfig = require(`${process.env.PACKAGE_DIR}/config/webpack.dev.config.js`); 75 | } catch (err) { 76 | console.log(err); 77 | } 78 | 79 | const protectedProps = [ 80 | 'entry', 81 | 'output', 82 | 'resolve', 83 | 'resolveLoader', 84 | ]; 85 | 86 | const merged = merge({ 87 | customizeObject: (a, b, key) => { 88 | if (protectedProps.includes(key)) return a; 89 | return undefined; 90 | }, 91 | })(defaultConfig, userConfig); 92 | 93 | module.exports = merged; 94 | -------------------------------------------------------------------------------- /lib/step.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {function} descriptionHandler - will be passed the description message 3 | * @param {function} successHandler - will be passed the success message / or the return value of success callback 4 | * @param {function} errorHandler - will be passed the error message / or the return value of error callback 5 | * @returns {function} step - step function, see below 6 | */ 7 | function stepFactory({ 8 | descriptionHandler = console.log, 9 | successHandler = console.log, 10 | errorHandler = console.error, 11 | } = {}) { 12 | /** 13 | * step function has the syntax of 14 | * ``` 15 | * step(description, operation) 16 | * .success(successMsg|successCallback) 17 | * .error(errorMsg|errorCallback) 18 | * ``` 19 | * 20 | * step is best used with `await step(...)` 21 | * it keeps a internal `allSuccess` state. 22 | * if any step fails, subsequential steps won't be executed 23 | * 24 | * @param {OperationCallback} operation - the operation that you wanna take at this step. 25 | * 26 | * The operation callback: 27 | * @callback OperationCallback 28 | * @returns {Boolean|Promise} 29 | * a boolean indicate the success/error state of this operation 30 | * if it returns a promise, then the promise should resolve to boolean at final step 31 | * if error is throw from the promise, it's seen as an error state 32 | */ 33 | function step(description, fn) { 34 | let successCallback, 35 | errorCallback; 36 | if (!step.allSuccess) { 37 | return ({ 38 | success() { return this; }, 39 | error() { return this; }, 40 | }); 41 | } 42 | descriptionHandler(description); 43 | const promise = Promise.resolve(fn()) 44 | .then((isSuccess) => { 45 | console.log('isSuccess', isSuccess); 46 | if (!isSuccess) throw Error('error'); 47 | successCallback && successHandler(successCallback()); 48 | }) 49 | .catch((err) => { 50 | step.allSuccess = false; 51 | errorCallback && errorHandler(errorCallback()); 52 | }); 53 | 54 | promise.success = function (cb) { 55 | successCallback = typeof cb === 'function' ? cb : (() => cb); 56 | return this; 57 | }; 58 | promise.error = function (cb) { 59 | errorCallback = typeof cb === 'function' ? cb : (() => cb); 60 | return this; 61 | }; 62 | 63 | return promise; 64 | } 65 | 66 | step.allSuccess = true; 67 | return step; 68 | } 69 | 70 | module.exports = stepFactory; 71 | -------------------------------------------------------------------------------- /.script/generateI18n.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const fromJS = require('immutable').fromJS 3 | const i18nRegex = /(?:i18n`(.+?):=)(.+?)(?:`)/gm 4 | const i18nRegexPer = /(?:i18n`(.+?):=)(.+?)(?:`)/ 5 | 6 | // 遍历读取所有的js和jsx文件,当看到有文件内容有特征内容,则替换内容,并且过程中 有一个side effect 在指定的目录生成json文件 7 | 8 | function generateI18n(root) { 9 | const root_path = process.argv[2] || 'app' 10 | const i18nTargetRoot = process.env.I18nTargetRoot || 'app/i18n' 11 | 12 | 13 | 14 | console.log('search i18n shape in folder : ' + root_path) 15 | 16 | // 要使用此工具需保证 i18目录里有 index.json shape ['en_US', 'zh_CN'] 告知当前的语言库 17 | const i18nFolders = require(`${i18nTargetRoot}/index.json`) 18 | console.log('found folders ' + i18nFolders) 19 | 20 | const files = fs.readdirSync(root); 21 | files.forEach(file => { 22 | var pathname = root+'/'+file 23 | , stat = fs.lstatSync(pathname); 24 | if (stat.isDirectory()) { 25 | generateI18n(pathname) 26 | } 27 | if (['js', 'jsx'].includes(file.split('.').pop())) { 28 | const readablePathname = pathname.replace(root,'.') 29 | const data = fs.readFileSync(pathname, "utf-8") 30 | if (i18nRegex.test(data)) { 31 | const newData = data.replace(i18nRegex, (matchable) => { 32 | console.log(`find i18n shape ${matchable}, generating...`) 33 | return generate(matchable) 34 | }) 35 | fs.writeFile(pathname, newData, (err) => { 36 | if (err) throw err 37 | }) 38 | } 39 | } 40 | }) 41 | } 42 | 43 | function generate(matchable) { 44 | const i18nTargetRoot = process.env.I18nTargetRoot || 'app/i18n' 45 | const i18nFolders = require(`${i18nTargetRoot}/index.json`) 46 | const perMatchableArray = matchable.match(i18nRegexPer) 47 | const fileKey = perMatchableArray[1].split('$')[0] 48 | const value = perMatchableArray[2] 49 | const folderArray = fileKey.split('.') 50 | const folder = folderArray.shift() 51 | 52 | i18nFolders.forEach(i18nFolder => { 53 | const jsonFileName = `${i18nTargetRoot}/${i18nFolder}/${folder}.json` 54 | const i18nFiles = fs.readdirSync(`${i18nTargetRoot}/${i18nFolder}`); 55 | if (!i18nFiles.includes(`${folder}.json`)) { 56 | console.log(`create a new File ${jsonFileName}`) 57 | fs.writeFileSync(jsonFileName, '{}', "utf-8") 58 | } 59 | const json = fs.readFileSync(jsonFileName, "utf-8") 60 | const tmp = fromJS(JSON.parse(json)); 61 | if (!tmp.getIn(folderArray, '')) { 62 | const nextData = tmp.setIn(folderArray, value).toJS() 63 | fs.writeFileSync(jsonFileName, JSON.stringify(nextData, undefined, 3), "utf-8") 64 | console.log(`generate in ${jsonFileName} success`) 65 | } else { 66 | console.log(`${i18nFolder} already has value, skip`) 67 | } 68 | }) 69 | return matchable.replace(/:=.+/, '`'); 70 | } 71 | 72 | module.exports = generateI18n 73 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const WebpackCleanupPlugin = require('webpack-cleanup-plugin'); 5 | const merge = require('webpack-merge'); 6 | 7 | const { generalExtenalAlias } = require('./utils/createExternalAlias'); 8 | 9 | const buildEntryFromEnv = process.env.PACKAGE_DIR; 10 | if (buildEntryFromEnv) { 11 | console.log(`get package from ${buildEntryFromEnv}`); 12 | } 13 | const config = require(buildEntryFromEnv ? `${buildEntryFromEnv}/package.json` : 'codingIdePlugin/package.json'); 14 | 15 | const version = process.env.VERSION || config.codingIdePackage.version || config.version; 16 | 17 | const defaultConfig = { 18 | entry: buildEntryFromEnv ? `${buildEntryFromEnv}/src` : './node_modules/codingIdePlugin/src', 19 | output: { 20 | path: path.join(buildEntryFromEnv, 'dist', version), 21 | filename: 'index.js', 22 | }, 23 | resolve: { 24 | extensions: ['.js', '.jsx'], 25 | modules: [ 26 | 'src/', 27 | './node_modules', 28 | ], 29 | alias: { 30 | home: path.join(process.env.PACKAGE_DIR, 'src'), 31 | }, 32 | }, 33 | module: { 34 | rules: [ 35 | { test: /\.jsx?$/, use: ['babel-loader'] }, 36 | { test: /\.styl$/, 37 | use: [ 38 | 'style-loader', 39 | 'css-loader', 40 | 'stylus-loader', 41 | ], 42 | }, 43 | { 44 | test: /\.css$/, 45 | use: ['style-loader', 'css-loader'], 46 | }, 47 | ], 48 | }, 49 | plugins: [ 50 | new WebpackCleanupPlugin(), 51 | new webpack.DefinePlugin({ 52 | 'process.env': { 53 | NODE_ENV: '"production"', 54 | }, 55 | __DEV__: false, 56 | }), 57 | new webpack.optimize.UglifyJsPlugin({ 58 | compress: { 59 | warnings: false, 60 | screw_ie8: true, 61 | drop_debugger: true, 62 | }, 63 | }), 64 | new ExtractTextPlugin({ 65 | disable: false, 66 | allChunks: true, 67 | }), 68 | ], 69 | externals: [ 70 | (context, request, callback) => { 71 | if (request === 'react') request = 'lib/react' 72 | if (/^app\/.+/.test(request) || /^lib\/.+/.test(request)) { 73 | const newRequest = request 74 | .replace(/\//g, '.') 75 | .replace(/-(.)/g, (__, m) => m.toUpperCase()); 76 | return callback(null, `root ${newRequest}`); 77 | } 78 | callback(); 79 | }, 80 | generalExtenalAlias, 81 | ], 82 | }; 83 | 84 | let userConfig = {}; 85 | try { 86 | userConfig = require(`${buildEntryFromEnv}/config/webpack.production.config.js`); 87 | } catch (err) { 88 | console.log('no user config' + err); 89 | } 90 | 91 | const protectedProps = [ 92 | 'entry', 93 | 'output', 94 | 'resolve', 95 | 'resolveLoader', 96 | ]; 97 | 98 | const merged = merge({ 99 | customizeObject: (a, b, key) => { 100 | if (protectedProps.includes(key)) return a; 101 | return undefined; 102 | } 103 | })(defaultConfig, userConfig); 104 | 105 | module.exports = merged; 106 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const app = express(); 4 | const bodyParser = require('body-parser'); 5 | const cors = require('cors'); 6 | const server = require('http').createServer(app); 7 | const io = require('socket.io').listen(server); 8 | const webpackConfig = require('./webpack.dev.config'); 9 | const compiler = require('webpack')(webpackConfig); 10 | const mapPackage = require('./.script/mapPackage'); 11 | 12 | const packagePath = `${process.env.PACKAGE_DIR}/package.json` || 13 | '../../package.json'; 14 | 15 | const codingIdePackage = require(packagePath); 16 | const mappedPackage = mapPackage(codingIdePackage); 17 | 18 | io.on('connection', (socket) => { 19 | console.log(`hotreload socket server started ,connected id ${socket.id}`); 20 | socket.on('change', () => { 21 | socket.broadcast.emit('onchange'); 22 | }); 23 | }); 24 | 25 | const PORT = process.env.PORT || 4000; 26 | 27 | const corsOptions = { 28 | origin: ['http://localhost:8060','http://ide.test:8060'], 29 | optionsSuccessStatus: 200, 30 | credentials: true, 31 | }; 32 | const codingPackage = mappedPackage.codingIdePackage; 33 | 34 | 35 | app.use(cors(corsOptions)); 36 | app.use(bodyParser.json()); 37 | app.use((req, res, next) => { 38 | if (process.env.VERSION === 'platform') { 39 | res.set( 40 | 'Content-Type', 'application/vnd.coding.v2+json;charset=UTF-8' 41 | ); 42 | } 43 | return next(); 44 | }); 45 | 46 | 47 | app.get('/', (req, res) => { 48 | res.send('it works'); 49 | }); 50 | 51 | 52 | app.get('/packages', (req, res) => { 53 | res.json([{ 54 | author: codingPackage.author || '', 55 | description: codingPackage.description, 56 | displayName: codingPackage.displayName, 57 | enabled: true, 58 | id: 0, 59 | name: codingPackage.name, 60 | requirement: 'Required', 61 | status: 'Available', 62 | version: codingPackage.version, 63 | loadType: codingPackage.loadType, 64 | }]); 65 | }); 66 | 67 | app.get('/packages/:pkgName/:version/manifest.json', (req, res) => { 68 | res.json(mappedPackage); 69 | }); 70 | 71 | const webpackDevInstance = require('webpack-dev-middleware')(compiler, { 72 | publicPath: `/packages/${codingPackage.name}/${codingPackage.version}`, 73 | headers: { 'Access-Control-Allow-Origin': 'http://ide.test:8060' }, 74 | historyApiFallback: true, 75 | quiet: false, 76 | noInfo: false, 77 | inline: true, 78 | stats: { 79 | color: true, 80 | assets: false, 81 | colors: true, 82 | version: false, 83 | hash: false, 84 | timings: true, 85 | chunks: true, 86 | chunkModules: false, 87 | }, 88 | }); 89 | 90 | app.use(webpackDevInstance); 91 | compiler.watch({}, (err) => { 92 | if (!err) { 93 | console.log('send reload command to frontend'); 94 | io.emit('change', mappedPackage); 95 | } 96 | }); 97 | 98 | server.listen(PORT, () => { 99 | console.log(`plugin script folder served at /packages/${codingPackage.name}/${codingPackage.version}`); 100 | console.log(`plugin list api served at localhost:${PORT}/packages`); 101 | }); 102 | 103 | module.exports = { emitChange: io.emit }; 104 | -------------------------------------------------------------------------------- /bin/codingIde.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const exec = require('child_process').exec; 4 | const path = require('path'); 5 | const stepFactory = require('../lib/step'); 6 | const parse = require('../lib/parse'); 7 | const generateI18n = require('../.script/generateI18n') 8 | 9 | function execPromise(command, options = {}, callback) { 10 | return new Promise((resolve, reject) => { 11 | return exec(command, options, function (err, stdout, stderr) { 12 | if (err) { return reject(err,stdout, stderr); } 13 | if (callback) { 14 | callback(stdout); 15 | } 16 | return resolve(stdout); 17 | } 18 | ); 19 | }); 20 | } 21 | 22 | async function i18n() { 23 | process.env.NODE_ENV = 'development'; 24 | const PACKAGE_DIR = process.env.PACKAGE_DIR || path.resolve(__dirname, '../../../'); 25 | process.env.I18nTargetRoot = process.env.I18nTargetRoot || path.resolve(__dirname, '../../../', 'i18n'); 26 | console.log('PACKAGE_DIR', PACKAGE_DIR) 27 | generateI18n(`${PACKAGE_DIR}/src`) 28 | } 29 | 30 | async function serve() { 31 | const step = stepFactory(); 32 | let stepNum = 1; 33 | 34 | await step(`[${stepNum++}] get current version`, () => { 35 | return execPromise('git describe --always --tags') 36 | .then((stdout) => { 37 | console.log(`current version is ${stdout}`); 38 | process.env.VERSION = stdout.replace(/\s+/, ''); 39 | process.env.PACKAGE_DIR = process.env.PACKAGE_DIR || path.resolve(__dirname, '../../../'); 40 | return true; 41 | }); 42 | }); 43 | await step(`[${stepNum++}] start serve from gist`, () => { 44 | require('../server.prod.js') 45 | }); 46 | } 47 | 48 | 49 | async function start() { 50 | process.env.NODE_ENV = 'development'; 51 | process.env.PACKAGE_DIR = process.env.PACKAGE_DIR || path.resolve(__dirname, '../../../'); 52 | require('../server.js') 53 | } 54 | async function build() { 55 | const step = stepFactory(); 56 | let stepNum = 1; 57 | await step(`[${stepNum++}] get current version`, () => { 58 | return execPromise('git describe --always --tags') 59 | .then((stdout) => { 60 | console.log(`current version is ${stdout}`); 61 | process.env.VERSION = stdout.replace(/\s+/, ''); 62 | return true; 63 | }); 64 | }); 65 | await step(`[${stepNum++}] clean up dist folder`, () => { 66 | return execPromise('node_modules/.bin/rimraf dist/**').then(() => { 67 | console.log('cleared'); 68 | return true; 69 | }); 70 | }); 71 | await step(`[${stepNum++}] build source`, () => { 72 | process.env.PACKAGE_DIR = process.env.PACKAGE_DIR || path.resolve(__dirname, '../../../'); 73 | console.log('current source dir', process.env.PACKAGE_DIR, process.env.VERSION); 74 | return execPromise(`webpack --config ${path.resolve(__dirname, '../', 'webpack.production.config.js')} --progress --profile --colors`) 75 | .then((out) => { 76 | console.log(out); 77 | return true; 78 | }) 79 | .catch((err, stdout, stderr) => { 80 | console.log(err); 81 | return false; 82 | }); 83 | }); 84 | await step(`[${stepNum++}] generate manifest`, () => { 85 | try { 86 | require('../.script/buildManifest'); 87 | return true; 88 | } catch (err) { 89 | throw err; 90 | } 91 | }); 92 | } 93 | 94 | const cli = parse(process.argv); 95 | 96 | 97 | if (cli.args[0] === 'build') { 98 | build(); 99 | } 100 | 101 | if (cli.args[0] === 'start') { 102 | start(); 103 | } 104 | 105 | if (cli.args[0] === 'i18n') { 106 | i18n(); 107 | } 108 | 109 | if (cli.args[0] === 'serve') { 110 | serve(); 111 | } -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | nodemon is a utility for node, and replaces the use of the executable 4 | node. So the user calls `nodemon foo.js` instead. 5 | 6 | nodemon can be run in a number of ways: 7 | 8 | `nodemon` - tries to use package.json#main property to run 9 | `nodemon` - if no package, looks for index.js 10 | `nodemon app.js` - runs app.js 11 | `nodemon --arg app.js --apparg` - eats arg1, and runs app.js with apparg 12 | `nodemon --apparg` - as above, but passes apparg to package.json#main (or 13 | index.js) 14 | `nodemon --debug app.js 15 | 16 | */ 17 | 18 | const fs = require('fs'); 19 | const path = require('path'); 20 | const existsSync = fs.existsSync || path.existsSync; 21 | 22 | module.exports = parse; 23 | 24 | /** 25 | * 26 | * @param {Array} full process arguments, including `node` leading arg 27 | * @return {Object} { options, script, args } 28 | */ 29 | function parse(argv) { 30 | if (typeof argv === 'string') { 31 | argv = argv.split(' '); 32 | } 33 | 34 | const eat = function (i, args) { 35 | if (i <= args.length) { 36 | return args.splice(i + 1, 1).pop(); 37 | } 38 | }; 39 | 40 | const args = argv.slice(2); 41 | let script = null; 42 | const options = { scriptPosition: null }; 43 | 44 | const codingOpt = codingIdeOption.bind(null, options); 45 | let lookForArgs = true; 46 | 47 | // move forward through the arguments 48 | for (let i = 0; i < args.length; i++) { 49 | // if the argument looks like a file, then stop eating 50 | // if (!script) { 51 | // if (args[i] === '.' || existsSync(args[i])) { 52 | // script = args.splice(i, 1).pop(); 53 | 54 | // // we capture the position of the script because we'll reinsert it in 55 | // // the right place in run.js:command (though I'm not sure we should even 56 | // options.scriptPosition = i; 57 | // i--; 58 | // continue; 59 | // } 60 | // } 61 | 62 | if (lookForArgs) { 63 | // respect the standard way of saying: hereafter belongs to my script 64 | if (args[i] === '--') { 65 | args.splice(i, 1); 66 | // cycle back one argument, as we just ate this one up 67 | i--; 68 | 69 | // ignore all further nodemon arguments 70 | lookForArgs = false; 71 | 72 | // move to the next iteration 73 | continue; 74 | } 75 | 76 | if (codingOpt(args[i], eat.bind(null, i, args)) !== false) { 77 | args.splice(i, 1); 78 | // cycle back one argument, as we just ate this one up 79 | i--; 80 | } 81 | } 82 | } 83 | 84 | options.script = script; 85 | options.args = args; 86 | 87 | return options; 88 | } 89 | 90 | 91 | /** 92 | * Given an argument (ie. from process.argv), sets nodemon 93 | * options and can eat up the argument value 94 | * 95 | * @param {Object} options object that will be updated 96 | * @param {Sting} current argument from argv 97 | * @param {Function} the callback to eat up the next argument in argv 98 | * @return {Boolean} false if argument was not a nodemon arg 99 | */ 100 | function codingIdeOption(options, arg, eatNext) { 101 | // line seperation on purpose to help legibility 102 | if (arg === '--help' || arg === '-h' || arg === '-?') { 103 | const help = eatNext(); 104 | options.help = help || true; 105 | } else 106 | 107 | if (arg === '--version' || arg === '-v') { 108 | options.version = true; 109 | } else 110 | 111 | if (arg === '--dump') { 112 | options.dump = true; 113 | } else 114 | 115 | if (arg === '--verbose' || arg === '-V') { 116 | options.verbose = true; 117 | } else 118 | 119 | // Depricated as this is "on" by default 120 | if (arg === '--js') { 121 | options.js = true; 122 | } else 123 | 124 | if (arg === '--quiet' || arg === '-q') { 125 | options.quiet = true; 126 | } else 127 | 128 | if (arg === '--hidden') { // TODO document this flag? 129 | options.hidden = true; 130 | } else 131 | 132 | if (arg === '--config') { 133 | options.configFile = eatNext(); 134 | } else 135 | 136 | if (arg === '--cwd') { 137 | options.cwd = eatNext(); 138 | 139 | // go ahead and change directory. This is primarily for nodemon tools like 140 | // grunt-nodemon - we're doing this early because it will affect where the 141 | // user script is searched for. 142 | process.chdir(path.resolve(options.cwd)); 143 | } else { 144 | // this means we didn't match 145 | return false; 146 | } 147 | } 148 | --------------------------------------------------------------------------------