diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..c16c082c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,232 @@ +var OFF = 0, WARN = 1, ERROR = 2; + +module.exports = exports = { + "env": { + "es6": true, + "node": true, + "browser": true + }, + + "extends": "eslint:recommended", + + // "ecmaFeatures": { + // // env=es6 doesn't include modules, which we are using + // "modules": true + // }, + // + + "rules": { + "no-console": "off" + }, + + "globals": { + "W": "writable", + "node": "writable", + "J": "writable", + "JSUS": "writable" + } + + // + // "rules": { + // // Possible Errors (overrides from recommended set) + // "no-extra-parens": ERROR, + // "no-unexpected-multiline": ERROR, + // // All JSDoc comments must be valid + // "valid-jsdoc": [ ERROR, { + // "requireReturn": false, + // "requireReturnDescription": false, + // "requireParamDescription": true, + // "prefer": { + // "return": "returns" + // } + // }], + // + // // Best Practices + // + // // Allowed a getter without setter, but all setters require getters + // "accessor-pairs": [ ERROR, { + // "getWithoutSet": false, + // "setWithoutGet": true + // }], + // "block-scoped-var": WARN, + // "consistent-return": ERROR, + // "curly": ERROR, + // "default-case": WARN, + // // the dot goes with the property when doing multiline + // "dot-location": [ WARN, "property" ], + // "dot-notation": WARN, + // "eqeqeq": [ ERROR, "smart" ], + // "guard-for-in": WARN, + // "no-alert": ERROR, + // "no-caller": ERROR, + // "no-case-declarations": WARN, + // "no-div-regex": WARN, + // "no-else-return": WARN, + // "no-empty-label": WARN, + // "no-empty-pattern": WARN, + // "no-eq-null": WARN, + // "no-eval": ERROR, + // "no-extend-native": ERROR, + // "no-extra-bind": WARN, + // "no-floating-decimal": WARN, + // "no-implicit-coercion": [ WARN, { + // "boolean": true, + // "number": true, + // "string": true + // }], + // "no-implied-eval": ERROR, + // "no-invalid-this": ERROR, + // "no-iterator": ERROR, + // "no-labels": WARN, + // "no-lone-blocks": WARN, + // "no-loop-func": ERROR, + // "no-magic-numbers": WARN, + // "no-multi-spaces": ERROR, + // "no-multi-str": WARN, + // "no-native-reassign": ERROR, + // "no-new-func": ERROR, + // "no-new-wrappers": ERROR, + // "no-new": ERROR, + // "no-octal-escape": ERROR, + // "no-param-reassign": ERROR, + // "no-process-env": WARN, + // "no-proto": ERROR, + // "no-redeclare": ERROR, + // "no-return-assign": ERROR, + // "no-script-url": ERROR, + // "no-self-compare": ERROR, + // "no-throw-literal": ERROR, + // "no-unused-expressions": ERROR, + // "no-useless-call": ERROR, + // "no-useless-concat": ERROR, + // "no-void": WARN, + // // Produce warnings when something is commented as TODO or FIXME + // "no-warning-comments": [ WARN, { + // "terms": [ "TODO", "FIXME" ], + // "location": "start" + // }], + // "no-with": WARN, + // "radix": WARN, + // "vars-on-top": ERROR, + // // Enforces the style of wrapped functions + // "wrap-iife": [ ERROR, "outside" ], + // "yoda": ERROR, + // + // // Strict Mode - for ES6, never use strict. + // "strict": [ ERROR, "never" ], + // + // // Variables + // "init-declarations": [ ERROR, "always" ], + // "no-catch-shadow": WARN, + // "no-delete-var": ERROR, + // "no-label-var": ERROR, + // "no-shadow-restricted-names": ERROR, + // "no-shadow": WARN, + // // We require all vars to be initialized (see init-declarations) + // // If we NEED a var to be initialized to undefined, + // // it needs to be explicit + // "no-undef-init": OFF, + // "no-undef": ERROR, + // "no-undefined": OFF, + // "no-unused-vars": WARN, + // // Disallow hoisting - let & const don't allow hoisting anyhow + // "no-use-before-define": ERROR, + // + // // Node.js and CommonJS + // "callback-return": [ WARN, [ "callback", "next" ]], + // "global-require": ERROR, + // "handle-callback-err": WARN, + // "no-mixed-requires": WARN, + // "no-new-require": ERROR, + // // Use path.concat instead + // "no-path-concat": ERROR, + // "no-process-exit": ERROR, + // "no-restricted-modules": OFF, + // "no-sync": WARN, + // + // // ECMAScript 6 support + // "arrow-body-style": [ ERROR, "always" ], + // "arrow-parens": [ ERROR, "always" ], + // "arrow-spacing": [ ERROR, { "before": true, "after": true }], + // "constructor-super": ERROR, + // "generator-star-spacing": [ ERROR, "before" ], + // "no-arrow-condition": ERROR, + // "no-class-assign": ERROR, + // "no-const-assign": ERROR, + // "no-dupe-class-members": ERROR, + // "no-this-before-super": ERROR, + // "no-var": WARN, + // "object-shorthand": [ WARN, "never" ], + // "prefer-arrow-callback": WARN, + // "prefer-spread": WARN, + // "prefer-template": WARN, + // "require-yield": ERROR, + // + // // Stylistic - everything here is a warning because of style. + // "array-bracket-spacing": [ WARN, "always" ], + // "block-spacing": [ WARN, "always" ], + // "brace-style": [ WARN, "1tbs", { "allowSingleLine": false } ], + // "camelcase": WARN, + // "comma-spacing": [ WARN, { "before": false, "after": true } ], + // "comma-style": [ WARN, "last" ], + // "computed-property-spacing": [ WARN, "never" ], + // "consistent-this": [ WARN, "self" ], + // "eol-last": WARN, + // "func-names": WARN, + // "func-style": [ WARN, "declaration" ], + // "id-length": [ WARN, { "min": 2, "max": 32 } ], + // "indent": [ WARN, 4 ], + // "jsx-quotes": [ WARN, "prefer-double" ], + // // "linebreak-style": [ WARN, "unix" ], + // "lines-around-comment": [ WARN, { "beforeBlockComment": true } ], + // "max-depth": [ WARN, 8 ], + // "max-len": [ WARN, 132 ], + // "max-nested-callbacks": [ WARN, 8 ], + // "max-params": [ WARN, 8 ], + // "new-cap": WARN, + // "new-parens": WARN, + // "no-array-constructor": WARN, + // "no-bitwise": OFF, + // "no-continue": OFF, + // "no-inline-comments": OFF, + // "no-lonely-if": WARN, + // "no-mixed-spaces-and-tabs": WARN, + // "no-multiple-empty-lines": WARN, + // "no-negated-condition": OFF, + // "no-nested-ternary": WARN, + // "no-new-object": WARN, + // "no-plusplus": OFF, + // "no-spaced-func": WARN, + // "no-ternary": OFF, + // "no-trailing-spaces": WARN, + // "no-underscore-dangle": WARN, + // "no-unneeded-ternary": WARN, + // "object-curly-spacing": [ WARN, "always" ], + // "one-var": OFF, + // "operator-assignment": [ WARN, "never" ], + // "operator-linebreak": [ WARN, "after" ], + // "padded-blocks": [ WARN, "never" ], + // "quote-props": [ WARN, "consistent-as-needed" ], + // // "quotes": [ WARN, "single" ], + // "require-jsdoc": [ WARN, { + // "require": { + // "FunctionDeclaration": true, + // "MethodDefinition": true, + // "ClassDeclaration": false + // } + // }], + // "semi-spacing": [ WARN, { "before": false, "after": true }], + // "semi": [ ERROR, "always" ], + // "sort-vars": OFF, + // "space-after-keywords": [ WARN, "always" ], + // "space-before-blocks": [ WARN, "always" ], + // "space-before-function-paren": [ WARN, "never" ], + // "space-before-keywords": [ WARN, "always" ], + // "space-in-parens": [ WARN, "never" ], + // "space-infix-ops": [ WARN, { "int32Hint": true } ], + // "space-return-throw-case": ERROR, + // "space-unary-ops": ERROR, + // "spaced-comment": [ WARN, "always" ], + // "wrap-regex": WARN + // } +}; diff --git a/.gitignore b/.gitignore index c313df80..43f3eba5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,24 @@ descil.conf.js imgscore +log/*.log + + +games_available/ultimatum-game +games_available/dictator +games_available/meritocracy +games_available/ultimatum +games_available/stopgo +games_available/artex +games_available/MiniSvo +games_available/car-sharing +games_available/DCL +games_available/rules +games_available/prisonersgame +games_available/testergame + +games/ultimatum-game +games/testergame games/dictator games/meritocracy games/ultimatum @@ -34,10 +52,11 @@ games/artex games/MiniSvo games/car-sharing games/DCL +games/rules +games/prisonersgame + games_new/ -games_available/ start/ -log/*.log bin/nodegame diff --git a/.travis.yml b/.travis.yml index 0eacd4bb..f5188f25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,23 @@ node_js: - 6 - 7 - 8 + - 10 before_install: # Get installer script. - - wget https://github.com/nodeGame/nodegame-test/raw/master/nodegame-installer.js + - wget https://raw.githubusercontent.com/nodeGame/nodegame/master/bin/nodegame-installer.js - chmod a+x nodegame-installer.js install: - - node nodegame-installer.js @dev --install-dir node_modules --no-spinner --branch v4 - - npm install -g should - - npm install -g mocha + ## Remove package json, otherwise npm does not install nodegame as dependency. + - npm install --only=dev + - rm package.json + # --branch v4 + - node nodegame-installer.js @dev --install-dir node_modules --no-spinner --yes script: # Add extra tests here. # Test Ultimatum game. - - cd node_modules/nodegame-test/games/ultimatum-game + - cd node_modules/nodegame/games/ultimatum-game - ./bin/run-standalone-test-v4.sh diff --git a/CHANGELOG b/CHANGELOG index 8c5874bd..c0f95498 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,149 @@ # nodeGame Changelog +# 7.1.0 +- New release, updated installer. + +# 7.0.4 +- Updated installer. +- Minor fixes. + +# 7.0.3 +- Minor, installation fixes. + +# 7.0.0 +- Dropped --phantoms option in launcher. +- More robust paths in launcher. +- Added nodeGameRoot path in launcher. +- Installer adapted to support alpha releases. + +# 6.3.0 +- Fixed installer getting stuck on versions and help. +- Added installer option --no-games to skip installing default games. +- New release. + +# 6.2.4 (6.2.1-6.2.3 deprecated) +- Fixed "default" option in launcher for Commander v7. +- New release, fixed support for images loaded in mainframe. + +# 6.2.0 +- Removed debugger from launcher.js. +- Updated server with more options for WaitingRoom. + +# 6.1.1 +- Built widgets. + +# 6.1.0 +- Fixed launcher for Commander v7. +- Updated server,client,window,widgets. + +# 6.0.3 +- Fixed corrupted build in 6.0.2 (thanks jjensenius3). + +# 6.0.2 +- Fixed bug create-game missing private folder in stable version (thanks +jjensenius3). + +# 6.0.1 +- Updated server. + +# 6.0.0 +- Option --port to specify the port of the server in launcher. + +# 5.11.2 +- Fixing version numbers. + +# 5.11.0 +- Small update to client and bug fixes. + +# 5.10.1 +- Now really updated installer, as claimed in 5.10.0 + +# 5.10.0 +- New release, updated installer. + +# 5.9.0 +- New release, updated installer. + +# 5.8.1 +- New release, updated installer. + +# 5.8.0 +- Game.stepBack. + +# 5.7.0 +- GameRoom.computeBonus handles disconnected players in dump file. + +# 5.6.0-5 +- New release, updated installer. + +# 5.4.0 +- New release, updated server, widgets, window. + +:# 5.3.1 +- Minor fixes to installer. + +# 5.3.0 +- Improved servernode.js conf file. +- Improved installer: --dry option. +- Improved installer: better handling of parent node_modules folders. + +# 5.1.0 +- Minor update to installer. +- Widgets: CustomInput, and garbage collection. +- Window and Client: check frame height automatically. + +# 5.0.1 +- Minor update to installer. +- Fixed VisualTimer.restart bug introduced in v5. +- Server does not scan hidden folders inside the games directory. +- Generator does not allow to create games starting with a dot. + +# 5.0.0 +- Updated installer, now installing latest versions of v3, v4, v5. +- Updated installer, checks for node_modules folder above the current folder. +- Updated package.json with stricter version numbers. + +# 4.3.3 +- Updated package.json to fix the version numbers of v4 + +# 4.3.2 +- Re-enabled support for games without treatment description (bug introduced +in 4.3.0) +- Installer checks better for -v and -h options. + +# 4.3.0 +- Treatment selection in waiting room. If ALLOW_SELECT_TREATMENT is true in +waitroom.settings.js then a small dropdown menu allows user to select the +treatment for the game. +- Small fixes. + +# 4.2.1 (current, not npm) +- Fixed building client. + +# 4.2.0 +- Improved create-game command. +- Cleanup +- Small fixes. + +# 4.1.5 +- Fixed installer on Node < 8.x + +# 4.1.3 +- Improved installer +- Msg anti-spoofing support +- Fixed bug on Chrome with flickering scrollbar +- More options on Monitor, including display of last error +- Improved nodegame-mturk support +- Fixed tests +- Smaller fixes + +# 4.0.4 +- Improved monitor error logging. +- Updated installer version. + +# 4.0.2 +- Big release, check CHANGELOG on website. + # 3.5 - Printing version on startup (launcher). @@ -15,7 +159,7 @@ # 2.0.2 - Fixed install.stable script. -- Fixing launcher options. +- Fixing launcher options. # 2.0.0 (current) - Added -s option to launcher. Starts the server in SSL mode. Can be empty (loads nodegame-server/ssl/), or a path to a SSL directory. diff --git a/README.md b/README.md index 0dd5f3e8..e6c38a24 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ # nodeGame -nodeGame is a free, open source, real-time javascript framework for -online, multiplayer games in the browser. +Fast, scalable JavaScript for large-scale, online, multiplayer, real-time games +and experiments. --- -nodeGame is a general framework to play any kind of game online, but -it specially designed to conduct _social experiments_. - ## The Good parts - Open source and open standard (HTML5) @@ -18,8 +15,10 @@ it specially designed to conduct _social experiments_. [NDDB](http://nodegame.github.com/NDDB/docs/nddb.js.html) Javascript database - Server can run multiple games at the same time - - Customizable waiting rooms for online games + - Powerful and customizible waiting rooms + - Monitor interface - Works on mobile devices and tablets + - Bots (for playing) and Phantoms (for testing) - Installation is required only for the server, clients just need their browser windows - Integrates smoothly with other libraries (e.g. jQuery, D3.js, etc.) and web services, such as Amazon Mechanical Turk @@ -31,25 +30,20 @@ of game theory. It is called the [Ultimatum](http://en.wikipedia.org/wiki/Ultimatum_game) game. To play it follows the steps: + 1. Download the latest version of [node.js](http://nodejs.org) for your platform 2. Download the latest version of [git](http://www.git-scm.com) for your platform - 3. Download the development version of nodeGame using the install - script for - [Mac/Linux](https://raw.githubusercontent.com/nodeGame/nodegame/master/bin/install.latest.sh) - or - [Windows](https://raw.githubusercontent.com/nodeGame/nodegame/master/bin/install.latest.cmd) - 4. Open a terminal and browse to the `nodegame/` folder + 3. Download [installer](http://nodegame.org/nodegame-installer.js), and install nodegame: `node nodegame-installer` + 4. Enter installation directory. 5. Start the server with the command: `node launcher.js` - 6. Open two or more browser tabs pointing to - `localhost:8080/ultimatum` - 7. Open a browser tab pointing to + 6. Open one tab pointing to `localhost:8080` + 7. Select Ultimatum game + 8. Open more tabs, or start a bot through the waiting room interface + 7. Check the monitor interface at `localhost:8080/ultimatum/monitor` -To keep your development version code base up to date you can use the -script in `bin/pull-all.sh` (Mac/Linux only). - ## Documentation Complete documentation is available in the nodeGame diff --git a/bin/build.sh b/bin/legacy/build.sh similarity index 100% rename from bin/build.sh rename to bin/legacy/build.sh diff --git a/bin/install-nodegame-for-module-node5.beta.sh b/bin/legacy/install-nodegame-for-module-node5.beta.sh similarity index 100% rename from bin/install-nodegame-for-module-node5.beta.sh rename to bin/legacy/install-nodegame-for-module-node5.beta.sh diff --git a/bin/install-nodegame-for-module.sh b/bin/legacy/install-nodegame-for-module.sh similarity index 100% rename from bin/install-nodegame-for-module.sh rename to bin/legacy/install-nodegame-for-module.sh diff --git a/bin/install.dev.sh b/bin/legacy/install.dev.sh similarity index 100% rename from bin/install.dev.sh rename to bin/legacy/install.dev.sh diff --git a/bin/install.latest.cmd b/bin/legacy/install.latest.cmd similarity index 100% rename from bin/install.latest.cmd rename to bin/legacy/install.latest.cmd diff --git a/bin/install.latest.sh b/bin/legacy/install.latest.sh similarity index 100% rename from bin/install.latest.sh rename to bin/legacy/install.latest.sh diff --git a/bin/install.stable.cmd b/bin/legacy/install.stable.cmd similarity index 100% rename from bin/install.stable.cmd rename to bin/legacy/install.stable.cmd diff --git a/bin/install.stable.sh b/bin/legacy/install.stable.sh similarity index 100% rename from bin/install.stable.sh rename to bin/legacy/install.stable.sh diff --git a/bin/pull-all.sh b/bin/legacy/pull-all.sh similarity index 75% rename from bin/pull-all.sh rename to bin/legacy/pull-all.sh index 0fc564cc..364f8961 100755 --- a/bin/pull-all.sh +++ b/bin/legacy/pull-all.sh @@ -7,17 +7,21 @@ echo_and_pull() { git pull || echo ' FAILED!' } -GAMES=(ultimatum) +GAMES=(ultimatum-game) MODULES=(nodegame-client nodegame-server nodegame-window nodegame-widgets nodegame-requirements nodegame-game-template nodegame-monitor JSUS NDDB - shelf.js descil-mturk nodegame-db nodegame-mongodb nodegame-generator) + nodegame-generator nodegame-mturk) + +## Removed modules for now. +## nodegame-db nodegame-mongodb # Change the current working directory to the parent directory of the script, # i.e. the nodegame directory. Using the below command instead of simply # "cd .." makes sure that it does not matter from where the script is executed -cd "$(dirname "${BASH_SOURCE[0]}")/.." +# cd "$(dirname "${BASH_SOURCE[0]}")/.." -echo_and_pull nodegame +## Not yet, this fails, we just update the modules. +## echo_and_pull nodegame for GAME in "${GAMES[@]}"; do ( diff --git a/nodegame.js b/bin/nodegame-cli-experimental.js similarity index 99% rename from nodegame.js rename to bin/nodegame-cli-experimental.js index 8b7b7399..cf450447 100755 --- a/nodegame.js +++ b/bin/nodegame-cli-experimental.js @@ -11,6 +11,9 @@ "use strict"; +console.log('not ready yet!'); +return; + // Modules. const fs = require('fs-extra'); const path = require('path'); diff --git a/nodegame-installer.js b/bin/nodegame-installer.js similarity index 54% rename from nodegame-installer.js rename to bin/nodegame-installer.js index 4aaac46f..91c0cfbf 100755 --- a/nodegame-installer.js +++ b/bin/nodegame-installer.js @@ -1,10 +1,9 @@ #!/usr/local/bin/node /** * # nodeGame Installer - * Copyright(c) 2017 Stefano Balietti + * Copyright(c) 2011-2023 Stefano Balietti * MIT Licensed * - * * http://www.nodegame.org */ @@ -27,14 +26,64 @@ const log = txt => { else console.log(' ' + txt); }; const err = txt => { - console.error(' ' + txt); + console.error(' Error: ' + txt); +}; +const warn = txt => { + console.error(' Warning: ' + txt); +}; + +// const MAIN_MODULE = 'nodegame'; +let MAIN_MODULE = 'nodegame'; + +// All stable versions. +// Versions below < 3 are not available. +const STABLE_VERSIONS = { + v3: '3.5.3', + v4: '4.3.3', + v5: '5.11.2', + v6: '6.3.0', + v7: '7.1.0' + // v8: '8.0.0' }; -if (process.argv.indexOf('--help') !== -1) { +const AVAILABLE_VERSIONS = Object.keys(STABLE_VERSIONS).concat(['dev']); + +// Installer default version. +const INSTALLER_VERSION = 'v7'; + +// If node_modules folders are detected, their paths (without node_modules) +// is stored in here. +var parentNodeModules; + +// The actual version being installed, user can change it. +var version = STABLE_VERSIONS[INSTALLER_VERSION]; + +// Check if it is a help call. +let p = process.argv[2]; +if (p === '--help' || p === '-h') { printHelp(); return; } +else if (p === '--list-versions') { + console.log(' List of stable versions:'); + for (let i in STABLE_VERSIONS) { + if (STABLE_VERSIONS.hasOwnProperty(i)) { + console.log(' @' + i + ': ' + STABLE_VERSIONS[i]); + } + } + return; +} +else if (p === '--version' || p === '-v') { + console.log('nodegame-installer ' + version); + return; +} + +var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); +var alpha = false; var verbose = false; var nodeModulesExisting = false; var isDev = false; @@ -42,62 +91,88 @@ var doSSH = false; var noSpinner = false; var doNotMoveInstall = false; var yes; +var noParentDirCheck; var branch; var warnings; +var dry; -const MAIN_MODULE = 'nodegame-test'; +// Add default games in games/ and games_available/. +var addGames = true; -// Installer default version. -const INSTALLER_VERSION = "4.0.8"; - -// The actual version being installed, user can change it. -var version = INSTALLER_VERSION; -// User requested version; -var requestedVersion = requestedVersion = '@' + version; +// User requested version. +var requestedVersion = '@' + version; for (let i = 0; i < process.argv.length; i++) { - if (process.argv[i].charAt(0) === '@') { + let option = process.argv[i]; + + if (option.charAt(0) === '@') { requestedVersion = process.argv[i].substr(1); if (requestedVersion === 'dev') { isDev = true; - version = INSTALLER_VERSION; requestedVersion = '@' + version; - if (process.argv.indexOf('--ssh') !== -1) doSSH = true; - branch = process.argv.indexOf('--branch'); - if (branch !== -1) { - branch = process.argv[branch+1]; - if (!branch) { - err('--branch option found, ' + - 'but no value provided.'); - log(); - return; - } - } - else { - branch = undefined; - } + + // For testing alpha versions. + // MAIN_MODULE = 'nodegame-test'; + // version = '7.0.4'; + // requestedVersion = '@' + version; + // alpha = true; + } + else if (requestedVersion === 'alpha') { + // For testing alpha versions. + MAIN_MODULE = 'nodegame-test'; + requestedVersion = '@latest'; + alpha = true; } else { - version = requestedVersion; - if (version.length < 1 || version.length > 5) { - err('Error: invalid version number: ', version); + version = STABLE_VERSIONS[requestedVersion]; + if (!version) { + err('invalid version: ', version); + log('available version options: ' + AVAILABLE_VERSIONS); log(); return; } - requestedVersion = '@' + requestedVersion; + requestedVersion = '@' + version; } - break; + } + else if (option === '--no-spinner') { + noSpinner = true; + } + else if (option === '--yes') { + yes = true; + } + else if (option === '--no-parent-dir-check') { + noParentDirCheck = true; + } + else if (option === '--branch') { + branch = process.argv[i+1]; + if (!branch) { + err('--branch option found, but no value provided.'); + log(); + return; + } + + } + else if (option === '--ssh') { + doSSH = true; + } + else if (option === '--dry') { + dry = true; + } + else if (option === '--no-games') { + addGames = false; } } -if (process.argv.indexOf('--no-spinner') !== -1) noSpinner = true; -if (process.argv.indexOf('--yes') !== -1) yes = true; +if ((doSSH || branch) && !isDev) { + err('--branch and --doSSH options are available only with @dev'); + return; +} // nodeGame version. const VERSION = isDev ? "v" + version + '-dev' : "v" + version; -const NODEGAME_AND_VERSION = 'nodegame-' + VERSION; +const NODEGAME_AND_VERSION = 'nodegame-' + VERSION + (alpha ? '-alpha' : ''); const ROOT_DIR = process.cwd() const NODE_MODULES_DIR = path.resolve(ROOT_DIR, 'node_modules'); @@ -129,17 +204,25 @@ const NODEGAME_MODULES = [ 'nodegame-window', 'nodegame-widgets', 'nodegame-monitor', 'nodegame-game-template', 'nodegame-requirements', 'nodegame-generator', + 'nodegame-mturk', // No need to replace these now. // 'nodegame-db', 'nodegame-mondodb', 'JSUS', 'NDDB', 'ultimatum-game' ]; -const N_MODULES = NODEGAME_MODULES.length; const GAMES_AVAILABLE_DIR = path.resolve(INSTALL_DIR, 'games_available'); const GAMES_ENABLED_DIR = path.resolve(INSTALL_DIR, 'games'); +// Making sure we do not slip out on an exception. + +process.on('uncaughtException', function(err) { + warn('A generic error occurred during the installation:\n'); + log(err.stack) + installationFailed(); +}); + // Printing Info. // Print cool nodegame logo. @@ -151,58 +234,106 @@ printInstallInfo(); // Check node version is. var nodeVersion = process.versions.node.split('.'); -if (parseInt(nodeVersion[0], 10) < 4) { - err('Error: node version >= 4.x is required.'); - err('Please upgrade your Node.Js installation, ' + +if (parseInt(nodeVersion[0], 10) < 10) { + err('node version >= 10.x is required.\n' + + 'Please upgrade your Node.Js installation, ' + 'visit: http://nodejs.org'); - log(); + installationAborted(); return; } // Check if install dir exists (abort). if (fs.existsSync(INSTALL_DIR)) { - err('Error: installation directory already existing.'); - log(); + err('installation directory already existing.'); + installationAborted(); return; } // Check if node_modules exists (prompt continue?) if (fs.existsSync(NODE_MODULES_DIR)) { nodeModulesExisting = true; - err('Warning: node_modules directory already existing.'); + warn('A "node_modules" folder was detected in this directory.'); + log(); + log('If you continue, it will become a subfolder of the nodeGame '); + log('installation. This might affect live node processes, as well as '); + log('other Node.JS packages relying on this node_modules folder. If '); + log('unsure, choose No and try to install nodeGame on another path.'); if (!yes) { - confirm(' Continue? [y/n] ', function(ok) { + log(); + confirm('Continue? [y/n]', function(ok) { if (ok) { - process.stdin.destroy(); log(); - doInstall(); + log(); + checkParentNodeModules(); } else { - err('Installation aborted.'); + installationAborted(true); log(); } }) return; } - else { + else { log('Continue? [y/n] --yes'); log(); } } // Install. -doInstall(); - +if (isDev) checkGitExists(checkParentNodeModules); +else checkParentNodeModules(); // Helper functions. /////////////////////////////////////////////////////////////////////////////// +function checkParentNodeModules() { + parentNodeModules = getParentNodeModules(); + + // Check if a node_modules folder exists in any folder from the one above. + // to top /. + if (!noParentDirCheck && parentNodeModules.length) { + let str; + str = 'A "node_modules" folder was detected in '; + str += parentNodeModules.length === 1 ? 'a parent directory: ' : + 'these parent directories: '; + warn(str); + parentNodeModules.forEach(dir => logList(dir)); + log(); + + str = 'If you continue, ' + (parentNodeModules.length === 1 ? + 'that folder' : 'those folders'); + log(str + ' will be temporarily renamed. This'); + log('might affect only live node processes. If unsure, choose No '); + log('and try to install nodeGame on another path.'); + log(); + if (!yes) { + confirm('Continue? [y/n]', renameParentCb); + } + else { + log('Continue? [y/n] --yes'); + log(); + renameParentCb(true); + } + } + else { + doInstall(); + } +} + function doInstall() { var sp; + // Create spinner. log('Downloading and installing nodeGame packages.'); + if (dry) { + log(); + warn('Dry run: aborting.'); + closeRL(2); + return; + } + if (!noSpinner) { sp = new Spinner(' This might take a few minutes %s '); sp.start(); @@ -226,12 +357,28 @@ function doInstall() { log(); logList(stderr.trim()); log(); + installationFailed(); return; } else { - if (verbose) logList(stdout.trim()); + if (verbose) { + log(); + logList(stdout.trim()); + } + if (!fs.existsSync(path.resolve(NODE_MODULES_DIR))) { + log(); + log(); + log('Doh! It looks like npm has a different default ' + + 'installation folder.'); + log('This can happen if you have a directory called '+ + '"node_modules" in any of '); + log('the parent folders. Please try using a ' + + 'different path.'); + installationFailed(); + return; + } log(); - log('Done! Now some finishing magics...'); + log('Done! Now some final magic...'); try { someMagic(); } @@ -260,32 +407,55 @@ function doInstall() { // logList(stdout.trim()); // } // }); - err('Oops! The following error/s occurred: '); + log('Oops! The following error/s occurred: '); log(); console.error(e); installationFailed(); return; } } + return; }); } -// Helper stuff. -//////////////// +function checkGitExists(cb) { + let child = execFile( + 'git', + [ '--version' ], + { cwd: ROOT_DIR }, + (error, stdout, stderr) => { + if (error) { + err('git not found, cannot install @dev version.'); + log('Install git from: https://git-scm.com/ and retry.'); + installationFailed(); + } + else { + if (cb) cb(); + } + }); +} function printNodeGameInfo() { log(); - log('*********************************************** '); - log('** WELCOME TO NODEGAME INSTALLER v' + INSTALLER_VERSION + - ' ** '); - log('*********************************************** '); + if (alpha) { + log('***************************************************** '); + log('** WELCOME TO NODEGAME INSTALLER v' + version + + (alpha ? '-alpha' : '') + ' ** '); + log('***************************************************** '); + } + else { + log('*********************************************** '); + log('** WELCOME TO NODEGAME INSTALLER v' + version + ' ** '); + log('*********************************************** '); + } + log(); log('nodeGame: fast, scalable JavaScript for online, large-scale,'); log('multiplayer, real-time games and experiments in the browser.'); log(); log('creator: Stefano Balietti'); - log('website: http://nodegame.org'); + log('website: https://nodegame.org'); log('license: MIT'); log('mail: info@nodegame.org'); log('twitter: @nodegameorg'); @@ -295,13 +465,12 @@ function printNodeGameInfo() { } function printInstallInfo() { - let str; log(); log('----------------------------------------------'); log(); log('node version: ' + process.version); - str = 'nodeGame version: ' + VERSION; + let str = 'nodeGame version: ' + VERSION; if (branch) str += ' (' + branch + ')'; log(str); str = 'install directory: ' + INSTALL_DIR; @@ -329,10 +498,10 @@ function printFinalInfo() { log(); log('Open a browser tab at the address:'); - log(' http://localhost:8080/ultimatum'); + log(' http://localhost:8080/'); log(); - log('Open another tab with an autoplay player:'); + log('Start a bot from the waiting room interface, or open another tab:'); log(' http://localhost:8080/ultimatum?clientType=autoplay'); log(); @@ -353,60 +522,106 @@ function printFinalInfo() { log(); } +function closeRL(code) { + // rl.clearLine(); + rl.close(); + // if (force) process.stdin.destroy(); + process.exit(code); +} + +function someMagic(cb) { + let mainNgDir = path.resolve(NODE_MODULES_DIR, MAIN_MODULE); -function someMagic() { + // Check if log and private directories have been created. + if (!fs.existsSync(path.resolve(mainNgDir, 'log'))) { + fs.mkdirSync(path.resolve(mainNgDir, 'log')); + } + if (!fs.existsSync(path.resolve(mainNgDir, 'private'))) { + fs.mkdirSync(path.resolve(mainNgDir, 'private')); + } + if (!fs.existsSync(path.resolve(mainNgDir, 'export'))) { + fs.mkdirSync(path.resolve(mainNgDir, 'export')); + } if (!doNotMoveInstall) { // Move nodegame folder outside node_modules. - fs.renameSync(path.resolve(NODE_MODULES_DIR, MAIN_MODULE), INSTALL_DIR); + fs.renameSync(mainNgDir, INSTALL_DIR); - // Old npms put already all modules under nodegame. - if (!fs.existsSync(INSTALL_DIR_MODULES)) { - fs.renameSync(NODE_MODULES_DIR, - INSTALL_DIR_MODULES); + try { + // Old npms put already all modules under nodegame. + if (!fs.existsSync(INSTALL_DIR_MODULES)) { + fs.renameSync(NODE_MODULES_DIR, + INSTALL_DIR_MODULES); + } + else if (!nodeModulesExisting) { + fs.rmdirSync(NODE_MODULES_DIR); + } } - else if (!nodeModulesExisting) { - fs.rmdirSync(NODE_MODULES_DIR); + catch(e) { + let keep = nodeModulesExisting; + moveFolderSync(NODE_MODULES_DIR, INSTALL_DIR_MODULES, keep); } } - // nodeGame generator: make link and store conf. - - makeLink(path.resolve(INSTALL_DIR_MODULES, - 'nodegame-generator', - 'bin', 'nodegame'), - path.resolve(INSTALL_DIR, 'bin', 'nodegame'), - 'file'); + if (isDev) { + getAllGitModules(function() { + if (addGames) { + // Move games from node_modules. + copyGameFromNodeModules('ultimatum-game'); + } - fs.writeFileSync(path.resolve(INSTALL_DIR_MODULES, - 'nodegame-generator', - 'conf', - 'generator.conf.json'), - JSON.stringify({ - author: "", - email: "", - gamesFolder: GAMES_AVAILABLE_DIR - }, 4)); + // Generator. + fixGenerator(); + // Restore any parent node_modules folder that was renamed. + restoreParentNodeModules(); - if (isDev) { - getAllGitModules(function() { - // Move games from node_modules. - copyGameFromNodeModules('ultimatum-game'); // Print final Information. printFinalInfo(); + + closeRL(0); }); } else { - // Move games from node_modules. - copyGameFromNodeModules('ultimatum-game'); + + if (addGames) { + // Move games from node_modules. + copyGameFromNodeModules('ultimatum-game'); + } + + // Generator. + fixGenerator(); + + // Restore any parent node_modules folder that was renamed. + restoreParentNodeModules(); + // Print final Information. printFinalInfo(); + + closeRL(0); } } +function fixGenerator() { + // nodeGame generator: make link and store conf. + makeLink(path.resolve(INSTALL_DIR_MODULES, + 'nodegame-generator', + 'bin', 'nodegame'), + path.resolve(INSTALL_DIR, 'bin', 'nodegame'), + 'file'); + + fs.writeFileSync(path.resolve(INSTALL_DIR_MODULES, + 'nodegame-generator', 'conf', 'generator.conf.json'), + JSON.stringify({ + author: "", + email: "", + ngDir: INSTALL_DIR + }, 4)); +} + function getAllGitModules(cb) { + let gitPrecommitHook = path.resolve(INSTALL_DIR, 'git-hooks', 'pre-commit'); let counter = NODEGAME_MODULES.length; if (verbose) log('Converting modules into git repos.'); for (let i = 0; i < NODEGAME_MODULES.length; i++) { @@ -427,7 +642,9 @@ function getAllGitModules(cb) { } }); - nodeModulesCopy = path.resolve(NODE_MODULES_DIR, + let destDir = doNotMoveInstall ? + NODE_MODULES_DIR : INSTALL_DIR_MODULES; + nodeModulesCopy = path.resolve(destDir, ('_node_modules-' + module)); fs.renameSync(nodeModulesPath, nodeModulesCopy); } @@ -442,6 +659,10 @@ function getAllGitModules(cb) { if (nodeModulesCopy) { fs.renameSync(nodeModulesCopy, nodeModulesPath); } + // Copy pre-commit hook. + fs.copyFileSync(gitPrecommitHook, + path.resolve(modulePath, '.git', 'hooks', + 'pre-commit')); counter--; if (counter == 0 && cb) cb(); }); @@ -503,7 +724,7 @@ function copyGameFromNodeModules(game, enable) { enable = 'undefined' === typeof enable ? true : enable; let gameDir = path.resolve(GAMES_AVAILABLE_DIR, game); - // Move game from node_modules into games_available directory. + // Move game from node_modules into games_available directory. fs.renameSync(path.resolve(INSTALL_DIR_MODULES, game), gameDir); // Make sure that the test command works. @@ -518,19 +739,13 @@ function copyGameFromNodeModules(game, enable) { if (!enable) return; - // Enable gapath.resolve(GAMES_AVAILABLE_DIR, game). + // Enable it. makeLink(gameDir, path.resolve(GAMES_ENABLED_DIR, game)); } -function confirm(msg, callback) { - var rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - rl.question(msg, function(input) { - rl.close(); - callback(/^y|yes|ok|true$/i.test(input)); +function confirm(msg, callback, ...params) { + rl.question(' ' + msg + ' ', function(input) { + callback(/^y|yes|ok|true$/i.test(input, ...params)); }); } @@ -539,7 +754,7 @@ function removeDirRecursiveSync(dir) { throw new Error(' removeDirRecursiveSync error: cannot remove "/"'); } if (dir.indexOf(INSTALL_DIR_MODULES) === -1) { - err(' removeDirRecursiveSync error: there seems to be ' + + err('removeDirRecursiveSync error: there seems to be ' + 'an error with the path to remove: '); console.error(dir); } @@ -556,45 +771,118 @@ function removeDirRecursiveSync(dir) { }); fs.rmdirSync(dir); } -}; +} -function installationFailed() { +function getParentNodeModules() { + let tks = ROOT_DIR.split(path.sep); + let found = []; + let dirPath = tks[0]; + for (let i = 0 ; i < (tks.length - 1) ; i++) { + if (i !== 0) dirPath = path.join(dirPath, tks[i]); + if (fs.existsSync(path.join(dirPath, 'node_modules'))) { + found.push(dirPath); + } + } + return found; +} + +// Need this wrapper because --yes option +function renameParentCb(ok) { + if (ok) { + log(); + let res = renameParentNodeModules(parentNodeModules); + if (res !== true) { + err('Could not rename "node_modules" folder in: ' + res[0]); + installationFailed(); + } + log(); + doInstall(); + } + else { + installationAborted(true); + return; + } +} + +function renameParentNodeModules(parents, restore) { + let out = []; + for (let i = 0; i < parents.length; i++) { + try { + let f1 = path.join(parents[i], 'node_modules'); + let f2 = f1 + '_backup'; + // Add _bak or remove _bak from parent node_modules folders. + if (restore) fs.renameSync(f2, f1); + else fs.renameSync(f1, f2); + } + catch(e) { + out.push(parents[i]); + // If we are not restoring original folders, exit immediately. + if (!restore) return(out); + } + } + return true; +} + +function restoreParentNodeModules() { + if (!parentNodeModules || !parentNodeModules.length) return; + let res = renameParentNodeModules(parentNodeModules, true); + if (res !== true) { + log(); + warn('Could not restore parent "node_modules" folder in: '); + res.forEach(dir => logList(dir)); + } +} +function installationAborted(byUser) { + let str = 'Installation aborted' + (byUser ? ' by user.' : '.'); log(); + log(str); + closeRL(1); + return; +} - err('Installation did not complete successfully.'); +function installationFailed() { + log(); + + log('Installation did not complete successfully.'); log('----------------------------------------------'); log(); - err('If you think this might be a bug, please report it. ' + + log('If you think this might be a bug, please report it. ' + 'You can either:'); - err(' - open an issue at: ' + + log(' - open an issue at: ' + 'https://github.com/nodeGame/nodegame/issues'); - err(' - send an email to info@nodegame.org'); + log(' - send an email to info@nodegame.org'); log(); + + // Restore any parent node_modules folder that was renamed. + restoreParentNodeModules(); + closeRL(1); } function printHelp() { - log(); - log('@ Install a specific version (>=3.5.1)'); - log('@dev Install from git repos, when available'); - log('--branch Checkout this branch on all git repos'); - log('--yes Answer yes to all questions'); - log('--install-dir Set the name of the installation directory;'); + log('@ Installs a specific version (v3, v4, v5, v6)'); + log('@dev Installs latest nodeGame from git repos'); + log('--branch Checkouts this branch on all git repos'); + log('--ssh Uses ssh to get all git repos'); + log('--yes Answers yes to all questions'); + log('--install-dir Sets the name of the installation directory;'); log(' if equals to node_modules, the npm structure'); log(' stays unchanged'); log('--no-spinner Does not start the spinner'); - log('--help Print this help'); + log('--dry Does not actually install anything'); + log('--list-versions Lists stable versions'); + log('--no-games Does not install default games'); + log('--version Prints installer version'); + log('--help Prints this help'); log(); } // Kudos: cli-spinner package. function Spinner(text) { - var that; - that = this; this.spinners = [ "|/-\\", @@ -664,10 +952,10 @@ function Spinner(text) { readline.clearLine(stream, 0); readline.cursorTo(stream, 0); }; -}; +} function inArray(needle, haystack) { - var func, i, len; + var i, len; len = haystack.length; for (i = 0; i < len; i++) { if (needle === haystack[i]) { @@ -676,3 +964,24 @@ function inArray(needle, haystack) { } return false; } + +function moveFolderSync(from, to, copy) { + if (!fs.existsSync(to)) fs.mkdirSync(to); + fs.readdirSync(from).forEach(element => { + + let fileFrom = path.join(from, element); + let fileTo = path.join(to, element); + + let stats = fs.lstatSync(fileFrom); + if (stats.isFile() || stats.isSymbolicLink()) { + if (copy) fs.copyFileSync(fileFrom, fileTo); + else fs.renameSync(fileFrom, fileTo); + } + else { + moveFolderSync(fileFrom, fileTo); + } + }); + // ALl files moved, removed dir. + if (!copy) fs.rmdirSync(from); + +} diff --git a/bin/update/update-steps.js b/bin/update/update-steps.js new file mode 100644 index 00000000..c8f8c7f2 --- /dev/null +++ b/bin/update/update-steps.js @@ -0,0 +1,46 @@ +const readline = require('readline'); +let rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: true +}); + +var steps = [ + 'Update version in package.json in nodegame', + 'Update CHANGELOG file in nodegame', + 'Update version in nodegame-installer', + 'Git push nodegame to master', + 'Check tests on Travis', + 'Upload nodegame-installer to website', + 'Update version in index.htm and upload it to website', + 'Update changelog.htm and upload it to website', + 'Commit and push changes to nodegame-website', + 'Update nodegame-heroku package.json version', + 'Tweet about new version :)' +]; + +var counter = 0; +function step(str) { + return new Promise((resolve) => { + rl.question(' ' + (counter + 1) + '. ' + steps[counter++], () => { + resolve(); + if (counter >= steps.length) { + console.log(); + console.log(' Done!'); + rl.close(); + } + }); + }); +}; + +console.log(); + +step() + .then(() => { return step(); }) + .then(() => { return step(); }) + .then(() => { return step(); }) + .then(() => { return step(); }) + .then(() => { return step(); }) + .then(() => { return step(); }) + .then(() => { return step(); }) + .then(() => { return step(); }); diff --git a/bin/update_version.sh b/bin/update/update_version.sh similarity index 100% rename from bin/update_version.sh rename to bin/update/update_version.sh diff --git a/conf/http.js b/conf/http.js index 5f5f9c17..c2e17367 100644 --- a/conf/http.js +++ b/conf/http.js @@ -1,7 +1,7 @@ /** * # http.js * - * Copyright(c) 2013 Stefano Balietti + * Copyright(c) 2020 Stefano Balietti * MIT Licensed * * Configuration file for ServerNode in nodegame-server. @@ -10,8 +10,9 @@ module.exports = configure; function configure(app, servernode) { - // Nothing extra to configure. - // Default configuration set already in: + + // Edit this file to modify the default configuration options in: // node_modules/nodegame-server/conf/http.js + return true; }; diff --git a/conf/loggers.js b/conf/loggers.js index 3d1e7eac..472aa322 100644 --- a/conf/loggers.js +++ b/conf/loggers.js @@ -1,46 +1,60 @@ +/** + * # servernode.js + * + * Copyright(c) 2020 Stefano Balietti + * MIT Licensed + * + * Configuration file for ServerNode in nodegame-server. + * --- + */ module.exports = configure; -var path = require('path'); +const path = require('path'); -function configure (loggers) { +function configure(loggers) { -// var config = { -// levels: { -// silly: 0, -// verbose: 1, -// info: 2, -// data: 3, -// warn: 4, -// debug: 5, -// error: 6 -// }, -// colors: { -// silly: 'magenta', -// verbose: 'cyan', -// info: 'green', -// data: 'grey', -// warn: 'yellow', -// debug: 'blue', -// error: 'red' -// } -// }; + // Edit this file to modify the default configuration options in: + // node_modules/nodegame-server/conf/loggers.js -// var rootDir = path.resolve(__dirname, '..'); -// var logDir = rootDir + '/log/'; -// -// loggers.add('ultimatumchannel', { -// console: { -// level: 'silly', -// colorize: true, -// }, -// file: { -// level: 'silly', -// timestamp: true, -// filename: logDir + 'channel', -// maxsize: 1000, -// maxFiles: 10, -// }, -// }); -// - return true; + // For instance: + + // let config = { + // levels: { + // silly: 0, + // verbose: 1, + // info: 2, + // data: 3, + // warn: 4, + // debug: 5, + // error: 6 + // }, + // colors: { + // silly: 'magenta', + // verbose: 'cyan', + // info: 'green', + // data: 'grey', + // warn: 'yellow', + // debug: 'blue', + // error: 'red' + // } + // }; + + // let rootDir = path.resolve(__dirname, '..'); + // let logDir = rootDir + '/log/'; + // + // loggers.add('ultimatumchannel', { + // console: { + // level: 'silly', + // colorize: true, + // }, + // file: { + // level: 'silly', + // timestamp: true, + // filename: logDir + 'channel', + // maxsize: 1000, + // maxFiles: 10, + // }, + // }); + + return true; } diff --git a/conf/servernode.js b/conf/servernode.js index 54ad0e89..645c5f2c 100644 --- a/conf/servernode.js +++ b/conf/servernode.js @@ -1,7 +1,7 @@ /** * # servernode.js * - * Copyright(c) 2017 Stefano Balietti + * Copyright(c) 2020 Stefano Balietti * MIT Licensed * * Configuration file for ServerNode in nodegame-server. @@ -10,11 +10,13 @@ module.exports = configure; function configure(servernode) { - // Extra configuration goes here, e.g.: - // servernode.port = 80; - // Default configuration set already in: + // Edit this file to modify the default configuration options in: // node_modules/nodegame-server/conf/servernode.js + // Extra configuration goes here, e.g.: + // servernode.port = 80; + // servernode.homePage = false; + return true; } diff --git a/conf/sio.js b/conf/sio.js index 64012fa7..ba122f02 100644 --- a/conf/sio.js +++ b/conf/sio.js @@ -1,7 +1,7 @@ /** * # sio.js * - * Copyright(c) 2013 Stefano Balietti + * Copyright(c) 2020 Stefano Balietti * MIT Licensed * * Configuration file for the Socket.io server in nodegame-server. @@ -10,8 +10,9 @@ module.exports = configure; function configure(sio, servernode) { - // Nothing extra to configure. - // Default configuration set already in: + + // Edit this file to modify the default configuration options in: // node_modules/nodegame-server/conf/sio.js + return true; }; diff --git a/games_available/README.md b/games_available/README.md new file mode 100644 index 00000000..8aab11c2 --- /dev/null +++ b/games_available/README.md @@ -0,0 +1,3 @@ +## Games Available Directory + +Games need to be symlinked from games/ folder to be enabled. diff --git a/launcher.js b/launcher.js index 8d947ac1..6251ed8e 100644 --- a/launcher.js +++ b/launcher.js @@ -1,6 +1,6 @@ /** * # Launcher file for nodeGame Server - * Copyright(c) 2017 Stefano Balietti + * Copyright(c) 2011-2020 Stefano Balietti * MIT Licensed * * Load configuration options and start the server @@ -11,12 +11,12 @@ "use strict"; // Modules. -var fs = require('fs'); -var path = require('path'); -var exec = require('child_process').exec; -var J = require('JSUS').JSUS; +const fs = require('fs'); +const path = require('path'); +const exec = require('child_process').exec; +const J = require('JSUS').JSUS; -var NDDB; +// TODO: refactor, eliminate var; check new Commander options. // Load commander. var program = require('commander'); @@ -33,12 +33,9 @@ var sn; // ServerNode options. var options; -// Conf file for all variables. -var confFile; - // Other local options. var confDir, logDir, gamesDir, debug, infoQuery, runTests; -var nClients, clientType, killServer, auth, wait; +var nClients, clientType, killServer, auth, wait, port; var codesDb; var cert, key; @@ -59,13 +56,11 @@ ignoredOptions = []; // Defaults. -confFile = null; - -confDir = './conf'; -logDir = './log'; -gamesDir = './games'; -debug = false; -infoQuery = false; +confDir = path.resolve(__dirname, 'conf'); +logDir = path.resolve(__dirname, 'log'); +gamesDir = path.resolve(__dirname, 'games'); +debug = undefined; +infoQuery = undefined; nClients = 4; clientType = 'autoplay'; @@ -88,57 +83,71 @@ program 'Sets the configuration directory') .option('-l, --logDir ', 'Sets the log directory') + .option('-L, --logLevel ', + 'Sets the log level. Values: error(default)|warn|info|silly') .option('-g, --gamesDir ', 'Sets the games directory') .option('-d, --debug', 'Enables the debug mode') - .option('-i, --infoQuery', + .option('-i, --infoQuery [false]', 'Enables getting information via query-string ?q=') .option('-b, --build [components]', 'Rebuilds the specified components', list) .option('-s, --ssl [path-to-ssl-dir]', 'Starts the server with SSL encryption') + .option('-f, --default [channel]', + 'Sets the default channel') + .option('-P, --port [port]', + 'Sets the port of the server') + // Connect phantoms. .option('-p, --phantoms ', - 'Connect phantoms to the specified channel') - .option('-n, --nClients ', - 'Sets the number of clients phantoms to connect (default: 4)') - .option('-t, --clientType ', - 'Sets the client type of connecting phantoms (default: autoplay)') - .option('-T, --runTests', - 'Run tests after all phantoms have reached game over ' + - '(overwrites settings.js in test/ folder') - .option('-k, --killServer', - 'Kill server after all phantoms have reached game over') - .option('-a --auth [option]', - 'Phantoms pass through /auth/. Options: createNew|new|' + - 'nextAvailable|next|code|id:code&pwd:password|file:path/to/file. ' + - 'Default: "new".') - .option('-w --wait [milliseconds]', - 'Waits before connecting the next phantom. Default: 1000') + 'Connect phantoms to the specified channel **DISCONTINUED**') + // .option('-n, --nClients ', + // 'Sets the number of clients phantoms to connect (default: 4)') + // .option('-t, --clientType ', + // 'Sets the client type of connecting phantoms (default: autoplay)') + // .option('-T, --runTests', + // 'Run tests after all phantoms are game-over ' + + // '(overwrites settings.js in test/)') + // .option('-k, --killServer', + // 'Kill server after all phantoms are game-over') + // .option('-a --auth [option]', + // 'Phantoms auth options. Values: new(default)|createNew|' + + // 'nextAvailable|next|code|id:code&pwd:password|file:path/to/file.') + // .option('-w --wait [milliseconds]', + // 'Waits before connecting the next phantom. Default: 1000') .parse(process.argv); +// User options (Commander >= 7). +let opts = program.opts(); + +if (opts.phantoms) { + console.log('***Err: option --phantoms no longer supported. ' + + 'PhantomJS support discontinued.'); + return false; +} -if (program.confFile) { - if (!fs.existsSync(program.ConfFile)) { - return printErr('--confFile ' + program.confFile + ' not found.'); +if (opts.confFile) { + if (!fs.existsSync(opts.confFile)) { + return printErr('--confFile ' + opts.confFile + ' not found.'); } - options = require(program.confFile); + options = require(opts.confFile); if ('object' !== typeof options) { - return printErr('--confFile ' + program.confFile + ' did not return ' + + return printErr('--confFile ' + opts.confFile + ' did not return ' + 'a configuration object.'); } - if (program.confDir) ignoredOptions.push('--confDir'); - if (program.logDir) ignoredOptions.push('--logDir'); - if (program.gamesDir) ignoredOptions.push('--gamesDir'); - if (program.debug) ignoredOptions.push('--debug'); - if (program.infoQuery) ignoredOptions.push('--infoQuery'); + if (opts.confDir) ignoredOptions.push('--confDir'); + if (opts.logDir) ignoredOptions.push('--logDir'); + if (opts.gamesDir) ignoredOptions.push('--gamesDir'); + if (opts.debug) ignoredOptions.push('--debug'); + if (opts.infoQuery) ignoredOptions.push('--infoQuery'); } else { @@ -155,67 +164,82 @@ else { // Adds a new game directory (Default is nodegame-server/games). servernode.gamesDirs.push(gamesDir); - // Sets the debug mode, exceptions will be thrown. - servernode.debug = debug; - // Can get information from /?q= - servernode.enableInfoQuery = infoQuery; + + // Sets the debug mode, exceptions will be thrown, if TRUE. + if ('undefined' !== typeof debug) { + servernode.debug = debug; + } + // Can get information from /?q=, if TRUE + if ('undefined' !== typeof infoQuery) { + servernode.enableInfoQuery = infoQuery; + } // Basepath (without trailing slash). // servernode.basepath = '/mybasepath'; return true; }, - http: function(http) { - // Special configuration for Express goes here. - return true; - }, - sio: function(sio) { - // Special configuration for Socket.Io goes here here. - // Might not work in Socket.IO 1.x (check). - - // sio.set('transports', ['xhr-polling']); - // sio.set('transports', ['jsonp-polling']); - - // sio.set('transports', [ - // 'websocket' - // , 'flashsocket' - // , 'htmlfile' - // , 'xhr-polling' - // , 'jsonp-polling' - // ]); - - return true; - } + // http: function(http) { + // // Special configuration for Express goes here. + // return true; + // }, + // sio: function(sio) { + // // Special configuration for Socket.Io goes here here. + // // Might not work in Socket.IO 1.x (check). + // + // // sio.set('transports', ['xhr-polling']); + // // sio.set('transports', ['jsonp-polling']); + // + // // sio.set('transports', [ + // // 'websocket' + // // , 'flashsocket' + // // , 'htmlfile' + // // , 'xhr-polling' + // // , 'jsonp-polling' + // // ]); + // + // return true; + // } }; // Validate other options. - if (program.confDir) { - if (!fs.existsSync(program.confDir)) { - return printErr('--confDir ' + program.confDir + ' not found.'); + if (opts.confDir) { + if (!fs.existsSync(opts.confDir)) { + return printErr('--confDir ' + opts.confDir + ' not found.'); + } + confDir = opts.confDir; + } + if (opts.logDir) { + if (!fs.existsSync(opts.logDir)) { + return printErr('--logDir ' + opts.logDir + ' not found.'); } - confDir = program.confDir; + logDir = opts.logDir; } - if (program.logDir) { - if (!fs.existsSync(program.logDir)) { - return printErr('--logDir ' + program.logDir + ' not found.'); + if (opts.gamesDir) { + if (!fs.existsSync(opts.gamesDir)) { + return printErr('--gamesDir ' + opts.gamesDir + ' not found.'); } - logDir = program.logDir; + gamesDir = opts.gamesDir; } - if (program.gamesDir) { - if (!fs.existsSync(program.gamesDir)) { - return printErr('--gamesDir ' + program.gamesDir + ' not found.'); + if (opts.debug) debug = true; + + // Parse infoQuery. + if (opts.infoQuery) { + if ('boolean' === typeof opts.infoQuery) { + infoQuery = opts.infoQuery; + } + else { + let i = opts.infoQuery.toLowerCase(); + infoQuery = i === 'f' || i === 'false' || i === '0' ? false : true; } - gamesDir = program.gamesDir; } - if (program.debug) debug = true; - if (program.infoQuery) infoQuery = true; } // Validate general options. -if ('boolean' === typeof program.ssl) { +if ('boolean' === typeof opts.ssl) { options.ssl = true; } -else if ('string' === typeof program.ssl) { +else if ('string' === typeof opts.ssl) { options.ssl = (function(dir) { var ssl; @@ -242,65 +266,82 @@ else if ('string' === typeof program.ssl) { return ssl; - })(program.ssl); + })(opts.ssl); if (!options.ssl) return; } -if (program.nClients) { - if (!program.phantoms) ignoredOptions.push('--nClients'); +if (opts['default']) { + options.defaultChannel = opts['default']; +} + +if (opts.port) { + port = J.isInt(opts.port, 0); + if (!port) { + return printErr('--port ' + opts.port + + ' is not a positive number.'); + } + options.port = port; +} + +if (opts.logLevel) { + options.logLevel = opts.logLevel; +} + +if (opts.nClients) { + if (!opts.phantoms) ignoredOptions.push('--nClients'); else { - nClients = parseInt(program.nClients, 10); + nClients = parseInt(opts.nClients, 10); if (isNaN(nClients)) { - return printErr('--nClients ' + program.nClients + + return printErr('--nClients ' + opts.nClients + ' is invalid.'); } } } -if (program.clientType) { - if (!program.phantoms) ignoredOptions.push('--clientType'); - else clientType = program.clientType; +if (opts.clientType) { + if (!opts.phantoms) ignoredOptions.push('--clientType'); + else clientType = opts.clientType; } -if (program.runTests) { - if (!program.runTests) ignoredOptions.push('--runTests'); - else runTests = program.runTests; +if (opts.runTests) { + if (!opts.runTests) ignoredOptions.push('--runTests'); + else runTests = opts.runTests; } -if (program.killServer) { - if (!program.phantoms) ignoredOptions.push('--killServer'); +if (opts.killServer) { + if (!opts.phantoms) ignoredOptions.push('--killServer'); else killServer = true; } -if (program.wait) { - if (!program.phantoms) { +if (opts.wait) { + if (!opts.phantoms) { ignoredOptions.push('--wait'); } else { - if (true === program.wait) { + if (true === opts.wait) { wait = 1000; } else { - wait = J.isInt(program.wait, 0); + wait = J.isInt(opts.wait, 0); if (false === wait) { printErr('--wait must be a positive number or undefined. ' + - 'Found:' + program.wait); + 'Found:' + opts.wait); process.exit(); } } } } -if (program.auth) { - if (!program.phantoms) { +if (opts.auth) { + if (!opts.phantoms) { ignoredOptions.push('--auth'); } - else if ('string' === typeof program.auth) { + else if ('string' === typeof opts.auth) { auth = (function(idIdx, pwdIdx) { var auth; - idIdx = program.auth.indexOf('id:'); + idIdx = opts.auth.indexOf('id:'); if (idIdx === 0) { - pwdIdx = program.auth.indexOf('&pwd:'); + pwdIdx = opts.auth.indexOf('&pwd:'); if (pwdIdx !== -1) { auth = { - id: program.auth.substr(3, (pwdIdx-3)), - pwd: program.auth.substr(pwdIdx+5) + id: opts.auth.substr(3, (pwdIdx-3)), + pwd: opts.auth.substr(pwdIdx+5) }; } else { @@ -309,67 +350,65 @@ if (program.auth) { process.exit(); } } - else if (program.auth === 'new') { + else if (opts.auth === 'new') { auth = 'createNew'; } - else if (program.auth === 'next') { + else if (opts.auth === 'next') { auth = 'nextAvailable'; } - else if (program.auth.indexOf('file:') === 0) { - NDDB = require('NDDB').NDDB; + else if (opts.auth.indexOf('file:') === 0) { + const NDDB = require('NDDB').NDDB; codesDb = new NDDB(); - codesDb.loadSync(program.auth.substr(5)); + codesDb.loadSync(opts.auth.substr(5)); if (!codesDb.size()) { - printErr('--auth no auth codes found: program.auth'); + printErr('--auth no auth codes found: opts.auth'); process.exit(); } codesDb = codesDb.db; } else { - auth = program.auth; + auth = opts.auth; } return auth; })(); } - else if ('boolean' === typeof program.auth) { + else if ('boolean' === typeof opts.auth) { auth = 'createNew'; } - else if ('number' === typeof program.auth || - 'object' === typeof program.auth) { + else if ('number' === typeof opts.auth || + 'object' === typeof opts.auth) { - auth = program.auth; + auth = opts.auth; } } // Rebuild server files as needed. -if (program.build) { +if (opts.build) { (function() { - var i, len, opts, modules; - var info, module, out; - var cssAlso, cssOnly; + let cssAlso, cssOnly; - len = program.build.length; + let len = opts.build.length; if (!len) { - program.build = [ 'client' ]; + opts.build = [ 'client' ]; } else if (len === 1) { - if (program.build[0] === 'all') { + if (opts.build[0] === 'all') { // Client will be done anyway. - program.build = [ 'window', 'widgets', 'JSUS', 'NDDB' ]; + opts.build = [ 'window', 'widgets', 'JSUS', 'NDDB' ]; } - else if (program.build[0] === 'css') { + else if (opts.build[0] === 'css') { cssOnly = true; } } - - info = J.resolveModuleDir('nodegame-server', __dirname); + + let info = J.resolveModuleDir('nodegame-server', __dirname); info = require(path.resolve(info, 'bin', 'info.js')); - + if (!cssOnly) { - out = 'nodegame-full.js'; + let out = 'nodegame-full.js'; - modules = { + let modules = { window: 'window', client: 'client', widgets: 'widgets', @@ -377,11 +416,11 @@ if (program.build) { NDDB: 'NDDB', css: 'css' }; - + // Starting build. - i = -1, len = program.build.length; + let i = -1; for ( ; ++i < len ; ) { - module = program.build[i]; + let module = opts.build[i]; if (!modules[module]) { throw new Error('unknown build component: ' + module); } @@ -398,9 +437,7 @@ if (program.build) { continue; } - opts = { all: true, clean: true }; - - info.build[module](opts); + info.build[module]({ all: true, clean: true }); console.log(''); } // Do client last. @@ -409,12 +446,12 @@ if (program.build) { all: true, output: out }); - J.copyFile(info.modulesDir.client + 'build/' + out, - info.serverDir.build + out); + J.copyFile(path.resolve(info.modulesDir.client, 'build', out), + path.resolve(info.serverDir.build, out)); console.log(info.serverDir.build + out + ' rebuilt.'); console.log(''); } - + if (cssAlso || cssOnly) { info.build.css(info.serverDir.css, function(err) { if (!err) { @@ -427,7 +464,7 @@ if (program.build) { startServer(); } - })(program.build); + })(); } else { startServer(); @@ -439,7 +476,11 @@ function startServer() { // Print warnings, if any. printIgnoredOptions(); - console.log('nodeGame v.' + require('./package.json').version); + console.log('nodeGame v.' + version); + // Add nodeGame version (might be higher than server version) to options. + options.nodeGameVersion = version; + + options.nodeGameRoot = __dirname; // Start server, options parameter is optional. sn = new ServerNode(options); @@ -452,9 +493,9 @@ function startServer() { var numFinished; // If there are not bots to add returns. - if (!program.phantoms) return; + if (!opts.phantoms) return; - gameName = program.phantoms; + gameName = opts.phantoms; channel = sn.channels[gameName]; if (!channel) { printErr('channel ' + gameName + ' was not found.'); @@ -480,7 +521,7 @@ function startServer() { console.log(); if (runTests) { - command = gameDir + 'node_modules/.bin/mocha'; + command = path.resolve(gameDir, 'node_modules', '.bin', 'mocha'); if (fs.existsSync(command)) { // Write and backup settings file. @@ -504,7 +545,7 @@ function startServer() { }; } - + startPhantom = function(i) { var str, config; str = 'Connecting phantom #' + (i+1) + '/' + nClients; @@ -539,7 +580,7 @@ function startServer() { phantoms = [], numFinished = 0; for (i = 0; i < nClients; ++i) { if (i > 0 && wait) { - (function(i) { + (function(i) { setTimeout(function() { startPhantom(i); }, wait * i); })(i); } @@ -578,14 +619,14 @@ function printErr(err) { function writeSettingsFile(gameDir) { var settings, settingsFile, bak; settings = 'module.exports = { numPlayers: ' + nClients + ' };'; - settingsFile = gameDir + 'test/settings.js'; + settingsFile = path.resolve(gameDir, 'test', 'settings.js'); // Make a backup of existing settings file, if found. if (fs.existsSync(settingsFile)) { bak = fs.readFileSync(settingsFile).toString(); fs.writeFileSync(settingsFile + '.bak', bak); } // Write updated settings file. - fs.writeFileSync(gameDir + 'test/settings.js', settings); + fs.writeFileSync(path.resolve(gameDir, 'test', 'settings.js'), settings); } // Exports the ServerNode instance. diff --git a/package.json b/package.json index 164aec5c..a912f1f5 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,47 @@ { - "name": "nodegame" - , "description": "Starter package for nodegame: install scripts, configurations and development files." - , "version": "4.0.0" - , "homepage": "http://nodegame.org" - , "author": "Stefano Balietti " - , "contributors": [ - { "name": "Stefano Balietti", "email": "futur.dorko@gmail.com" } - ] - , "repository": { - "type": "git" - , "url": "https://github.com/nodeGame/nodegame.git" + "name": "nodegame", + "description": "Fast, scalable JavaScript for large-scale, online, multiplayer, real-time games and experiments.", + "version": "7.1.0", + "homepage": "http://nodegame.org", + "author": "Stefano Balietti ", + "contributors": [ + { + "name": "Stefano Balietti", + "email": "futur.dorko@gmail.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/nodeGame/nodegame.git" + }, + "engines": { + "node": ">= 10.0.0" + }, + "dependencies": { + "nodegame-client": ">=7.1.0", + "nodegame-server": ">=7.0.3", + "nodegame-window": ">=7.0.0", + "nodegame-widgets": ">=7.0.2", + "nodegame-requirements": ">=7.0.0", + "nodegame-game-template": ">=7.0.0", + "nodegame-monitor": ">=7.0.0", + "nodegame-generator": ">=7.0.0", + "ultimatum-game": ">=7.0.0", + "nodegame-mturk": ">=7.0.0", + "JSUS": ">=1.1.0", + "NDDB": ">=3.0.2", + "nodegame-db": "*", + "nodegame-mongodb": "*", + "commander": "^7.0.0", + "fs-extra": "*", + "smoosh": "0.4.0" + }, + "devDependencies": { + "mocha": ">= 0.3.0", + "should": "4.4.1" + }, + "license": "MIT", + "scripts": { + "start": "node launcher.js" } - , "engines": { "node": ">= 0.10.0" } - , "dependencies" : { - "commander": "*", - "fs-extra": "*", - "ya-csv": "*", - "smoosh": "0.4.0" - } - , "license": "MIT" - , "scripts": { - "start": "node launcher.js" - } } diff --git a/test/launcher-autoplay.js b/test/launcher-autoplay.js index 805c17aa..b1d67799 100644 --- a/test/launcher-autoplay.js +++ b/test/launcher-autoplay.js @@ -16,10 +16,6 @@ if (process.argv.length < 3) { var gameName = process.argv[2]; var gameFolder = process.argv[3] || gameName; -console.log(process.argv); -console.log('---------------------------'); - - // Load the Node.js path object. var path = require('path');