├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── cmd.js ├── img.jpg ├── index.js └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | img.jpg 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cyberhobo [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] 2 | 3 | [travis-image]: https://img.shields.io/travis/feross/cyberhobo/master.svg 4 | [travis-url]: https://travis-ci.org/feross/cyberhobo 5 | [npm-image]: https://img.shields.io/npm/v/cyberhobo.svg 6 | [npm-url]: https://npmjs.org/package/cyberhobo 7 | [downloads-image]: https://img.shields.io/npm/dm/cyberhobo.svg 8 | [downloads-url]: https://npmjs.org/package/cyberhobo 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | #### Offline `git push` and `npm publish` for cyberhobos 13 | 14 | ![all aspiring cyberhobos seek to follow the ways of the dominictarr](https://raw.githubusercontent.com/feross/cyberhobo/master/img.jpg) 15 | 16 | This module is gifted to cyberhobo extraordinaire, dominictarr. 17 | 18 | ### features 19 | 20 | - Run `git push` and `npm publish` while you're offline! 21 | - Next time you're online, all queued commands will run in order. 22 | 23 | ### usage 24 | 25 | 1. Install it globally. 26 | 27 | ```bash 28 | npm install -g cyberhobo 29 | ``` 30 | 31 | 2. Set up bash/zsh aliases for `npm` and `git` so `cyberhobo` will run first. 32 | 33 | ```bash 34 | alias git='cyberhobo git' 35 | alias npm='cyberhobo npm' 36 | ``` 37 | 38 | `cyberhobo` will detect if you're offline and intercept `git push` and `npm publish` 39 | commands, **queueing them to run later** when you're back online. If you're online or 40 | if you run a non `push`/`publish` command, then it will run normally. 41 | 42 | #### when you're back online 43 | 44 | If you're back in civilization and you have an internet connection, the next time you run 45 | any `git` or `npm` command, `cyberhobo` will run all the commands that were queued up 46 | while you were offline. They will run **in order**. 47 | 48 | If any of them fails with a non-zero exit code then `cyberhobo` bails, printing out the remaining 49 | commands so you can run them manually. (TODO) 50 | 51 | ### example 52 | 53 | ```bash 54 | $ touch test.txt 55 | $ git add test.txt 56 | 57 | # oh no! lost internet connection now. keep working... 58 | 59 | $ git commit -m "wrote some awesome code" 60 | [master 4f5f136] wrote some awesome code 61 | 1 file changed, 0 insertions(+), 0 deletions(-) 62 | create mode 100644 test.txt 63 | 64 | $ git push 65 | CYBER HOBO ACTIVATED! Command saved for later! 66 | 67 | $ npm publish 68 | CYBER HOBO ACTIVATED! Command saved for later! 69 | 70 | # more commits, pushes, etc., ... 71 | 72 | # later, we have internet again! Run any git/npm command to push queued commands! 73 | 74 | $ git status 75 | ============================================================ 76 | HEY, YOU HAVE INTERNET NOW! 77 | Time to re-run the commands you saved while you were offline 78 | ============================================================ 79 | 80 | ==== Running "git push" in /Users/feross/code/cyberhobo ==== 81 | 82 | Counting objects: 3, done. 83 | Delta compression using up to 8 threads. 84 | Compressing objects: 100% (2/2), done. 85 | Writing objects: 100% (2/2), 229 bytes | 0 bytes/s, done. 86 | Total 2 (delta 1), reused 0 (delta 0) 87 | To git@github.com:feross/cyberhobo.git 88 | 1174974..4f5f136 master -> master 89 | 90 | 91 | ==== Running "npm publish" in /Users/feross/code/cyberhobo === 92 | 93 | npm http PUT https://registry.npmjs.org/cyberhobo 94 | npm http 201 https://registry.npmjs.org/cyberhobo 95 | + cyberhobo@0.1.0 96 | 97 | CYBER HOBO MISSION COMPLETE: all up to date 98 | 99 | On branch master 100 | Your branch is up-to-date with 'origin/master'. 101 | ``` 102 | 103 | ### warning 104 | 105 | This may be a horrible idea. I don't know. 106 | 107 | ### license 108 | 109 | MIT. Copyright [Feross Aboukhadijeh](https://www.twitter.com/feross). 110 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var cyberhobo = require('../') 3 | 4 | var argv = process.argv.slice(2) 5 | cyberhobo(argv) 6 | -------------------------------------------------------------------------------- /img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/cyberhobo/a7daaf74d80c2573402d5dbb5236301ceb483ed6/img.jpg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! cyberhobo. MIT License. Feross Aboukhadijeh */ 2 | var chalk = require('chalk') 3 | var connectivity = require('connectivity') 4 | var cp = require('child_process') 5 | var fs = require('fs') 6 | var once = require('once') 7 | var path = require('path') 8 | var series = require('run-series') 9 | var waterfall = require('run-waterfall') 10 | 11 | var COMMANDS = { 12 | git: ['push'], 13 | npm: ['publish'] 14 | } 15 | var DATA_DIR = process.env.HOME + '/.cyberhobo' 16 | 17 | function run (argv, cwd, cb) { 18 | if (typeof cwd === 'function') { 19 | cb = cwd 20 | cwd = undefined 21 | } 22 | cb = cb || function () {} 23 | cb = once(cb) 24 | 25 | var child = cp.spawn(argv[0], argv.slice(1), { 26 | cwd: cwd, 27 | stdio: 'inherit' 28 | }) 29 | child.on('exit', function (code) { 30 | if (code === 0) { 31 | cb(null) 32 | } else { 33 | cb(new Error('Command `' + argv.join(' ') + '` exited with code ' + code)) 34 | } 35 | }) 36 | child.on('error', cb) 37 | } 38 | 39 | function cyberhobo (argv) { 40 | waterfall([ 41 | // Ensure that the data folder exists 42 | function (cb) { 43 | fs.mkdir(DATA_DIR, { recursive: true }, function (err) { 44 | if (err) { 45 | console.error('WARNING: Could not create ~/.cyberhobo folder') 46 | run(argv) // let the command run 47 | } 48 | cb(err) 49 | }) 50 | }, 51 | 52 | // Check connectivity 53 | function (cb) { 54 | connectivity(function (online) { 55 | cb(null, online) 56 | }) 57 | }, 58 | 59 | // Check writeahead log for commands to run 60 | function (online, cb) { 61 | if (!online) { 62 | return cb(null, online) 63 | } 64 | 65 | var entries 66 | try { 67 | entries = fs.readdirSync(DATA_DIR) 68 | } catch (err) { 69 | cb(new Error('Could not read from ~/.cyberhobo')) 70 | } 71 | 72 | entries.sort() 73 | 74 | var fns = [] 75 | 76 | if (entries.length) { 77 | console.log('============================================================') 78 | console.log(' HEY, YOU HAVE INTERNET NOW!') 79 | console.log('Time to re-run the commands you saved while you were offline') 80 | console.log('============================================================') 81 | } 82 | 83 | entries.forEach(function (entry) { 84 | var filename = path.join(DATA_DIR, entry) 85 | try { 86 | var data = JSON.parse(fs.readFileSync(filename, 'utf8')) 87 | } catch (err) { 88 | console.error('ERROR: Invalid data in ' + filename) 89 | } 90 | 91 | fns.push(function (cb) { 92 | console.log('') 93 | console.log('==== ' + chalk.bold.green('Running ') + chalk.red(data.command.join(' ')) + chalk.green(' in ' + data.cwd) + ' ====') 94 | console.log('') 95 | run(data.command, data.cwd, cb) 96 | try { 97 | fs.unlinkSync(filename) 98 | } catch (err) { 99 | console.error('ERROR: Could not remove cyberhobo file: ' + filename) 100 | } 101 | }) 102 | }) 103 | 104 | series(fns, function (err) { 105 | if (entries.length) { 106 | console.log('CYBER HOBO MISSION COMPLETE: all up to date') 107 | } 108 | cb(err, online) 109 | }) 110 | }, 111 | 112 | // Run the command, or save it for later 113 | function (online, cb) { 114 | var program = argv[0] 115 | var command = argv[1] 116 | 117 | if (!online && COMMANDS[program] && COMMANDS[program].indexOf(command) >= 0) { 118 | // we're offline and this is a command we want to save for when we're online 119 | console.log('CYBER HOBO ACTIVATED!') 120 | 121 | var data = { 122 | command: argv, 123 | cwd: process.cwd() 124 | } 125 | 126 | try { 127 | var filename = path.join(DATA_DIR, Date.now() + '.txt') 128 | fs.writeFileSync(filename, JSON.stringify(data), 'utf8') 129 | console.log('Command saved for later!') 130 | } catch (err) { 131 | console.error('WARNING: Could not write to ~/.cyberhobo/ -- running command now') 132 | run(argv) 133 | } 134 | } else { 135 | // we're online or this is a command we don't know about -- run the command now 136 | run(argv) 137 | } 138 | } 139 | 140 | ], function (err) { 141 | if (err) { 142 | console.error(err.message || err) 143 | } 144 | }) 145 | } 146 | 147 | module.exports = cyberhobo 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cyberhobo", 3 | "description": "Offline `git push` and `npm publish` for cyberhobos", 4 | "version": "1.0.0", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bin": { 11 | "cyberhobo": "./bin/cmd.js" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/feross/cyberhobo/issues" 15 | }, 16 | "dependencies": { 17 | "chalk": "^4.1.0", 18 | "connectivity": "^1.0.2", 19 | "once": "^1.4.0", 20 | "run-series": "^1.1.9", 21 | "run-waterfall": "^1.1.7" 22 | }, 23 | "homepage": "https://github.com/feross/cyberhobo/", 24 | "keywords": [ 25 | "git push", 26 | "npm publish", 27 | "offline", 28 | "git", 29 | "npm", 30 | "cyberhobo", 31 | "mad science" 32 | ], 33 | "license": "MIT", 34 | "main": "index.js", 35 | "preferGlobal": true, 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/feross/cyberhobo.git" 39 | }, 40 | "scripts": { 41 | "test": "standard" 42 | }, 43 | "devDependencies": { 44 | "standard": "*" 45 | }, 46 | "funding": [ 47 | { 48 | "type": "github", 49 | "url": "https://github.com/sponsors/feross" 50 | }, 51 | { 52 | "type": "patreon", 53 | "url": "https://www.patreon.com/feross" 54 | }, 55 | { 56 | "type": "consulting", 57 | "url": "https://feross.org/support" 58 | } 59 | ] 60 | } 61 | --------------------------------------------------------------------------------