├── .prettierrc ├── docs ├── commands.md └── plugins.md ├── .gitignore ├── bin └── mana ├── src ├── commands │ ├── mana.js │ ├── scratch │ │ └── scratch.js │ ├── store │ │ ├── search.js │ │ └── store.js │ ├── json.js │ └── config │ │ ├── get.js │ │ └── set.js ├── extensions │ ├── rethinkdb-extension.js │ ├── filesystem-extension.js │ ├── log-extension.js │ └── config-extension.js └── cli.js ├── readme.md ├── __tests__ └── cli-integration.test.js ├── package.json └── LICENSE /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 4 5 | } -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Command Reference for mana 2 | 3 | TODO: Add your command reference here 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | coverage 5 | .nyc_output 6 | dist 7 | build 8 | .vscode 9 | -------------------------------------------------------------------------------- /bin/mana: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node --no-warnings 2 | 3 | 4 | // run the CLI with the current process arguments 5 | require('../src/cli').run(process.argv) 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/commands/mana.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | run: async ({ done, meta }) => { 3 | done({ 4 | commands: meta 5 | .commandInfo() 6 | .map(([name, description]) => ({ name, description })) 7 | }) 8 | }, 9 | hidden: true 10 | } 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # mana CLI 2 | 3 | A CLI for mana. 4 | 5 | ## Customizing your CLI 6 | 7 | Check out the documentation at https://github.com/infinitered/gluegun/tree/master/docs. 8 | 9 | ## Publishing to NPM 10 | 11 | To package your CLI up for NPM, do this: 12 | 13 | ```shell 14 | $ npm login 15 | $ npm whoami 16 | $ npm lint 17 | $ npm test 18 | (if typescript, run `npm run build` here) 19 | $ npm publish 20 | ``` 21 | 22 | # License 23 | 24 | MIT - see LICENSE 25 | 26 | -------------------------------------------------------------------------------- /src/commands/scratch/scratch.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | alias: 's', 3 | description: `create a scratch file and open it`, 4 | run: async ({ parameters, system, done, fs }) => { 5 | const filepath = await fs.tempfile() 6 | 7 | await fs.touch(filepath) 8 | 9 | const result = await system.run(`sublime -nw ${filepath}`) 10 | 11 | const contents = await fs.readFile(filepath, 'utf8') 12 | 13 | await fs.unlink(filepath) 14 | 15 | done(contents) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/store/search.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | run: async ({ parameters, fs, db, die, assert, done }) => { 3 | assert(parameters.first !== undefined, 'you need to pass id') 4 | 5 | const r = require('rethinkdb') 6 | 7 | try { 8 | const c = await db() 9 | 10 | const cursor = await r 11 | .table('docs') 12 | .filter(r.row('id').match(`^${parameters.first}`)) 13 | .run(c) 14 | 15 | done(await cursor.toArray()) 16 | } catch (e) { 17 | die(e.message) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/extensions/rethinkdb-extension.js: -------------------------------------------------------------------------------- 1 | const r = require('rethinkdb') 2 | 3 | module.exports = toolbox => { 4 | const { getConfig } = toolbox 5 | 6 | let connection = null 7 | 8 | toolbox.db = async () => { 9 | const { rethinkdb } = await getConfig() 10 | if (rethinkdb === undefined) { 11 | throw new Error(`database is not configured`) 12 | } 13 | 14 | connection = await r.connect({ 15 | host: rethinkdb.host, 16 | port: rethinkdb.port, 17 | db: rethinkdb.db 18 | }) 19 | 20 | return connection 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/json.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | run: async ({ done, fs, die, parameters: { first } }) => { 3 | const jquery = require('json-query') 4 | 5 | const input = await fs.stdin() 6 | const query = first === undefined ? '.' : first 7 | 8 | try { 9 | const data = JSON.parse(input) 10 | 11 | done( 12 | jquery(query, { 13 | data: data, 14 | locals: { 15 | parse: input => { 16 | return JSON.parse(input) 17 | } 18 | } 19 | }).value 20 | ) 21 | } catch (e) { 22 | die(e.message) 23 | } 24 | }, 25 | hidden: true 26 | } 27 | -------------------------------------------------------------------------------- /src/extensions/filesystem-extension.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const path = require('path') 3 | const crypto = require('crypto') 4 | const fss = require('fs') 5 | const fs = fss.promises 6 | 7 | module.exports = ctx => { 8 | const tempfile = async () => { 9 | const tempfile = crypto.randomBytes(8).toString('hex') 10 | const tempdir = await fs.mkdtemp(path.join(os.tmpdir(), 'mana-')) 11 | 12 | return path.resolve(tempdir, tempfile) 13 | } 14 | 15 | const touch = async filepath => { 16 | await fs.writeFile(filepath, '', 'utf8') 17 | } 18 | 19 | const stdin = () => { 20 | return fss.readFileSync(0, 'utf8') 21 | } 22 | 23 | ctx.fs = { 24 | ...fs, 25 | tempfile, 26 | touch, 27 | stdin 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/extensions/log-extension.js: -------------------------------------------------------------------------------- 1 | module.exports = toolbox => { 2 | toolbox.print = message => { 3 | process.stdout.write(`${JSON.stringify(message)}\n`) 4 | } 5 | 6 | toolbox.die = message => { 7 | process.stderr.write( 8 | `${JSON.stringify({ ok: false, error: message })}\n` 9 | ) 10 | process.exit(1) 11 | } 12 | 13 | toolbox.done = (data = {}) => { 14 | process.stdout.write(`${JSON.stringify(data)}\n`) 15 | process.exit(0) 16 | } 17 | 18 | toolbox.assert = (condition, message) => { 19 | if (condition !== true) { 20 | process.stderr.write( 21 | `${JSON.stringify({ ok: false, error: message })}\n` 22 | ) 23 | process.exit(1) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/store/store.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | run: async ({ parameters, fs, db, die, done }) => { 3 | const r = require('rethinkdb') 4 | 5 | let data = parameters.first === '-' ? fs.stdin() : '' 6 | let created_at = new Date().toISOString() 7 | let name = parameters.options.name || 'no name' 8 | 9 | try { 10 | const c = await db() 11 | 12 | const { generated_keys } = await r 13 | .table('docs') 14 | .insert({ 15 | name, 16 | data, 17 | created_at 18 | }) 19 | .run(c) 20 | 21 | done({ id: generated_keys[0], name, data, created_at }) 22 | } catch (e) { 23 | die(e.message) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/config/get.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | description: `get a value from the configuration`, 3 | run: async ({ parameters, assert, getConfig, setConfig, done }) => { 4 | const { first } = parameters 5 | 6 | assert(first !== undefined, `you must pass a key path`) 7 | 8 | const config = await getConfig() 9 | 10 | const keyPath = first.split('.') 11 | const path = keyPath.slice(0, -1) 12 | const finalKey = keyPath.slice(-1)[0] 13 | 14 | let obj = config 15 | for (let key of path) { 16 | if (obj[key] === undefined) { 17 | obj[key] = {} 18 | } 19 | 20 | obj = obj[key] 21 | } 22 | 23 | const value = obj[finalKey] === undefined ? null : obj[finalKey] 24 | 25 | done({ key: first, value: value }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/commands/config/set.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | description: `set a value in the configuration`, 3 | run: async ({ parameters, assert, getConfig, setConfig, done }) => { 4 | const { first, second } = parameters 5 | 6 | assert(first !== undefined, `you must pass a key path and it's value`) 7 | 8 | const config = await getConfig() 9 | 10 | const keyPath = first.split('.') 11 | const path = keyPath.slice(0, -1) 12 | const finalKey = keyPath.slice(-1)[0] 13 | 14 | let obj = config 15 | for (let key of path) { 16 | if (obj[key] === undefined) { 17 | obj[key] = {} 18 | } 19 | 20 | obj = obj[key] 21 | } 22 | 23 | obj[finalKey] = second 24 | 25 | await setConfig(config) 26 | 27 | done(config) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | const { build } = require('gluegun') 2 | 3 | /** 4 | * Create the cli and kick it off 5 | */ 6 | async function run(argv) { 7 | // create a CLI runtime 8 | const cli = build() 9 | .brand('mana') 10 | .src(__dirname) 11 | .plugins('./node_modules', { 12 | matching: '@mana-cli/plugin-*' 13 | }) 14 | .exclude(['print', 'config', 'http']) 15 | .create() 16 | // enable the following method if you'd like to skip loading one of these core extensions 17 | // this can improve performance if they're not necessary for your project: 18 | // .exclude(['meta', 'strings', 'print', 'filesystem', 'semver', 'system', 'prompt', 'http', 'template', 'patching']) 19 | // and run it 20 | const toolbox = await cli.run(argv) 21 | 22 | // send it back (for testing, mostly) 23 | return toolbox 24 | } 25 | 26 | module.exports = { run } 27 | -------------------------------------------------------------------------------- /__tests__/cli-integration.test.js: -------------------------------------------------------------------------------- 1 | const { system, filesystem } = require('gluegun') 2 | 3 | const src = filesystem.path(__dirname, '..') 4 | 5 | const cli = async cmd => 6 | system.run('node ' + filesystem.path(src, 'bin', 'mana') + ` ${cmd}`) 7 | 8 | test('outputs version', async () => { 9 | const output = await cli('--version') 10 | expect(output).toContain('0.0.1') 11 | }) 12 | 13 | test('outputs help', async () => { 14 | const output = await cli('--help') 15 | expect(output).toContain('0.0.1') 16 | }) 17 | 18 | test('generates file', async () => { 19 | const output = await cli('generate foo') 20 | 21 | expect(output).toContain('Generated file at models/foo-model.js') 22 | const foomodel = filesystem.read('models/foo-model.js') 23 | 24 | expect(foomodel).toContain(`module.exports = {`) 25 | expect(foomodel).toContain(`name: 'foo'`) 26 | 27 | // cleanup artifact 28 | filesystem.remove('models') 29 | }) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mana", 3 | "version": "0.0.1", 4 | "description": "mana CLI", 5 | "private": true, 6 | "bin": { 7 | "mana": "bin/mana" 8 | }, 9 | "scripts": { 10 | "format": "prettier --write **/*.{js,json} && standard --fix", 11 | "lint": "standard", 12 | "test": "jest", 13 | "watch": "jest --watch", 14 | "snapupdate": "jest --updateSnapshot", 15 | "coverage": "jest --coverage" 16 | }, 17 | "files": [ 18 | "src", 19 | "LICENSE", 20 | "readme.md", 21 | "docs", 22 | "bin" 23 | ], 24 | "license": "MIT", 25 | "dependencies": { 26 | "gluegun": "latest", 27 | "js-yaml": "^3.13.1", 28 | "json-query": "^2.2.2", 29 | "rethinkdb": "^2.3.3" 30 | }, 31 | "devDependencies": { 32 | "standard": "^12.0.1", 33 | "prettier": "^1.12.1", 34 | "jest": "^24.1.0" 35 | }, 36 | "jest": { 37 | "testEnvironment": "node" 38 | }, 39 | "standard": { 40 | "env": [ 41 | "jest" 42 | ] 43 | }, 44 | "prettier": { 45 | "semi": false, 46 | "singleQuote": true 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/extensions/config-extension.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs').promises 2 | const os = require('os') 3 | const path = require('path') 4 | 5 | const YAML = require('js-yaml') 6 | 7 | module.exports = toolbox => { 8 | const configPath = path.resolve(os.homedir(), '.manarc') 9 | 10 | const getConfig = async () => { 11 | let contents 12 | 13 | try { 14 | contents = await fs.readFile(configPath, 'utf8') 15 | } catch (e) { 16 | contents = `# mana configuration file\ncreated_at: ${new Date().toISOString()}` 17 | } 18 | 19 | return YAML.safeLoad(contents) 20 | } 21 | 22 | const setConfig = async config => { 23 | const contents = YAML.safeDump(config) 24 | 25 | try { 26 | const file = await fs.open(configPath, 'w') 27 | 28 | await file.writeFile(contents, 'utf8') 29 | 30 | await file.close() 31 | } catch (e) { 32 | toolbox.die(e.message) 33 | } 34 | } 35 | 36 | toolbox.getConfig = getConfig 37 | toolbox.setConfig = setConfig 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 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 | -------------------------------------------------------------------------------- /docs/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugin guide for mana 2 | 3 | Plugins allow you to add features to mana, such as commands and 4 | extensions to the `toolbox` object that provides the majority of the functionality 5 | used by mana. 6 | 7 | Creating a mana plugin is easy. Just create a repo with two folders: 8 | 9 | ``` 10 | commands/ 11 | extensions/ 12 | ``` 13 | 14 | A command is a file that looks something like this: 15 | 16 | ```js 17 | // commands/foo.js 18 | 19 | module.exports = { 20 | run: (toolbox) => { 21 | const { print, filesystem } = toolbox 22 | 23 | const desktopDirectories = filesystem.subdirectories(`~/Desktop`) 24 | print.info(desktopDirectories) 25 | } 26 | } 27 | ``` 28 | 29 | An extension lets you add additional features to the `toolbox`. 30 | 31 | ```js 32 | // extensions/bar-extension.js 33 | 34 | module.exports = (toolbox) => { 35 | const { print } = toolbox 36 | 37 | toolbox.bar = () => { print.info('Bar!') } 38 | } 39 | ``` 40 | 41 | This is then accessible in your plugin's commands as `toolbox.bar`. 42 | 43 | # Loading a plugin 44 | 45 | To load a particular plugin (which has to start with `mana-*`), 46 | install it to your project using `npm install --save-dev mana-PLUGINNAME`, 47 | and mana will pick it up automatically. 48 | --------------------------------------------------------------------------------