diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7462061 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/node_modules/ + +/*.iml +/.idea/ + +/.yarnclean +/*.log + +/dist-dev + +/tsconfig.lib.*.json +/webpack.lib.*.js + +/package-lock.json \ No newline at end of file diff --git a/Application.js b/Application.js new file mode 100644 index 0000000..8c34ecd --- /dev/null +++ b/Application.js @@ -0,0 +1,49 @@ +// --------------------------------------------- +// Copied from https://github.com/eggplanetio/jxa/blob/reference-return/index.js +// --------------------------------------------- + +const exec = require('child_process').execSync; + +function dereference(path, args) { + try { + args = args ? JSON.stringify(args) : ''; + args = args.substr(1, args.length - 2); + + const cmd = `osascript -l JavaScript -e '${path}(${args})'`; + const res = exec(cmd, {stdio: 'pipe'}).toString().trim(); + if (res.indexOf('Application') === 0) { + return createReference(res); + } else { + return res; + } + } catch (e) { + // console.log(e); + } +} + +function createInspector(path) { + return () => `[object JXAReference => ${dereference(path + '.toString')}]`; +} + +function createReference(path) { + return new Proxy( + (recv, _, args) => dereference(path, args), + { + apply: (target, thisArg, argumentsList) => dereference(path, argumentsList), + + get(target, propertyName) { + return propertyName === 'inspect' + ? createInspector(path) + : createReference(`${path}.${propertyName.toString()}`); + }, + + set(target, property, value, receiver) { + // TODO: Implement `set` proxy handler + }, + }, + ); +} + +module.exports = function (handle) { + return createReference('Application("' + handle + '")'); +}; \ No newline at end of file diff --git a/bin/multiplerun b/bin/multiplerun new file mode 100755 index 0000000..e2a69c1 --- /dev/null +++ b/bin/multiplerun @@ -0,0 +1,18 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); +const multiplerun = require('../index'); + +const packageJsonPath = path.join(process.cwd(), 'package.json'); + +if (!fs.existsSync(packageJsonPath)) { + throw new Error(`💀 Undefined "${packageJsonPath}" file.`); +} + +const {multiplerun: layout} = JSON.parse(fs.readFileSync(packageJsonPath, {encoding: 'utf8'})); + +if (!layout) { + throw new Error(`💀 Undefined "multiplerun" option in "${packageJsonPath}" file.`); +} + +multiplerun(layout[process.argv[2]]); diff --git a/index.js b/index.js new file mode 100644 index 0000000..012abe9 --- /dev/null +++ b/index.js @@ -0,0 +1,72 @@ +const {exec} = require('child_process'); +const Application = require('./Application'); + +function useDefaultTerminal(layout) { + function run(command) { + if (process.platform === 'darwin') { + exec(`osascript -e 'tell application "Terminal" to do script "cd ${process.cwd()}; ${command};"'`); + } else if (process.platform === 'win32') { + exec(`start cmd /k "cd ${process.cwd()} && ${command}"`); + } + } + + for (const row of layout) { + if (Array.isArray(row)) { + for (const command of row) { + if (typeof command !== 'string') { + throw new Error('???'); + } + run(command); + } + } else if (typeof row === 'string') { + run(row); + } + } +} + +function useIterm(layout) { + const iTerm = Application('iTerm'); + + iTerm.includeStandardAdditions = true; + iTerm.activate(); + + iTerm.createWindowWithDefaultProfile(); + + const windowId = iTerm.currentWindow().id(); + + layout.forEach((row, r) => { + if (r === 0) return; + iTerm.windows.byId(windowId).currentTab().sessions.at(r - 1).splitVerticallyWithDefaultProfile(); + }); + + let commandCount = 0; + + layout.forEach(row => { + if (typeof row === 'string') { + iTerm.windows.byId(windowId).currentTab().sessions.at(commandCount).write({text: `cd ${process.cwd()}; ${row};`}); + commandCount++; + } else if (Array.isArray(row)) { + row.forEach((command, c) => { + if (c < row.length - 1) { + iTerm.windows.byId(windowId).currentTab().sessions.at(commandCount).splitHorizontallyWithDefaultProfile(); + } + iTerm.windows.byId(windowId).currentTab().sessions.at(commandCount).write({text: `cd ${process.cwd()}; ${command};`}); + commandCount++; + }); + } + }); +} + +module.exports = function (layout) { + if (process.platform === 'darwin') { + try { + useIterm(layout); + } catch (error) { + useDefaultTerminal(layout); + } + } else if (process.platform === 'win32') { + useDefaultTerminal(layout); + } else { + console.error(`😭 Sorry! Only macOS and Windows are supported yet!`); + } +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..aa739dc --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "multiplerun", + "version": "0.1.3", + "description": "Run multiple commands on multiple terminals (or iTerm split panes)", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "bin": { + "multiplerun": "./bin/multiplerun" + }, + "author": "Seoyeon Lee", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/iamssen/multiplerun.git" + }, + "upstream": { + "git": "https://github.com/iamssen/multiplerun.git" + }, + "bugs": { + "url": "https://github.com/iamssen/multiplerun/issues" + }, + "homepage": "https://github.com/iamssen/multiplerun" +} diff --git a/readme-assets/iTerm.png b/readme-assets/iTerm.png new file mode 100644 index 0000000..79bf4f6 Binary files /dev/null and b/readme-assets/iTerm.png differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8d4bb36 --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +# Run multiple commands on multiple terminals (or iTerm split panes) + +Install + +```sh +npm install multiplerun --save-dev +``` + +And add config to your `package.json` + +```json +{ + "name": "some-package", + "scripts": { + "multiplerun-test": "multiplerun test" + }, + "multiplerun": { + "test": [ + "echo multiplerun!", + [ + "echo hello", + "echo world" + ] + ] + } +} +``` + +```sh +npm run multiplerun-test +``` + +If you have installed [iTerm.app](https://www.iterm2.com/) on your Mac. You can see like this + +![iTerm Example](./readme-assets/iTerm.png) + +Or those commands will be executed via default terminal app (`cmd.exe` or `Terminal.app`) \ No newline at end of file