├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── lovelier.js ├── lib ├── compiler.js ├── cross.js ├── glob.js ├── logger.js ├── love.js └── watch.js ├── package-lock.json ├── package.json └── test └── game └── main.moon /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: NPM Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | - run: npm install 17 | 18 | publish-npm: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | registry-url: https://registry.npmjs.org/ 27 | - run: npm publish 28 | env: 29 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | *.lua 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Patrick R 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lovelier 2 | 3 | [![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com) 4 | 5 | A Love2D reloader with Moonscript support. 6 | 7 | ## Dependencies 8 | 9 | * [Love2D](https://love2d.org) 10 | * [Moonscript](http://moonscript.org) (optional) 11 | 12 | ## Installation 13 | 14 | ```bash 15 | $> npm install -g lovelier 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```bash 21 | $> lovelier --help 22 | 23 | Usage: lovelier [options] [command] 24 | 25 | Options: 26 | -V, --version output the version number 27 | -m, --moon use moonscript 28 | -b, --bin love binary path 29 | -h, --help output usage information 30 | 31 | Commands: 32 | dev run the game in development mode 33 | ``` 34 | 35 | ### Running your Love2D Project 36 | 37 | ```bash 38 | $> cd myProject 39 | $> lovelier dev . 40 | ``` 41 | 42 | #### With Moonscript 43 | 44 | ```bash 45 | $> cd myProject 46 | $> lovelier --moon dev . 47 | ``` 48 | 49 | #### With a specific Love2D path 50 | 51 | ```bash 52 | $> cd myProject 53 | $> lovelier --bin /usr/local/bin/love dev . 54 | ``` 55 | 56 | ## Patform notes 57 | 58 | ### OSX 59 | 60 | Running love can trigger a `unidentified developer` error, which can be solved by allowing love in the [System Preferences](https://www.macworld.co.uk/how-to/mac-software/mac-app-unidentified-developer-3669596/) -------------------------------------------------------------------------------- /bin/lovelier.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const path = require('path'); 5 | const { version } = require('../package.json'); 6 | const { watch } = require('../lib/watch'); 7 | const Love = require('../lib/love'); 8 | const Compiler = require('../lib/compiler'); 9 | const glob = require('../lib/glob'); 10 | const logger = require('../lib/logger')('lovelier'); 11 | 12 | program 13 | .name('lovelier') 14 | .version(version) 15 | .option('-m, --moon', 'use moonscript') 16 | .option('-b, --bin ', 'love binary path'); 17 | 18 | program 19 | .command('dev ') 20 | .description('run the game in development mode') 21 | .action((folder) => { 22 | folder = path.resolve(folder); 23 | 24 | const { moon, bin } = program.opts(); 25 | const extension = moon ? 'moon' : 'lua'; 26 | const game = Love(folder, { bin }); 27 | const compile = Compiler(extension, folder); 28 | 29 | logger.info('running game in development mode'); 30 | watch(glob(folder, extension), [ compile ], () => { 31 | logger.info('restarting game'); 32 | game.restart() 33 | }); 34 | }); 35 | 36 | program.parse(process.argv); 37 | -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | const logger = require('./logger')('compiler'); 2 | const { exec } = require('child_process'); 3 | 4 | const SKIP = {}; 5 | const COMPILERS = { 6 | 'moon': 'moonc', 7 | 'lua': SKIP 8 | }; 9 | 10 | function Compiler(extension, folder) { 11 | return function compile(cb) { 12 | if (COMPILERS[extension] === SKIP) { 13 | logger.verbose('skipping compilation'); 14 | return cb(); 15 | } 16 | 17 | const cmd = `${COMPILERS[extension]} ${folder}`; 18 | 19 | logger.info(`compiling ${extension} files`); 20 | 21 | exec(cmd, (err) => { 22 | if (err) { logger.error(err); } 23 | cb(err); 24 | }); 25 | }; 26 | } 27 | 28 | Compiler.supports = (ext) => !!COMPILERS[ext]; 29 | 30 | module.exports = Compiler; -------------------------------------------------------------------------------- /lib/cross.js: -------------------------------------------------------------------------------- 1 | function has(arr, str) { 2 | return arr.indexOf(str) >= 0; 3 | } 4 | 5 | function crossEnv() { 6 | let calls = []; 7 | 8 | let ref = {}; 9 | 10 | ref['on'] = (...args) => { 11 | const platforms = args.slice(0, -1); 12 | const fn = args.slice(-1)[0]; 13 | calls.push({ platforms, fn }); 14 | return ref; 15 | } 16 | 17 | ref['build'] = () => { 18 | for (let {platforms, fn} of calls) { 19 | if (has(platforms, process.platform) || has(platforms, '_')) { 20 | return fn; 21 | } 22 | } 23 | return null; 24 | }; 25 | 26 | return ref; 27 | }; 28 | 29 | module.exports = { crossEnv }; -------------------------------------------------------------------------------- /lib/glob.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = (folder, extension) => { 4 | return path.join(folder, `**/*.${extension}`); 5 | }; -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | if (!process.env.DEBUG) { 2 | process.env.DEBUG = '*'; 3 | } 4 | 5 | const debug = require('debug'); 6 | 7 | module.exports = function (name = 'game') { 8 | return { 9 | info: debug(`${name}:info`), 10 | error: debug(`${name}:error`), 11 | verbose: debug(`${name}:verbose`) 12 | }; 13 | }; -------------------------------------------------------------------------------- /lib/love.js: -------------------------------------------------------------------------------- 1 | const logger = require('./logger')('love'); 2 | const { spawn, execSync } = require('child_process'); 3 | const { crossEnv } = require('./cross'); 4 | 5 | module.exports = function (folder, opts = {}) { 6 | let child = null; 7 | let firstRun = true; 8 | 9 | const { bin = 'love' } = opts; 10 | 11 | const safe = (fn) => { 12 | try { fn() } catch(e) {} 13 | } 14 | 15 | const spawnLove = crossEnv() 16 | /* --- MACOS --- */ 17 | .on('darwin', () => { 18 | let args = [ '-W', '-n', '-a', bin, '--args', folder, '--console' ]; 19 | if (!firstRun) { args.unshift('-j'); } 20 | 21 | return spawn('open', args); 22 | }) 23 | /* --- Others --- */ 24 | .on('_', () => spawn(bin, [folder])) 25 | .build(); 26 | 27 | const killLove = crossEnv() 28 | /* --- MACOS --- */ 29 | .on('darwin', (child) => { 30 | child.kill(); 31 | safe(() => execSync('killall love')); 32 | }) 33 | /* --- Others --- */ 34 | .on('_', (child) => child.kill()) 35 | .build(); 36 | 37 | const stop = () => { 38 | if (child) { 39 | killLove(child); 40 | child = null; 41 | } 42 | }; 43 | 44 | const start = () => { 45 | stop(); 46 | // $ open -n -j -a love --args 47 | child = spawnLove(); 48 | child.stderr.on('data', (data) => { logger.error(data.toString()); }); 49 | child.stdout.on('data', (data) => { logger.verbose(data.toString()); }); 50 | 51 | firstRun = false; 52 | }; 53 | 54 | const restart = start; 55 | 56 | ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException'].forEach((sig) => { 57 | process.on(sig, () => { 58 | stop(); 59 | process.exit(1); 60 | }); 61 | }); 62 | 63 | return { start, stop, restart }; 64 | }; 65 | -------------------------------------------------------------------------------- /lib/watch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const gaze = require('gaze'); 4 | const Queue = require('queue'); 5 | const logger = require('./logger')('watch'); 6 | 7 | function noop(cb) { 8 | cb(); 9 | } 10 | 11 | function watch(sources, preprocessors = [], notifyEnd) { 12 | let success = true; 13 | let queue = Queue({ concurrency: 1, autostart: true }); 14 | 15 | const onComplete = (cb) => { 16 | notifyEnd(); 17 | cb(); 18 | }; 19 | 20 | queue.on('success', function (result, job) { 21 | if (success) { 22 | success = false; 23 | logger.info('done'); 24 | queue.push(onComplete); // trigger the callback by adding it to the queue 25 | } 26 | }); 27 | 28 | const startProcessing = () => { 29 | success = true; 30 | 31 | logger.info(`running preprocessors`); 32 | preprocessors.forEach(pp => { 33 | const action = (cb) => { 34 | pp((err) => { 35 | // Errors will stop the queue, we simply mark this round as failed 36 | if (err) { success = false; } 37 | cb(); 38 | }); 39 | }; 40 | 41 | queue.push(action); 42 | }); 43 | 44 | // In the event no preprocessor is passed on, this allows the queue to be triggered regardless 45 | queue.push(noop); 46 | }; 47 | 48 | logger.info(`watching ${sources}`); 49 | 50 | gaze(sources, function(err) { 51 | if (err) { 52 | logger.error(err); 53 | process.exit(1); 54 | } 55 | 56 | this.on('all', function(event, filepath) { 57 | logger.info(filepath.replace(sources, '') + ' was ' + event); 58 | startProcessing(); 59 | }); 60 | }); 61 | 62 | startProcessing(); 63 | }; 64 | 65 | module.exports = { watch }; 66 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lovelier", 3 | "version": "1.0.4", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 11 | }, 12 | "brace-expansion": { 13 | "version": "1.1.11", 14 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 15 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 16 | "requires": { 17 | "balanced-match": "^1.0.0", 18 | "concat-map": "0.0.1" 19 | } 20 | }, 21 | "commander": { 22 | "version": "4.1.0", 23 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz", 24 | "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==" 25 | }, 26 | "concat-map": { 27 | "version": "0.0.1", 28 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 29 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 30 | }, 31 | "debug": { 32 | "version": "4.1.1", 33 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 34 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 35 | "requires": { 36 | "ms": "^2.1.1" 37 | } 38 | }, 39 | "fs.realpath": { 40 | "version": "1.0.0", 41 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 42 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 43 | }, 44 | "gaze": { 45 | "version": "1.1.3", 46 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", 47 | "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", 48 | "requires": { 49 | "globule": "^1.0.0" 50 | } 51 | }, 52 | "glob": { 53 | "version": "7.1.6", 54 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 55 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 56 | "requires": { 57 | "fs.realpath": "^1.0.0", 58 | "inflight": "^1.0.4", 59 | "inherits": "2", 60 | "minimatch": "^3.0.4", 61 | "once": "^1.3.0", 62 | "path-is-absolute": "^1.0.0" 63 | } 64 | }, 65 | "globule": { 66 | "version": "1.3.0", 67 | "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz", 68 | "integrity": "sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==", 69 | "requires": { 70 | "glob": "~7.1.1", 71 | "lodash": "~4.17.10", 72 | "minimatch": "~3.0.2" 73 | } 74 | }, 75 | "inflight": { 76 | "version": "1.0.6", 77 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 78 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 79 | "requires": { 80 | "once": "^1.3.0", 81 | "wrappy": "1" 82 | } 83 | }, 84 | "inherits": { 85 | "version": "2.0.4", 86 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 87 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 88 | }, 89 | "lodash": { 90 | "version": "4.17.15", 91 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 92 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 93 | }, 94 | "minimatch": { 95 | "version": "3.0.4", 96 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 97 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 98 | "requires": { 99 | "brace-expansion": "^1.1.7" 100 | } 101 | }, 102 | "ms": { 103 | "version": "2.1.2", 104 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 105 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 106 | }, 107 | "once": { 108 | "version": "1.4.0", 109 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 110 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 111 | "requires": { 112 | "wrappy": "1" 113 | } 114 | }, 115 | "path-is-absolute": { 116 | "version": "1.0.1", 117 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 118 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 119 | }, 120 | "queue": { 121 | "version": "6.0.1", 122 | "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.1.tgz", 123 | "integrity": "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==", 124 | "requires": { 125 | "inherits": "~2.0.3" 126 | } 127 | }, 128 | "wrappy": { 129 | "version": "1.0.2", 130 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 131 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lovelier", 3 | "version": "1.0.5", 4 | "description": "A Love2D live-reloader with Moonscript support", 5 | "main": "index.js", 6 | "bin": { 7 | "lovelier": "./bin/lovelier.js" 8 | }, 9 | "keywords": [ 10 | "lua", 11 | "moonscript", 12 | "love2d", 13 | "game", 14 | "reload" 15 | ], 16 | "author": "patrick@tronica.io", 17 | "license": "MIT", 18 | "dependencies": { 19 | "commander": "^4.1.0", 20 | "debug": "^4.1.1", 21 | "gaze": "^1.1.3", 22 | "queue": "^6.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/game/main.moon: -------------------------------------------------------------------------------- 1 | love.draw = (a) -> 2 | love.graphics.print("Hello World", 400, 300) 3 | --------------------------------------------------------------------------------