diff --git a/haxetool/core.js b/haxetool/core.js new file mode 100644 index 0000000..34db546 --- /dev/null +++ b/haxetool/core.js @@ -0,0 +1,67 @@ +const path = require('path'); + + +const BuildSystem = { + HAXE: 'haxe', + OPENFL: 'openfl', + ADOBE_AIR: 'adobe_air', +}; + + +const Platform = { + FLASH: 'flash', + HTML5: 'html5', + LINUX: 'linux', + WINDOWS: 'windows', + ANDROID: 'android', + NEKO: 'neko', +}; + + +class Config { + + constructor(params) { + this._params = []; + this.name = null; + this.main = null; + this.sources = []; + this.assets = []; + this.libs = []; + this.meta = { + title: null, + version: null, + pack: null, + company:null + }; + if (params) { + this.update(params); + } + } + + update(params) { + this._params.push(params); + if (params.name !== undefined) this.name = params.name; + if (params.name !== undefined) this.main = params.main; + const cwd = process.cwd(); + if (params.sources !== undefined) this.sources = this.sources.concat(params.sources.map(item => path.resolve(cwd, item))); + if (params.assets !== undefined) this.assets = this.assets.concat(params.assets.map(item => path.resolve(cwd, item))); + if (params.libs !== undefined) this.libs = this.libs.concat(Array.isArray(params.libs) ? params.libs : Object.entries(params.libs).map(([k, v]) => ({name: k, version: v}))); + if (params.meta !== undefined) this.meta = {...this.meta, ...params.meta}; + } + + branch(params) { + const result = new Config(); + for (const params of this._params) { + result.update(params); + } + result.update(params); + return result; + } +} + + +module.exports = { + BuildSystem: BuildSystem, + Platform: Platform, + Config: Config, +}; \ No newline at end of file diff --git a/haxetool/debug.js b/haxetool/debug.js new file mode 100755 index 0000000..cdd4671 --- /dev/null +++ b/haxetool/debug.js @@ -0,0 +1,81 @@ +const net = require('net'); +const through = require('through2'); +const colors = require('ansi-colors'); +const log = require('fancy-log'); + + +const _colors = { + '[DEBUG]': colors.white, + '[INFO]': colors.cyan, + '[ERROR]': colors.red, + '[WARNING]': colors.yellow, +}; + +const getColor = (line) => { + for (const [tag, color] of Object.entries(_colors)) { + if (line.indexOf(tag) > -1) { + return color; + } + } + return null;// colors.reset; +}; + + +class Debug { + + static log (line, color) { + if (color === undefined) { + color = getColor(line) || colors.white; + } + if (line[0] === '\t') { + console.log(color(line)); + } else { + const result = line.split(' '); + console.log(colors.gray(result.slice(0, 4).join(' ')) + ' ' + color(result.slice(4).join(' '))); + } + }; + + constructor() { + this.host = 'localhost'; + this.port = 6000 + Math.floor(Math.random() * 1000); + } + + macro() { + return [ + `CompilationOption.set('debug.address','${this.host}')`, + `CompilationOption.set('debug.port','${this.port}')`, + ]; + }; + + run() { + const debug = this; + return through.obj(function (file, enc, callback) { + if (this.disabled) { + this.emit('end'); + callback(); + return; + } + let color = colors.white; + const server = net.createServer((socket) => { + socket.on("data", (data) => { + const lines = data.toString().split('\n'); + for (let line of lines) if (line.length > 2) { + const newColor = getColor(line); + if (newColor != null) color = newColor; + Debug.log(line, color); + } + }); + socket.on("close", () => { + socket.destroy(); + server.close(); + this.emit('end'); + callback(); + }); + }); + log(colors.green('[debug]'), colors.cyan('listen on'), colors.magenta(`${debug.host}:${debug.port}`)); + server.listen(debug.port, debug.host); + }) + } +} + +module.exports = Debug; diff --git a/haxetool/haxe.js b/haxetool/haxe.js index b189e57..7b52ee3 100755 --- a/haxetool/haxe.js +++ b/haxetool/haxe.js @@ -77,9 +77,29 @@ class Haxe extends Sdk { } haxelib(args) { - const haxelibBin = this.haxelibBin; - //return exec(this.binPath, [path.basename(haxelibBin)].concat(args).join(' ')); - return exec('.', [haxelibBin].concat(args).join(' ')); + return exec('.', [this.haxelibBin].concat(args).join(' ')); + } + + openfl(command, platform, config, debug=false) { + log(this.tag, colors.cyan('openfl', platform)); + const buildDir = path.join(os.tmpdir(), 'build', config.name); + mkdirp.sync(buildDir); + + const projectTemplate = template(fs.readFileSync(path.resolve(__dirname, '..', 'template/project.xml'))); + const project = projectTemplate({...config, buildDir: buildDir}); + fs.writeFileSync(path.resolve(buildDir, 'project.xml'), project); + + const args = ['-cwd', buildDir, 'run', 'openfl', command, platform]; + if (debug) { + args.push('-debug'); + } + const target = path.resolve(buildDir, platform, 'bin'); + rmdir(target); + return this.haxelib(args).then(() => vfs.src(`${target}/**/*`)); + } + + build(platform, config) { + } install(packages) { @@ -132,108 +152,8 @@ class Haxe extends Sdk { return promise; } - /** - * - * @param params - * @returns {*} - * - * { - * command: 'build', - * platform: 'flash', - * version: '1.0.0', - * lib: {}, - * values: {}, - * macro: [], - * outputFile: 'out.swf', - * debug: false - * } - */ - openfl(params) { - params = Object.assign({ - title: null, - pack: null, - company: null, - version: null, - lib: [], - cp: [], - asset: [], - main: null, - values: {}, - macro: [], - outputFile: null, - debug: false - }, params); - const files = []; - let stream = null; - - const bufferContents = (file, enc, callback) => { - // ToDo: check file not stream - files.push(file); - callback(); - }; - - const endStream = (callback) => { - log(this.tag, colors.cyan(`openfl ${params.command} ${params.platform}`)); - - if (!Array.isArray(params.lib)) { - params.lib = Object.entries(params.lib).map(([k, v]) => ({name: k, version: v.split('@')[0]})); - } - - params.cp = params.cp.map(item => path.resolve(files[0].path, item)); - params.asset = params.asset.map(item => path.resolve(files[0].path, item)); - - const buildDir = path.join(os.tmpdir(), 'build', params.outputFile); - params.buildDir = buildDir; - mkdirp.sync(params.buildDir); - - const projectTemplate = template(fs.readFileSync(path.resolve(__dirname, '..', 'template/project.xml'))); - const project = projectTemplate(params); - fs.writeFileSync(path.resolve(buildDir, 'project.xml'), project); - - const args = ['-cwd', params.buildDir, 'run', 'openfl', params.command, params.platform]; - - if (params.debug) { - args.push('-debug'); - } - const target = `${buildDir}/${params.platform}/bin`; - rmdir(target); - - this.haxelib(args).then(() => { - vfs.src(`${target}/**/*`).pipe(through.obj((file, enc, cb) => { - stream.push(file); - cb(); - }, (cb) => { - callback(); - cb(); - })); - //callback(); - }).catch((error) => { - stream.emit('error', new PluginError({plugin: this.name, message: error})); - callback(); - }); - }; - - return stream = through.obj(bufferContents, endStream); - } - - /** - * - * @param params - * - * { - * platform: 'neko', - * version: '1.0.0', - * values: {}, - * macro: [], - * lib: [], - * src: [], - * main: 'Main.hx', - * outputFile: 'out.n', - * debug: false, - * } - */ - build(params) { + _build(params) { params = Object.assign({ version: null, values: {}, diff --git a/haxetool/project.js b/haxetool/project.js new file mode 100644 index 0000000..acb8477 --- /dev/null +++ b/haxetool/project.js @@ -0,0 +1,219 @@ +const gulp = require('gulp'); +//const concat = require('gulp-concat'); +//const uglify = require('gulp-uglify'); +//const babel = require('gulp-babel'); +//const template = require('gulp-template'); +const Haxe = require('./haxe'); +const FlashPlayer = require('./flashplayer'); +const Neko = require('./neko'); +const Debug = require('./debug'); +const webserver = require('gulp-webserver'); +const run = require('gulp-run'); +const tail = require('./tail'); +//const deb = require('gulp-debian'); +const {BuildSystem, Platform, Config} = require('./core'); +const vfs = require('vinyl-fs'); + + +/** + * + */ +class Builder { + + constructor(buildSystem) { + this.buildSystem = buildSystem; + this.target = 'target'; + } + + prepare() { + return Promise.resolve(); + } + + call(debug) { + throw 'Not Implemented'; + } + + static register(buildSystem, builder) { + Builder.factory[buildSystem] = builder; + } + + static new(buildSystem, config) { + return new Builder.factory[buildSystem](buildSystem, config); + } +} + +Builder.factory = {}; + +/** + * + */ +class HaxeBuilder extends Builder { + + constructor(buildSystem) { + super(buildSystem); + this.haxe = new Haxe(); + } + + prepare() { + return this.haxe.prepare(); + } + + call(platform, config, debug) { + switch (this.buildSystem) { + case BuildSystem.OPENFL: + return this.haxe.openfl('build', platform, config, debug) + .then(result => result.pipe(vfs.dest(`${this.target}/${platform}`))); + } + } +} + +Builder.register(BuildSystem.HAXE, HaxeBuilder); +Builder.register(BuildSystem.OPENFL, HaxeBuilder); + +/** + * + */ +class Runner { + + constructor(platform, name) { + this.platform = platform; + this.name = name; + this.target = 'target'; + } + + prepare() { + return Promise.resolve(); + } + + call(debug) { + throw 'Not Implemented'; + } + + static register(platform, builder) { + Runner.factory[platform] = builder; + } + + static new(platform, name) { + return new Runner.factory[platform](platform, name); + } +} + +Runner.factory = {}; + +/** + * + */ +class FlashRunner extends Runner { + + constructor(platform, name) { + super(platform, name); + this.player = new FlashPlayer(); + } + + prepare() { + return this.player.prepare(); + } + + call(debug) { + return gulp.src(`${this.target}/${this.platform}/${this.name}.swf`) + .pipe(this.player.run(true)) + .pipe(debug.run()); + } +} + +Runner.register(Platform.FLASH, FlashRunner); + +/** + * + */ +class Html5Runner extends Runner { + + call(debug) { + return gulp.src(`${this.target}/${this.platform}`) + .pipe(webserver({ + host: 'localhost', port: 3000, + open: true, + fallback: 'index.html' + })); + } +} + +Runner.register(Platform.HTML5, Html5Runner); + +/** + * + */ +class LinuxRunner extends Runner { + + call(debug) { + return gulp.src(`${this.target}/${this.platform}/${this.name}`) + .pipe(run(`./${this.name}`, {cwd: `target/${this.platform}`, verbosity: 1})) + .pipe(tail(Debug.log)); + } +} + +Runner.register(Platform.LINUX, LinuxRunner); + +/** + * + */ +class NekoRunner extends Runner { + + call(debug) { + return gulp.src(`${this.target}/${this.platform}/${this.name}.n`) + .pipe(new Neko().run()) + .pipe(debug.run()); + } +} + +Runner.register(Platform.NEKO, NekoRunner); + + +/** + * + */ +class Project { + + constructor(buildSystem, platforms, config) { + this.buildSystem = buildSystem; + this.platforms = platforms; + this.config = config; + } + + build(platform) { + const builder = Builder.new(this.buildSystem); + return [ + () => builder.prepare(), + () => builder.call(platform, this.config) + ]; + } + + run(platform) { + const builder = Builder.new(this.buildSystem); + const runner = Runner.new(platform, this.config.name); + const debug = new Debug(); + return [ + () => builder.prepare(), + () => builder.call(platform, this.config, debug), + () => runner.prepare(), + () => runner.call(debug) + ]; + } + + bind(module, gulp /* ToDo: spike */) { + for (const platform of this.platforms) { + module.exports[`${this.config.name}:${platform}:build`] = gulp.series(this.build(platform)); + module.exports[`${this.config.name}:${platform}:run`] = gulp.series(this.run(platform)); + } + return this; + } +} + +Project.BuildSystem = BuildSystem; +Project.Platform = Platform; +Project.Config = Config; +Project.Builder = Builder; +Project.Runner = Runner; + + +module.exports = Project; \ No newline at end of file diff --git a/haxetool/tail.js b/haxetool/tail.js new file mode 100644 index 0000000..b4ddeed --- /dev/null +++ b/haxetool/tail.js @@ -0,0 +1,43 @@ +const through = require('through2'); +const colors = require('ansi-colors'); +const log = require('fancy-log'); + +const TAG = colors.green('[tail]'); + + +const { Writable } = require('stream'); +const { StringDecoder } = require('string_decoder'); + + +class StringWritable extends Writable { + constructor(handler, options) { + super(options); + this.handler = handler; + const state = this._writableState; + this._decoder = new StringDecoder(state.defaultEncoding); + this.data = ''; + } + _write(chunk, encoding, callback) { + if (encoding === 'buffer') { + chunk = this._decoder.write(chunk); + for (const line of chunk.split('\n')) if (line.length) { + this.handler(line); + } + } + this.data += chunk; + callback(); + } + _final(callback) { + this.data += this._decoder.end(); + callback(); + } +} + + +module.exports = (handler) => { + return through.obj(function (file, enc, callback) { + file.contents.pipe(new StringWritable(handler)); + this.push(file); + callback(); + }); +}; diff --git a/index.js b/index.js index 526c489..f2d9145 100644 --- a/index.js +++ b/index.js @@ -4,4 +4,5 @@ module.exports = { FlashPlayer: require('./haxetool/flashplayer'), Neko: require('./haxetool/neko'), AdobeAir: require('./haxetool/adobe_air'), + Project: require('./haxetool/project'), }; \ No newline at end of file diff --git a/package.json b/package.json index 9590471..7a5a70c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gulp-haxetool", - "version": "0.0.4", + "version": "0.0.5", "description": "Haxe tool for gulp", "main": "index.js", "dependencies": { @@ -9,7 +9,9 @@ "fancy-log": "^1.3.2", "fs-extra": "^5.0.0", "got": "^8.3.0", - "gulp": "github:gulpjs/gulp#4.0", + "gulp": "^4.0.0", + "gulp-run": "^1.7.1", + "gulp-webserver": "^0.9.1", "lodash.template": "^4.4.0", "mkdirp": "^0.5.1", "plugin-error": "^1.0.1", diff --git a/template/project.xml b/template/project.xml index b81073a..945d4db 100644 --- a/template/project.xml +++ b/template/project.xml @@ -1,13 +1,13 @@ - - - <% cp.forEach(function(item) { %> + + + <% sources.forEach(function(item) { %> <% }); %> - <% asset.forEach(function(item) { %> + <% assets.forEach(function(item) { %> <% }); %> - <% lib.forEach(function(item) { %> - <% }); %> + <% libs.forEach(function(item) { %> + <% }); %>