diff --git a/.gitignore b/.gitignore
index 2ec2183..ffec2b7 100755
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,7 @@ out/
*.iws
*.ids
*.stackdump
-.idea/
\ No newline at end of file
+.idea/
+config.json
+package-lock.json
+/node_modules
\ No newline at end of file
diff --git a/WORK.md b/WORK.md
new file mode 100644
index 0000000..5e24e5a
--- /dev/null
+++ b/WORK.md
@@ -0,0 +1,25 @@
+* build
+ * gulp
+
+* deploy
+ * capistrano
+
+* ui
+ * login frame
+ * select person frame (autoselect)
+ * game mode frame (single, start server, find server)
+ * game frame
+
+* engine
+ * config
+ * map
+ * tanks
+ * bullets
+ * boxes
+ * map changes
+ * bonuses
+
+* proto
+
+* common
+ * single game
\ No newline at end of file
diff --git a/build/client.js b/build/client.js
new file mode 100755
index 0000000..23b8871
--- /dev/null
+++ b/build/client.js
@@ -0,0 +1,48 @@
+"use strict";
+const gulp = require('gulp');
+const yargs = require('yargs');
+const tail = require('../tasks/tail');
+const Haxe = require('../tasks/haxe');
+const FlashPlayer = require('../tasks/flashplayer');
+const version = require('./version');
+const dateformat = require('dateformat');
+const prepare = require('./prepare');
+const debug = require('../tasks/debug');
+
+
+const build = (params) => function build() {
+ params = params || {};
+ const argv = yargs.argv;
+ const values = {
+ build: dateformat(new Date(), 'yyyy-mm-dd HH:MM:ss'),
+ };
+ let outputFile = 'tankz.swf';
+ if (params.outputFile) {
+ outputFile = params.outputFile;
+ }
+ if (argv.dev) {
+ values.dev = true;
+ if (!values['dev.address']) values['dev.address'] = argv['dev-address'] || 'localhost';
+ if (!values['dev.port']) values['dev.port'] = argv['dev-port'] || 5000;
+ }
+ return gulp.src('.')
+ .pipe(new Haxe().openfl({
+ command: 'build',
+ platform: 'flash',
+ version: version,
+ values: values,
+ outputFile: outputFile,
+ }))
+ .pipe(gulp.dest('target'));
+};
+
+const test = (build) => function test() {
+ const argv = yargs.argv;
+ return build()
+ .pipe(new FlashPlayer().run(argv.dev))
+ .pipe(tail(debug.log));
+};
+
+
+exports['client'] = gulp.series(prepare(Haxe.ID), build());
+exports['client:test'] = gulp.series(prepare(Haxe.ID, FlashPlayer.ID), test(build()));
diff --git a/build/prepare.js b/build/prepare.js
new file mode 100755
index 0000000..b6120d6
--- /dev/null
+++ b/build/prepare.js
@@ -0,0 +1,39 @@
+const AdobeAir = require('../tasks/adobeAir');
+const Haxe = require('../tasks/haxe');
+const FlashPlayer = require('../tasks/flashplayer');
+
+const packages = [
+ {name:'openfl', version:'6.0.1'},
+ {name:'lime', version:'5.3.0'},
+ 'promhx',
+ 'protohx',
+ 'haxework',
+];
+
+
+const prepareOne = (value) => {
+ switch (value) {
+ case Haxe.ID:
+ const haxe = new Haxe();
+ return haxe.prepare().then(() => haxe.install(packages));
+ case AdobeAir.ID:
+ return new AdobeAir().prepare();
+ case FlashPlayer.ID:
+ return new FlashPlayer().prepare();
+ default:
+ throw Error(`Unknown target: ${value}`)
+ }
+};
+
+const prepare = (...targets) => function prepare() {
+ const tasks = targets.map((target) => prepareOne(target));
+ return Promise.all(tasks);
+};
+
+
+const update = () => {
+ return new Haxe().upgrade();
+};
+
+module.exports = prepare;
+module.exports.update = update;
diff --git a/build/server.js b/build/server.js
new file mode 100755
index 0000000..c51f163
--- /dev/null
+++ b/build/server.js
@@ -0,0 +1,63 @@
+"use strict";
+const gulp = require('gulp');
+const yargs = require('yargs');
+const tail = require('../tasks/tail');
+const Haxe = require('../tasks/haxe');
+const FlashPlayer = require('../tasks/flashplayer');
+const Neko = require('../tasks/neko');
+const version = require('./version');
+const dateformat = require('dateformat');
+const prepare = require('./prepare');
+const debug = require('../tasks/debug');
+
+
+const build = (params) => function build() {
+ params = params || {};
+ const argv = yargs.argv;
+ const values = {
+ build: dateformat(new Date(), 'yyyy-mm-dd h:MM:ss'),
+ };
+ let outputFile = 'tankz.n';
+ if (params.outputFile) {
+ outputFile = params.outputFile;
+ }
+ if (argv.dev) {
+ values.dev = true;
+ if (!values['dev.address']) values['dev.address'] = argv['dev-address'] || 'localhost';
+ if (!values['dev.port']) values['dev.port'] = argv['dev-port'] || 5000;
+ }
+ return gulp.src('.')
+ .pipe(new Haxe().build({
+ platform: 'neko',
+ version: version,
+ values: values,
+ lib: [
+ 'protohx',
+ 'orm',
+ 'haxework',
+ ],
+ cp: [
+ 'src/common/haxe',
+ 'src/server/haxe',
+ 'src-gen/haxe',
+ ],
+ macro: [
+ `CompilationOption.set('debug.address','${values['dev.address']}')`,
+ `CompilationOption.set('debug.port','${values['dev.port']}')`,
+ ],
+ main: 'ru.m.tankz.server.Server',
+ outputFile: outputFile,
+ }))
+ .pipe(gulp.dest('target'));
+};
+
+const test = (build) => function test() {
+ const argv = yargs.argv;
+ return build()
+ .pipe(new Neko().run(argv.dev))
+ .pipe(debug());
+};
+
+
+exports['server'] = gulp.series(prepare(Haxe.ID), build());
+exports['server:test'] = gulp.series(prepare(Haxe.ID, FlashPlayer.ID), test(build()));
diff --git a/build/version.js b/build/version.js
new file mode 100644
index 0000000..2e7c713
--- /dev/null
+++ b/build/version.js
@@ -0,0 +1,2 @@
+const packageInfo = require('../package.json');
+module.exports = packageInfo.version;
\ No newline at end of file
diff --git a/config.example.json b/config.example.json
new file mode 100755
index 0000000..ce6ff44
--- /dev/null
+++ b/config.example.json
@@ -0,0 +1,7 @@
+{
+ "SdkDir": "C:\\sdk",
+ "SSH": {
+ "PrivateKey": null,
+ "Passphrase": null
+ }
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100755
index 0000000..8e0ad9d
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,31 @@
+"use strict";
+const os = require('os');
+const gulp = require('gulp');
+const clean = require('gulp-clean');
+const Sdk = require('./tasks/sdk');
+const Config = require('./config.json');
+const prepare = require('./build/prepare');
+
+if (Config.SdkDir) {
+ Sdk.dir = Config.SdkDir;
+}
+
+exports.clean = () => {
+ return gulp.src('target/*', {read: false}).pipe(clean());
+};
+
+const merge = (value) => {
+ if (typeof value === 'string') {
+ value = require(value);
+ }
+ for (let key in value) if (value.hasOwnProperty(key)) {
+ exports[key] = value[key];
+ }
+};
+
+exports.update = prepare.update;
+merge('./build/prepare');
+merge('./build/client');
+merge('./build/server');
+
+exports.default = gulp.series(exports.clean, exports.client, exports.server);
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100755
index 0000000..071ac31
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "tankz",
+ "version": "0.0.1",
+ "private": true,
+ "devDependencies": {
+ "async": "^2.1.2",
+ "dateformat": "^2.0.0",
+ "fs-extra": "^3.0.1",
+ "gulp": "github:gulpjs/gulp#4.0",
+ "gulp-add-src": "^0.2.0",
+ "gulp-cat": "^0.3.3",
+ "gulp-clean": "^0.3.2",
+ "gulp-exec": "^2.1.3",
+ "gulp-gunzip": "^1.0.0",
+ "gulp-mark": "0.0.2",
+ "gulp-replace-task": "^0.11.0",
+ "gulp-sequence": "^0.4.6",
+ "gulp-ssh": "^0.6.0",
+ "gulp-untar": "0.0.6",
+ "gulp-unzip": "^0.2.0",
+ "merge-stream": "^1.0.1",
+ "progress": "^2.0.0",
+ "request": "^2.81.0",
+ "request-progress": "^3.0.0",
+ "tail": "^1.2.2",
+ "tmp-file": "^2.0.1",
+ "vinyl-source-stream": "^1.1.0",
+ "yargs": "^8.0.2"
+ },
+ "dependencies": {}
+}
diff --git a/project.xml b/project.xml
index 727e606..52717d7 100755
--- a/project.xml
+++ b/project.xml
@@ -2,6 +2,7 @@
+
@@ -14,8 +15,13 @@
-
-
+
+
\ No newline at end of file
diff --git a/server.hxml b/server.hxml
index 5b5c8d2..d5c59b2 100755
--- a/server.hxml
+++ b/server.hxml
@@ -5,5 +5,7 @@
-cp src/common/haxe
-cp src/server/haxe
-cp src-gen/haxe
+--macro CompilationOption.set('debug.address','localhost')
+--macro CompilationOption.set('debug.port','5000')
-neko target/server.n
# -D proto_debug
\ No newline at end of file
diff --git a/src/client/haxe/ru/m/tankz/Client.hx b/src/client/haxe/ru/m/tankz/Client.hx
index 77c53c3..5554846 100755
--- a/src/client/haxe/ru/m/tankz/Client.hx
+++ b/src/client/haxe/ru/m/tankz/Client.hx
@@ -31,7 +31,10 @@ class Client implements IConnectionHandler {
#if flash
L.push(new JSLogger());
#end
- L.d(TAG, Meta.getVersion());
+ Const.init();
+ L.d(TAG, "Debug: " + Const.DEBUG);
+ L.i(TAG, "Version: " + Const.VERSION);
+ L.i(TAG, "Build: " + Const.BUILD);
new Client();
}
diff --git a/src/client/haxe/ru/m/tankz/Const.hx b/src/client/haxe/ru/m/tankz/Const.hx
new file mode 100755
index 0000000..cda81df
--- /dev/null
+++ b/src/client/haxe/ru/m/tankz/Const.hx
@@ -0,0 +1,19 @@
+package ru.m.tankz;
+
+import flash.Lib;
+import flash.system.Capabilities;
+
+
+class Const {
+ public static var FPS:Int;
+ public static var BUILD:String;
+ public static var VERSION:String;
+ public static var DEBUG:Bool;
+
+ public static function init():Void {
+ FPS = Lib.current.stage.application.config.fps;
+ BUILD = CompilationOption.get("build");
+ VERSION = Lib.current.stage.application.config.version;
+ DEBUG = Capabilities.isDebugger;
+ }
+}
\ No newline at end of file
diff --git a/src/server/haxe/ru/m/tankz/server/Server.hx b/src/server/haxe/ru/m/tankz/server/Server.hx
index 6f9c2ad..68031c8 100755
--- a/src/server/haxe/ru/m/tankz/server/Server.hx
+++ b/src/server/haxe/ru/m/tankz/server/Server.hx
@@ -1,5 +1,6 @@
package ru.m.tankz.server;
+import haxework.log.SocketLogger;
import ru.m.core.connect.IConnection.IPacketBuilder;
import haxework.provider.Provider;
import haxework.log.TraceLogger;
@@ -37,6 +38,7 @@ class Server extends ThreadServer {
public static function main() {
L.push(new TraceLogger());
+ L.push(new SocketLogger());
L.d(TAG, "Running");
Provider.set(IPacketBuilder, new PacketBuilder());
var wserver = new Server();
diff --git a/tasks/adobeAir.js b/tasks/adobeAir.js
new file mode 100755
index 0000000..241ca7f
--- /dev/null
+++ b/tasks/adobeAir.js
@@ -0,0 +1,259 @@
+"use strict";
+const fs = require('fs');
+const tmp = require('tmp-file');
+const gutil = require('gulp-util');
+const exec = require('./exec');
+const download = require('./download');
+const unzip = require('gulp-unzip');
+const gulp = require('gulp');
+const through = require('through2');
+const mark = require('gulp-mark');
+const replace = require('gulp-replace-task');
+const col = gutil.colors;
+const Sdk = require('./sdk');
+
+
+class AdobeAir extends Sdk {
+
+ get adtBin() {
+ return `${this.path}/bin/adt.bat`;
+ }
+
+ get adlBin() {
+ return `${this.path}/bin/adl.exe`;
+ }
+
+ static buildAdtArgs(params, descriptor, keystore, files, output) {
+ //const quote = (value) => `"${value}"`;
+ const quote = (value) => value; // ToDo:
+ const command = ['-package'];
+ const target = params.target || 'native';
+ if (target !== 'native') command.push('-target', target); //ToDo: adobe bleat' (target param position in native and apk-captive-runtime targets)
+ if (params.useLegacyAOT) {
+ command.push('-useLegacyAOT', 'yes');
+ }
+ if (params.profile) {
+ command.push('-provisioning-profile', quote(params.profile));
+ }
+ if (keystore) {
+ command.push('-storetype', 'PKCS12');
+ command.push('-keystore', keystore.path);
+ command.push('-storepass', keystore.storepass);
+ }
+ if (params.target === 'native') {
+ command.push('-tsa', 'http://sha256timestamp.ws.symantec.com/sha256/timestamp');
+ command.push('-target', target);
+ }
+ command.push(quote(output), quote(descriptor.path));
+ if (params.extdir) {
+ command.push('-extdir', quote(params.extdir));
+ }
+ if (params.content) {
+ for (let k in params.content) if (params.content.hasOwnProperty(k)) {
+ command.push('-C', quote(k), quote(params.content[k]));
+ }
+ }
+ for (let file of files) {
+ command.push('-C', quote(file.base), quote(file.path.split(file.base)[1]));
+ }
+ return command;
+ };
+
+ constructor(version) {
+ super(AdobeAir.ID, version || AdobeAir.VERSION);
+ }
+
+ get prepared() {
+ return fs.existsSync(`${this.path}/air-sdk-description.xml`);
+ }
+
+ get link() {
+ return `http://airdownload.adobe.com/air/win/download/${this.version}/AIRSDK_Compiler.zip`;
+ }
+
+ adt(path, args) {
+ return exec(path, [this.adtBin].concat(args).join(' '));
+ }
+
+ adl(path, args) {
+ return exec(path, [this.adlBin].concat(args).join(' '));
+ }
+
+ pack(params) {
+ const files = [];
+ let descriptor = null;
+ let keystore = null;
+ let stream = null;
+
+ const bufferContents = (file, enc, callback) => {
+ // ToDo: check file not stream
+ switch (file.mark) {
+ case 'descriptor':
+ descriptor = file;
+ break;
+ case 'keystore':
+ keystore = file;
+ break;
+ default:
+ files.push(file);
+ }
+ callback();
+ };
+
+ const endStream = (callback) => {
+ gutil.log(this.tag, col.cyan('adt', 'build', params.target), '=>', col.magenta(params.outputFile));
+ if (!descriptor) {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: 'descriptor is not defined'}));
+ callback();
+ return;
+ }
+ const tmpFile = tmp.generateFile();
+ const command = AdobeAir.buildAdtArgs(params, descriptor, keystore, files, tmpFile.path);
+ this.adt('.', command)
+ .then(() => {
+ stream.push(new gutil.File({
+ path: params.outputFile,
+ contents: fs.createReadStream(tmpFile.path)
+ }));
+ callback();
+ })
+ .catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: error}));
+ callback();
+ });
+ };
+
+ return stream = through.obj(bufferContents, endStream);
+ }
+
+ install() {
+ let stream = null;
+ const bufferContents = (file, enc, callback) => {
+ gutil.log(this.tag, col.cyan('adt', 'install'), col.magenta(file.name));
+ const command = ['-installApp', '-platform', 'android', '-package', file.path];
+ this.adt('.', command)
+ .then(() => {
+ stream.push(file);
+ callback();
+ })
+ .catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: error}));
+ callback();
+ });
+ };
+ return stream = through.obj(bufferContents);
+ }
+
+ launch(appid) {
+ let stream = null;
+ const bufferContents = (file, enc, callback) => {
+ gutil.log(this.tag, col.cyan('adt', 'launch'), col.magenta(appid));
+ const command = ['-launchApp', '-platform', 'android', '-appid', appid];
+ this.adt('.', command)
+ .then(() => {
+ stream.push(file);
+ callback();
+ })
+ .catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: error}));
+ callback();
+ });
+ };
+ return stream = through.obj(bufferContents);
+ }
+
+ test(params) {
+ let root = null;
+ let descriptor = null;
+ let stream = null;
+
+ const bufferContents = (file, enc, callback) => {
+ // ToDo: check file not stream
+ switch (file.mark) {
+ case 'descriptor':
+ descriptor = file;
+ break;
+ default:
+ root = file;
+ }
+ callback();
+ };
+
+ const endStream = (callback) => {
+ gutil.log(this.tag, col.cyan('adl'));
+ //const command = AdobeAir.buildAdtArgs(params, files, tmpFile.path);
+ const command = [];
+ if (params.profile) command.push('-profile', params.profile);
+ if (params.extdir) command.push('-extdir', params.extdir);
+ if (!descriptor) {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: 'descriptor is not defined'}));
+ callback();
+ return;
+ }
+ command.push(descriptor.path);
+ command.push(root.path);
+ if (params.args) {
+ command.push('--');
+ for (let key of Object.keys(params.args)) {
+ const value = params.args[key];
+ if (value === true) {
+ command.push(`-${key}`);
+ } else if (value) {
+ command.push(`-${key}`, `${value}`);
+ }
+ }
+ }
+ this.adl('.', command)
+ .then(() => {
+ stream.emit('end');
+ callback();
+ })
+ .catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: error}));
+ callback();
+ });
+
+ //callback();
+ stream.push(root);
+ };
+
+ return stream = through.obj(bufferContents, endStream);
+ }
+
+ static descriptor(template, values) {
+ const tmpFile = tmp.generateFile();
+ const patterns = [];
+ for (let k in values) if (values.hasOwnProperty(k)) {
+ patterns.push({match: k, replacement: values[k]});
+ }
+ return gulp.src(template)
+ .pipe(replace({patterns: patterns}))
+ .pipe(mark.set('descriptor'))
+ .pipe(gulp.dest(tmpFile.path));
+ }
+
+ static keystore(keystore, storepass) {
+ return gulp.src(keystore).pipe(through.obj((file, enc, callback) => {
+ file.mark = 'keystore';
+ file.storepass = storepass;
+ callback(null, file);
+ }));
+ }
+
+ static pack(params) {
+ return new AdobeAir().pack(params);
+ }
+
+ static test(params) {
+ return new AdobeAir().test(params);
+ }
+}
+
+AdobeAir.ID = 'adobe-air';
+
+AdobeAir.VERSION_26 = '26.0';
+AdobeAir.VERSION_25 = '25.0';
+AdobeAir.VERSION_24 = '24.0';
+AdobeAir.VERSION = AdobeAir.VERSION_26;
+
+module.exports = AdobeAir;
\ No newline at end of file
diff --git a/tasks/android.js b/tasks/android.js
new file mode 100644
index 0000000..cfe4bff
--- /dev/null
+++ b/tasks/android.js
@@ -0,0 +1,72 @@
+"use strict";
+const exec = require('./exec');
+const fs = require('fs');
+const through = require('through2');
+
+
+const Android = {
+ adb: (args) => {
+ const adbBin = `${process.env.ANDROID_HOME}/platform-tools/adb.exe`;
+ return exec('.', [adbBin].concat(args).join(' ')).then((data) => {
+ for (let line of data.stderr.split('\n')) {
+ if (line.indexOf('Error') > -1) {
+ throw line;
+ }
+ }
+ });
+ },
+
+ aapt: (args) => {
+ let buildToolsVersion = null;
+ fs.readdirSync(`${process.env.ANDROID_HOME}/build-tools`).forEach(file => {
+ buildToolsVersion = file;
+ });
+ const aaptBin = `${process.env.ANDROID_HOME}/build-tools/${buildToolsVersion}/aapt.exe`;
+ return exec('.', [aaptBin].concat(args).join(' '));
+ },
+
+ apk: () => {
+ return through.obj(function(file, enc, callback) {
+ Android.aapt(['l', '-a', file.path]).then((data) => {
+ let activity = false;
+ for (let line of data.stdout.split('\n')) {
+ if (line.indexOf('package') > -1) {
+ const value = /"(.*?)"/.exec(line);
+ if (value) file.package = value[1]
+ }
+ if (line.indexOf('activity') > -1) {
+ activity = true;
+ }
+ if (activity && line.indexOf('name') > -1) {
+ const value = /"(.*?)"/.exec(line);
+ if (value) {
+ file.activity = value[1];
+ activity = false;
+ }
+ }
+ }
+ this.push(file);
+ callback();
+ });
+ });
+ },
+
+ install: () => {
+ return through.obj(function(file, enc, callback) {
+ Android.adb(['install', '-r', file.path]).then(() => {
+ this.push(file);
+ callback();
+ });
+ });
+ },
+
+ start: () => {
+ return through.obj((file, enc, callback) => {
+ const name = `${file.package}/${file.activity}`;
+ Android.adb(['shell', 'am', 'start', '-n', name]).then(() => callback());
+ });
+ }
+};
+
+
+module.exports = Android;
\ No newline at end of file
diff --git a/tasks/debug.js b/tasks/debug.js
new file mode 100755
index 0000000..37f3efb
--- /dev/null
+++ b/tasks/debug.js
@@ -0,0 +1,41 @@
+const gulp = require('gulp');
+const gutil = require('gulp-util');
+const col = gutil.colors;
+const net = require('net');
+const through = require('through2');
+
+const color = (level) => {
+ return {
+ '[DEBUG]': col.white,
+ '[INFO]': col.cyan,
+ '[ERROR]': col.red,
+ '[WARNING]': col.yellow,
+ }[level] || col.reset;
+};
+
+const log = (line) => {
+ const result = line.split(' ');
+ console.log(col.gray(result[0]) + ' ' + color(result[1])(result.slice(1).join(' ')));
+};
+
+module.exports = () => {
+ return through.obj(function (file, enc, callback) {
+ const server = net.createServer((socket) => {
+ socket.on("data", (data) => {
+ const lines = data.toString().split('\n');
+ for (let line of lines) if (line.length > 2) {
+ log(line.substr(2));
+ }
+ });
+ socket.on("close", () => {
+ socket.destroy();
+ server.close();
+ this.emit('end');
+ callback();
+ });
+ });
+ server.listen(5000);
+ })
+};
+
+module.exports.log = log;
\ No newline at end of file
diff --git a/tasks/download.js b/tasks/download.js
new file mode 100644
index 0000000..3fd43cc
--- /dev/null
+++ b/tasks/download.js
@@ -0,0 +1,24 @@
+'use strict';
+const path = require('path');
+const request = require('request');
+const progress = require('request-progress');
+const source = require('vinyl-source-stream');
+const gutil = require('gulp-util');
+
+
+module.exports = (url) => {
+ const name = path.basename(url);
+ let stream = null;
+ return stream = progress(request({url: url, encoding: null}, function(error, response, body) {
+ try {
+ if (error) throw error;
+ if (response.statusCode !== 200) throw `${response.statusCode} ${response.statusMessage}`;
+ } catch (e) {
+ this.emit('error', new gutil.PluginError({plugin: 'download', message: e}));
+ }
+ }))
+ .on('error', (e) => stream.emit('error', e))
+ .on('progress', (p) => stream.emit('progress', p))
+ .pipe(source(name));
+};
+
diff --git a/tasks/exec.js b/tasks/exec.js
new file mode 100755
index 0000000..9a78b4f
--- /dev/null
+++ b/tasks/exec.js
@@ -0,0 +1,32 @@
+const gutil = require('gulp-util');
+const child_process = require('child_process');
+const async = require('async');
+const col = gutil.colors;
+const Promise = require("bluebird");
+
+
+const TAG = col.green('[exec]');
+
+const queue = async.queue((task, done) => {
+ //gutil.log(TAG, col.magenta(task.command));
+ //process.chdir(task.dir);
+ child_process.exec(task.command, {cwd: task.dir, maxBuffer: 1024 * 5000}, (err, stdout, stderr) => {
+ if (err) {
+ task.failure(stderr || stdout || err);
+ } else {
+ task.success({stdout: stdout, stderr: stderr});
+ }
+ done();
+ });
+});
+
+module.exports = (dir, command) => {
+ return new Promise((success, failure) => {
+ queue.push({
+ dir: dir,
+ command: command,
+ success: success,
+ failure: failure,
+ });
+ });
+};
\ No newline at end of file
diff --git a/tasks/flashplayer.js b/tasks/flashplayer.js
new file mode 100755
index 0000000..fdbaa97
--- /dev/null
+++ b/tasks/flashplayer.js
@@ -0,0 +1,171 @@
+const path = require('path');
+const fs = require('fs');
+const fse = require('fs-extra');
+const os = require('os');
+const gunzip = require('gulp-gunzip');
+const untar = require('gulp-untar');
+const through = require('through2');
+const gulp = require('gulp');
+const gutil = require('gulp-util');
+const Sdk = require('./sdk');
+const exec = require('./exec');
+const col = gutil.colors;
+
+
+class FlashPlayer extends Sdk {
+
+ constructor(version) {
+ super(FlashPlayer.ID, version || FlashPlayer.VERSION);
+ }
+
+ playerPath(debug) {
+ const v = this.version.split('.');
+ const dir = `${v[0]}_${v[1]}_r${v[2]}_${v[3]}`;
+ return `${this.path}/${dir}${debug ? '_debug': ''}`;
+ }
+
+ prepare() {
+ let p = super.prepare();
+ if (!this.prepared && os.type() === 'Linux') {
+ p = p.then(() => {
+ let arch = null;
+ if (os.arch() === 'ia32') { arch = 'i386'}
+ if (os.arch() === 'x64') { arch = 'x86_64'}
+
+ const extract = (debug) => {
+ const v = this.version.split('.');
+ const playerPath = this.playerPath(debug);
+ const archive = `${playerPath}/flashplayer${v[0]}_${v[1]}r${v[2]}_${v[3]}_linux_sa${debug ? '_debug' : ''}.${arch}.tar.gz`;
+ return gulp.src(archive)
+ .pipe(gunzip()).pipe(untar())
+ .pipe(gulp.dest(playerPath));
+ };
+
+ return new Promise((success, fail) => {
+ extract().on('end', () => {
+ extract(true).on('end', success).on('error', fail)
+ }).on('error', fail);
+ })
+ })
+ }
+ return p;
+ }
+
+ get prepared() {
+ try {
+ return fs.existsSync(`${this.flashPlayerBin()}`);
+ } catch (e) {
+ return false;
+ }
+ }
+
+ get link() {
+ return `https://fpdownload.macromedia.com/pub/flashplayer/installers/archive/fp_${this.version}_archive.zip`;
+ }
+
+ flashPlayerBin(debug) {
+ if (os.type() === 'Windows_NT') {
+ const v = this.version.split('.');
+ const playerName = `flashplayer${v[0]}_${v[1]}r${v[2]}_${v[3]}_win_sa${debug ? '_debug' : ''}.exe`;
+ return `${this.playerPath(debug)}/${playerName}`;
+ } else if (os.type() === 'Linux') {
+ const binPath = `${this.playerPath(debug)}/${debug ? 'flashplayerdebugger': 'flashplayer'}`;
+ fs.chmodSync(binPath, 0o755);
+ return binPath;
+ } else {
+ throw `Unsupported os '${os.type()}'`;
+ }
+ }
+
+ static get flashPlayerDir() {
+ if (os.type() === 'Windows_NT') {
+ return `${process.env.APPDATA}/Macromedia/Flash Player`;
+ } else if (os.type() === 'Linux') {
+ return `${os.homedir()}/.macromedia/Flash_Player`;
+ }
+ }
+
+ static get log() {
+ return `${this.flashPlayerDir}/Logs/flashlog.txt`;
+ }
+
+ static enableLog() {
+ const filename = `${os.homedir()}/mm.cfg`;
+ const value = 'TraceOutputFileEnable=1';
+ if (fs.exists(filename)) {
+ const data = fs.readFileSync(filename);
+ if (data.indexOf(value) === -1) {
+ fs.appendFileSync(filename, `${value}\n`);
+ }
+ } else {
+ fs.writeFileSync(filename, `${value}\n`);
+ }
+ }
+
+ static trust(value) {
+ const filename = `${this.flashPlayerDir}/#Security/FlashPlayerTrust/gulp.cfg`;
+ if (fs.exists(filename)) {
+ const data = fs.readFileSync(filename);
+ if (data.indexOf(value) === -1) {
+ fs.appendFileSync(filename, `${value}\n`);
+ }
+ } else {
+ if (!fs.exists(path.dirname(filename))) {
+ fse.ensureDirSync(path.dirname(filename));
+ }
+ fs.writeFileSync(filename, `${value}\n`);
+ }
+ }
+
+ run(debug) {
+ let stream = null;
+ const bufferContents = (file, enc, callback) => {
+ gutil.log(this.tag, col.cyan("run"), col.magenta(file.path));
+ FlashPlayer.trust(file.path);
+ FlashPlayer.enableLog();
+ exec('.', [this.flashPlayerBin(debug), file.path].join(' ')).then(() => {
+ stream.emit('end');
+ //callback();
+ }).catch((error) => {
+ stream.emit('end');
+ //callback();
+ stream.emit('error', error);
+ });
+ //stream.push(file);
+ // ToDo: watch when file is exists
+ // or create log file in FlashPlayer.enableLog()?
+ stream.push(new gutil.File({
+ path: FlashPlayer.log
+ }));
+ };
+
+ return stream = through.obj(bufferContents);
+ }
+}
+
+FlashPlayer.ID = "flashplayer";
+
+FlashPlayer.VERSION_11_2_202_644 = '11.2.202.644';
+FlashPlayer.VERSION_11 = FlashPlayer.VERSION_11_2_202_644;
+
+FlashPlayer.VERSION_24_0_0_186 = '24.0.0.186';
+FlashPlayer.VERSION_24_0_0_194 = '24.0.0.194';
+FlashPlayer.VERSION_24_0_0_221 = '24.0.0.221';
+FlashPlayer.VERSION_24 = FlashPlayer.VERSION_24_0_0_221;
+
+FlashPlayer.VERSION_25_0_0_127 = '25.0.0.127';
+FlashPlayer.VERSION_25_0_0_148 = '25.0.0.148';
+FlashPlayer.VERSION_25_0_0_163 = '25.0.0.163';
+FlashPlayer.VERSION_25_0_0_117 = '25.0.0.171';
+FlashPlayer.VERSION_25 = FlashPlayer.VERSION_25_0_0_117;
+
+FlashPlayer.VERSION_26_0_0_131 = '26.0.0.131';
+FlashPlayer.VERSION_26 = FlashPlayer.VERSION_26_0_0_131;
+
+if (os.type() === 'Linux' && os.arch() === 'ia32') { //Linux i386
+ FlashPlayer.VERSION = FlashPlayer.VERSION_11;
+} else {
+ FlashPlayer.VERSION = FlashPlayer.VERSION_26;
+}
+
+module.exports = FlashPlayer;
\ No newline at end of file
diff --git a/tasks/haxe.js b/tasks/haxe.js
new file mode 100755
index 0000000..e9b38bd
--- /dev/null
+++ b/tasks/haxe.js
@@ -0,0 +1,251 @@
+"use strict";
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const tmp = require('tmp-file');
+const gutil = require('gulp-util');
+const exec = require('./exec');
+const download = require('./download');
+const gulp = require('gulp');
+const through = require('through2');
+const col = gutil.colors;
+const Sdk = require('./sdk');
+
+
+class Haxe extends Sdk {
+
+ getBin(name) {
+ if (os.type() === 'Windows_NT') {
+ return `${this.binPath}/${name}.exe`;
+ } else if (os.type() === 'Linux') {
+ const binPath = `${this.binPath}/${name}`;
+ fs.chmodSync(binPath, 0o755);
+ return binPath;
+ }
+ return ``
+ }
+
+ get binPath() {
+ if (os.type() === 'Windows_NT') {
+ if (this.intVersion >= 342) {
+ return `${this.path}`;
+ } else {
+ return `${this.path}/haxe-${this.version}`;
+ }
+ } else if (os.type() === 'Linux') {
+ return `${this.path}/haxe-${this.version}`;
+ }
+ }
+
+ get haxeBin() {
+ return this.getBin('haxe');
+ }
+
+ get haxelibBin() {
+ return this.getBin('haxelib');
+ }
+
+ constructor(version) {
+ super(Haxe.ID, version || Haxe.VERSION);
+ }
+
+ get prepared() {
+ try {
+ return fs.existsSync(this.haxeBin);
+ } catch (e) {
+ return false;
+ }
+ }
+
+ get link() {
+ if (os.type() === 'Windows_NT') {
+ return `https://github.com/HaxeFoundation/haxe/releases/download/${this.version}/haxe-${this.version}-win.zip`;
+ } else if (os.type() === 'Linux') {
+ let arch = null;
+ if (os.arch() === 'ia32') { arch = '32'}
+ if (os.arch() === 'x64') { arch = '64'}
+ return `https://github.com/HaxeFoundation/haxe/releases/download/${this.version}/haxe-${this.version}-linux${arch}.tar.gz`;
+ }
+ }
+
+ haxe(args) {
+ return exec('.', [this.haxeBin].concat(args).join(' '));
+ }
+
+ haxelib(args) {
+ const haxelibBin = this.haxelibBin;
+ return exec(this.binPath, [path.basename(haxelibBin)].concat(args).join(' '));
+ }
+
+ install(packages) {
+ let promise = this.haxelib(['setup', `${this.path}/lib`]);
+ const next = (args) => () => {
+ gutil.log(this.tag, col.cyan('haxelib', 'install'), col.magenta(args[1]));
+ return this.haxelib(args);
+ };
+
+ for (let pack of packages) {
+ const args = [];
+ let version = null;
+ if (typeof pack === 'string') {
+ args.push('install', pack);
+ } else if (typeof pack === 'object') {
+ version = pack.version;
+ if (pack.git) {
+ args.push('git', pack.name, pack.git);
+ if (pack.branch) args.push(pack.branch);
+ } else {
+ args.push('install', pack.name);
+ if (version) args.push(version);
+ }
+ args.push('--always');
+ }
+ let path = `${this.path}/lib/${args[1]}`;
+ if (version) path += `/${version.replace(/\./g, ',')}`;
+ if (!fs.existsSync(path)) {
+ promise = promise.then(next(args));
+ }
+ }
+ return promise;
+ }
+
+ upgrade() {
+ let promise = this.haxelib(['setup', `${this.path}/lib`]);
+ promise = promise.then(() => this.haxelib(['upgrade', '--always']));
+ return promise;
+ }
+
+ /**
+ *
+ * @param params
+ * @returns {*}
+ *
+ * {
+ * command: 'build',
+ * platform: 'flash',
+ * version: '1.0.0',
+ * build: '1999.12.12 00:00',
+ * values: {},
+ * outputFile: 'out.swf',
+ * }
+ */
+ openfl(params) {
+ const files = [];
+ let stream = null;
+
+ const bufferContents = (file, enc, callback) => {
+ // ToDo: check file not stream
+ files.push(file);
+ callback();
+ };
+
+ const endStream = (callback) => {
+ gutil.log(this.tag, col.cyan("openfl", params.command, params.platform), '=>', col.magenta(params.outputFile));
+ const args = ['-cwd', files[0].path, 'run', 'openfl', params.command, params.platform];
+ if (params.values) for (let key of Object.keys(params.values)) {
+ const value = params.values[key];
+ if (value === true) {
+ args.push(`-D${key}`);
+ } else if (value) {
+ args.push(`-D${key}="${value}"`);
+ }
+ }
+ const tmpFile = tmp.generateFile();
+ const dir = path.dirname(tmpFile.path);
+ const name = path.basename(tmpFile.path);
+ args.push(`--app-path=${dir}`);
+ args.push(`--app-file=${name}`);
+ if (params.version) args.push(`--meta-version=${params.version}`);
+ if (params.build) args.push(`--haxedef=BUILD="${params.build}"`);
+ this.haxelib(args).then(() => {
+ stream.push(new gutil.File({
+ path: params.outputFile,
+ contents: fs.createReadStream(`${dir}/flash/bin/${name}.swf`)
+ }));
+ callback();
+ }).catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: error}));
+ callback();
+ });
+ };
+
+ return stream = through.obj(bufferContents, endStream);
+ }
+
+ /**
+ *
+ * @param params
+ *
+ * {
+ * platform: 'neko',
+ * version: '1.0.0',
+ * build: '1999.12.12 00:00',
+ * values: {},
+ * lib: [],
+ * src: [],
+ * main: 'Main.hx',
+ * outputFile: 'out.n',
+ * }
+ */
+ build(params) {
+ const files = [];
+ let stream = null;
+
+ const bufferContents = (file, enc, callback) => {
+ // ToDo: check file not stream
+ files.push(file);
+ callback();
+ };
+
+ const endStream = (callback) => {
+ gutil.log(this.tag, col.cyan("haxe", params.platform), '=>', col.magenta(params.outputFile));
+ const args = [];
+ args.push('-main', params.main);
+ for (const lib of params.lib) {
+ args.push('-lib', lib);
+ }
+ for (const cp of params.cp) {
+ args.push('-cp', cp);
+ }
+ for (const macro of params.macro) {
+ args.push('--macro', `"${macro}"`);
+ }
+ if (params.values) for (let key of Object.keys(params.values)) {
+ const value = params.values[key];
+ if (value === true) {
+ args.push(`-D ${key}`);
+ } else if (value) {
+ args.push(`-D ${key}="${value}"`);
+ }
+ }
+ const tmpFile = tmp.generateFile();
+ const dir = path.dirname(tmpFile.path);
+ const name = path.basename(tmpFile.path);
+ args.push(`-${params.platform}`, tmpFile.path);
+ if (params.build) args.push(`--haxedef=BUILD="${params.build}"`);
+ //console.log('haxe', args.join(' '));
+ this.haxe(args).then(() => {
+ stream.push(new gutil.File({
+ path: params.outputFile,
+ contents: fs.createReadStream(tmpFile.path)
+ }));
+ callback();
+ }).catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.name, message: error}));
+ callback();
+ });
+ };
+
+ return stream = through.obj(bufferContents, endStream);
+ }
+}
+
+Haxe.ID = 'haxe';
+
+Haxe.VERSION_3_4_0 = '3.4.0';
+Haxe.VERSION_3_4_2 = '3.4.2';
+Haxe.VERSION_3_4_3 = '3.4.3';
+Haxe.VERSION_3 = Haxe.VERSION_3_4_2;
+Haxe.VERSION = Haxe.VERSION_3;
+
+module.exports = Haxe;
\ No newline at end of file
diff --git a/tasks/neko.js b/tasks/neko.js
new file mode 100644
index 0000000..6cc8b2b
--- /dev/null
+++ b/tasks/neko.js
@@ -0,0 +1,35 @@
+const exec = require('./exec');
+const gutil = require('gulp-util');
+const through = require('through2');
+const col = gutil.colors;
+
+
+class Neko {
+
+ constructor() {
+ this.tag = 'Neko';
+ }
+
+ run() {
+ let stream = null;
+ const bufferContents = (file, enc, callback) => {
+ gutil.log(this.tag, col.cyan("run"), col.magenta(file.path));
+
+ exec('.', ['neko', file.path].join(' '))
+ .then(() => {
+ stream.emit('end');
+ callback();
+ })
+ .catch((error) => {
+ stream.emit('error', new gutil.PluginError({plugin: this.tag, message: error}));
+ callback();
+ });
+
+ stream.push(file);
+ };
+
+ return stream = through.obj(bufferContents);
+ }
+}
+
+module.exports = Neko;
\ No newline at end of file
diff --git a/tasks/sdk.js b/tasks/sdk.js
new file mode 100755
index 0000000..2743757
--- /dev/null
+++ b/tasks/sdk.js
@@ -0,0 +1,77 @@
+"use strict";
+const os = require('os');
+const gulp = require('gulp');
+const gutil = require('gulp-util');
+const col = gutil.colors;
+const download = require('./download');
+const unzip = require('gulp-unzip');
+const gunzip = require('gulp-gunzip');
+const untar = require('gulp-untar');
+const Promise = require('bluebird');
+const ProgressBar = require('progress');
+
+class Sdk {
+ static set dir(value) {
+ Sdk._dir = value
+ }
+
+ static get dir() {
+ return Sdk._dir || `${os.homedir()}/sdk`;
+ }
+
+ static path(name, version) {
+ return `${this.dir}/${name}/${version}`;
+ }
+
+ constructor(name, version) {
+ this.name = name;
+ this.tag = col.green(`[${name}]`);
+ this.version = version;
+ this.path = Sdk.path(name, version);
+ }
+
+ get intVersion() {
+ const vArr = this.version.split('\.').reverse();
+ let m = 1;
+ let r = 0;
+ for (let v of vArr) {
+ r += parseInt(v) * m;
+ m *= 10;
+ }
+ return r;
+ }
+
+ get prepared() {
+ throw "Not implemented";
+ }
+
+ get link() {
+ throw "Not implemented";
+ }
+
+ prepare() {
+ gutil.log(this.tag, `version: ${col.magenta(this.version)}`);
+ return new Promise((success, fail) => {
+ if (this.prepared) {
+ success();
+ } else {
+ const bar = new ProgressBar(`${this.tag} [:bar] :percent :etas`, {width: 40, total: 1000, clear: true});
+ let stream = download(this.link)
+ .on('error', fail)
+ .on('progress', (p) => bar.update(p.percent))
+ .on('end', () => bar.update(1));
+ if (this.link.endsWith('.zip')) {
+ stream = stream.pipe(unzip());
+ } else if (this.link.endsWith('tar.gz')) {
+ stream = stream.pipe(gunzip()).pipe(untar())
+ }
+ return stream
+ .pipe(gulp.dest(this.path))
+ .on('end', success)
+ .on('error', fail);
+ }
+ });
+ }
+}
+
+module.exports = Sdk;
\ No newline at end of file
diff --git a/tasks/tail.js b/tasks/tail.js
new file mode 100755
index 0000000..f177b15
--- /dev/null
+++ b/tasks/tail.js
@@ -0,0 +1,26 @@
+'use strict';
+const through = require('through2');
+const gutil = require('gulp-util');
+const col = gutil.colors;
+const Tail = require('tail').Tail;
+
+module.exports = (handler) => {
+ let stream = null;
+ let tail = null;
+
+ const bufferContents = (file, enc, callback) => {
+ tail = new Tail(file.path);
+ tail.on("line", handler);
+ callback();
+ };
+
+ const endStream = (callback) => {
+ if (tail) {
+ tail.unwatch();
+ tail = null;
+ }
+ callback();
+ };
+
+ return stream = through.obj(bufferContents, endStream);
+};
\ No newline at end of file