├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── eval.js ├── file.js ├── js │ ├── index.js │ ├── osascript.js │ └── return.js └── stream.js ├── index.js ├── package.json └── test └── applescript_fixture.scpt /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Mikael Brevik 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | osascript 2 | === 3 | 4 | > Node.js module for doing Apple Open Scripting Architecture (OSA) in JavaScript or AppleScript. 5 | 6 | ## Install 7 | ``` 8 | npm install --save osascript 9 | ``` 10 | 11 | ## Requirements 12 | 13 | For using JavaScript as Automation language, Mac OS X Yosemite is required. 14 | On versions before that, you can pass AppleScripts. 15 | 16 | ## Usage 17 | 18 | By default, if no other type is defined and the passed file is not a AppleScript 19 | file (with extensions `.scpt` or `.applescript`), JavaScript is used. 20 | 21 | See the last example for overriding this behaviour and passing on AppleScript 22 | instead. All API's are the same. 23 | 24 | ### Default stream 25 | 26 | ```javascript 27 | var osascript = require('osascript'); 28 | var fs = require('fs'); 29 | 30 | // Run JavaScript file through OSA 31 | fs.createReadStream('someFile.js') 32 | .pipe(osascript()) 33 | .pipe(process.stdout); 34 | ``` 35 | 36 | This will pipe the data from `someFile.js` to the Apple Open Scripting Architecture (OSA) 37 | and print the result to the standard output. 38 | 39 | You can also do this in a short-hand: 40 | 41 | ```javascript 42 | // note the file method after require ¬ 43 | var osascript = require('osascript').file; 44 | 45 | // Run JavaScript file through OSA 46 | osascript('someFile.js').pipe(process.stdout); 47 | ``` 48 | 49 | Or if you only have a string, you can do that as well 50 | ```javascript 51 | // note the eval method after require ¬ 52 | var osascript = require('osascript').eval; 53 | 54 | // Run JavaScript text through OSA 55 | osascript('console.log("Hello, world!");').pipe(process.stdout); 56 | ``` 57 | 58 | ### Using a callback 59 | 60 | If you don't want to use a stream (just get the buffered output) 61 | you can do this on the `file` and `eval` methods by passing a 62 | function as the last argument: 63 | 64 | ```javascript 65 | // note the file method after require ¬ 66 | var osascript = require('osascript').file; 67 | 68 | // Run JavaScript file through OSA 69 | osascript('someFile.js', function (err, data) { 70 | console.log(err, data); 71 | }); 72 | ``` 73 | 74 | ```javascript 75 | // note the eval method after require ¬ 76 | var osascript = require('osascript').eval; 77 | 78 | // Run JavaScript text through OSA 79 | osascript('console.log("Hello, world!");', function (err, data) { 80 | console.log(err, data); 81 | }); 82 | ``` 83 | 84 | ### Using AppleScript 85 | 86 | As JavaScript as OSA isn't working on versions before Yosemite, 87 | we can use AppleScript as well. JavaScript is the default 88 | to try to encourage JS instead of AppleScript. When 89 | a filename is passed, AppleScript will be used if the filename 90 | has an AppleScript extension (`.scpt` or `.applescript`). 91 | 92 | 93 | ```javascript 94 | var osascript = require('osascript'); 95 | var fs = require('fs'); 96 | 97 | // Run JavaScript file through OSA 98 | fs.createReadStream('someAppleScript.applescript') 99 | // Need to override options to define AppleScript 100 | .pipe(osascript({ type: 'AppleScript' })) 101 | .pipe(process.stdout); 102 | ``` 103 | 104 | ```javascript 105 | // note the file method after require ¬ 106 | var osascript = require('osascript').file; 107 | 108 | // No need to pass options, as it can be deduced from filename. 109 | osascript('someFile.applescript', function (err, data) { 110 | console.log(err, data); 111 | }); 112 | ``` 113 | 114 | See [more examples](./examples). 115 | 116 | ### API 117 | 118 | API from base function required in this way: 119 | 120 | ```javascript 121 | var osascript = require('osascript'); 122 | ``` 123 | 124 | All endpoints uses `options`: 125 | 126 | ```javascript 127 | var defaultOptions = { 128 | type: 'JavaScript', 129 | args: [] // List of string arguments 130 | }; 131 | ``` 132 | 133 | Type is passed as language (option `-l`) to `osascript`. 134 | Can be either `JavaScript` (in Yosemite) or `AppleScript`. 135 | 136 | `flags` can be used to change the output style of return values from functions executed by osascript: 137 | 138 | ```js 139 | // JSON parsable return 140 | osascript('(function(){return ["foo",5, {foo: "barz"}]})()', {flags: ['-s', 's']}, function (data) { 141 | console.log(data); // ["foo", 5, {"foo":"barz"}] 142 | }); 143 | ``` 144 | 145 | `args` is a list of strings passed in as arguments to your scripts. In JavaScript 146 | you can access them using: 147 | 148 | ```js 149 | ObjC.import('Cocoa'); 150 | var args = ObjC.deepUnwrap($.NSProcessInfo.processInfo.arguments).slice(4); 151 | ``` 152 | 153 | `.slice(4)` is to cut away program name and language argument. In addition, 154 | `node-osascript` has to put in a `-` to indicate that the following list of 155 | strings should be passed as arguments. This `-` is on index 3. 156 | 157 | #### `osascript([options: Object])` 158 | 159 | Creates a PassThrough stream that can get piped text manually 160 | (text or files). 161 | 162 | #### `osascript.file(file[, options: Object, callback:function (err, data)])` 163 | See `options` as defined above. 164 | 165 | If callback function is passed, the buffered output from 166 | the OSA is passed as data (initiates the data immediately) 167 | 168 | #### `osascript.eval(scriptText[, options: Object, callback:function (err, data)])` 169 | `scriptText` is script in the language type as defined. 170 | 171 | See `options` as defined above. 172 | 173 | If callback function is passed, the buffered output from 174 | the OSA is passed as data (initiates the data immediately) 175 | 176 | ## TODO 177 | 178 | * [ ] Tests 179 | * [x] Error handling 180 | 181 | ## License 182 | 183 | [MIT License](LICENSE) 184 | -------------------------------------------------------------------------------- /examples/eval.js: -------------------------------------------------------------------------------- 1 | var osascript = require('../').eval; 2 | 3 | var script = 'get volume settings'; 4 | 5 | osascript(script, { type: 'AppleScript' }).pipe(process.stdout); 6 | // = output volume:44, input volume:75, alert volume:100, output muted:true 7 | 8 | 9 | // With callback 10 | osascript(script, { type: 'AppleScript' }, function (err, data) { 11 | console.log(data); 12 | // = output volume:44, input volume:75, alert volume:100, output muted:true 13 | }); 14 | -------------------------------------------------------------------------------- /examples/file.js: -------------------------------------------------------------------------------- 1 | var osascript = require('../').file; 2 | var path = require('path'); 3 | var file = path.resolve(__dirname, '../test/applescript_fixture.scpt'); 4 | 5 | osascript(file).pipe(process.stdout); 6 | 7 | // With callback 8 | osascript(file, function (err, data) { 9 | console.log("Data:", data); 10 | }); 11 | -------------------------------------------------------------------------------- /examples/js/index.js: -------------------------------------------------------------------------------- 1 | var osascript = require('../../').file; 2 | var path = require('path'); 3 | var file = path.resolve(__dirname, 'osascript.js'); 4 | 5 | osascript(file, { 6 | args: ['Some', 'Values'] 7 | }).pipe(process.stdout); 8 | -------------------------------------------------------------------------------- /examples/js/osascript.js: -------------------------------------------------------------------------------- 1 | var app = Application.currentApplication(); 2 | app.includeStandardAdditions = true; 3 | 4 | ObjC.import('Cocoa'); 5 | var args = ObjC.deepUnwrap($.NSProcessInfo.processInfo.arguments).slice(4); 6 | 7 | app.displayDialog('Hello World:' + args.join(',')); 8 | -------------------------------------------------------------------------------- /examples/js/return.js: -------------------------------------------------------------------------------- 1 | var osascript = require('../../').eval; 2 | var script = '(function(){return ["foo",5, {foo: "barz"}]})()'; 3 | 4 | /** 5 | * Quote from https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/osascript.1.html 6 | * > osascript normally prints its results in human-readable form: strings do not have quotes 7 | * > around them, characters are not escaped, braces for lists and records are omitted, etc. This 8 | * > is generally more useful, but can introduce ambiguities. For example, the lists `{"foo", 9 | * > "bar"}' and `{{"foo", {"bar"}}}' would both be displayed as `foo, bar'. To see the results in 10 | * > an unambiguous form that could be recompiled into the same value, use the s modifier. 11 | */ 12 | 13 | // Default return 14 | osascript(script, function (err, data) { 15 | if (err == null) { 16 | console.log('typeof data ->', typeof data); 17 | console.log('data ->', data); 18 | } else { 19 | console.log(err); 20 | } 21 | }); 22 | 23 | // JSON parsable return 24 | osascript(script, {type: 'JavaScript', flags: ['-s', 's']}, function (err, data) { 25 | if (err == null) { 26 | console.log('typeof data ->', typeof data); 27 | console.log('data ->', data); 28 | } else { 29 | console.log(err); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /examples/stream.js: -------------------------------------------------------------------------------- 1 | var osascript = require('../'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | fs.createReadStream(path.resolve(__dirname, '../test/applescript_fixture.scpt')) 6 | .pipe(osascript({ type: 'AppleScript' })) 7 | .on('close', function () { 8 | process.exit(0); 9 | }) 10 | .pipe(process.stdout); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var spawn = require('duplex-child-process').spawn; 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var noFileMsg = 'You need to specify filename'; 6 | var noInputMsg = 'You need to pass text to evaluate'; 7 | 8 | module.exports = function (opts) { 9 | return getSpawn(opts); 10 | }; 11 | 12 | module.exports.file = function (file, opts, cb) { 13 | if (typeof opts === 'function') { 14 | cb = opts; 15 | opts = {}; 16 | } 17 | if(!validateInput(file, cb, noFileMsg)) return; 18 | 19 | var stream = fs.createReadStream(file).pipe(getSpawn(opts, file)); 20 | if (cb) bufferStream(stream, cb); 21 | 22 | return stream; 23 | }; 24 | 25 | module.exports.eval = function (text, opts, cb) { 26 | if (typeof opts === 'function') { 27 | cb = opts; 28 | opts = {}; 29 | } 30 | if (!validateInput(text, cb, noInputMsg)) return; 31 | 32 | var stream = getSpawn(opts); 33 | if (cb) bufferStream(stream, cb); 34 | 35 | stream.write(text); 36 | stream.end(); 37 | return stream; 38 | }; 39 | 40 | function validateInput (input, cb, msg) { 41 | if (!input && cb === void 0) { 42 | throw Error(msg); 43 | } 44 | 45 | if(!input) { 46 | return cb(new Error(msg)); 47 | } 48 | 49 | return true; 50 | } 51 | 52 | function bufferStream (stream, cb) { 53 | var buffers = [], inError; 54 | 55 | stream.on('data', function(buffer) { 56 | buffers.push(buffer); 57 | }); 58 | 59 | stream.on('end', function() { 60 | var buffered = Buffer.concat(buffers); 61 | if (inError) return; 62 | 63 | return cb(null, buffered.toString()); 64 | }); 65 | 66 | stream.on('error', function(err) { 67 | inError = true; 68 | return cb(err); 69 | }); 70 | return stream; 71 | } 72 | 73 | function getSpawn (opts, file) { 74 | return spawn('osascript', argify(opts, file)); 75 | } 76 | 77 | function argify (opts, file) { 78 | var args; 79 | opts = opts || {}; 80 | if (opts.args) { 81 | args = ['-'].concat(opts.args); 82 | } else { 83 | args = []; 84 | } 85 | 86 | if ((file && isAppleScript(file) && !opts.type) || 87 | (opts.type && opts.type.toLowerCase() === 'applescript')) { 88 | return [].concat(args, opts.flags || []); 89 | } 90 | 91 | if (opts.type) { 92 | return ['-l', opts.type].concat(args, opts.flags || []); 93 | } 94 | 95 | return ['-l', 'JavaScript'].concat(args, opts.flags || []); 96 | } 97 | 98 | function isAppleScript (file) { 99 | var ext = path.extname(file); 100 | return (ext === '.scpt' || ext.toLowerCase() === '.applescript'); 101 | } 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osascript", 3 | "version": "1.2.0", 4 | "description": "A stream for controlling Macs through AppleScript or JavaScript.", 5 | "repository": "mikaelbr/node-osascript", 6 | "main": "index.js", 7 | "directories": { 8 | "test": "test", 9 | "examples": "examples" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [ 15 | "applescript", 16 | "osascript", 17 | "apple-javascript", 18 | "stream", 19 | "spawn", 20 | "jxa" 21 | ], 22 | "author": "Mikael Brevik ", 23 | "license": "MIT", 24 | "dependencies": { 25 | "duplex-child-process": "0.0.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/applescript_fixture.scpt: -------------------------------------------------------------------------------- 1 | display dialog "What should I do?" buttons {"Go home", "Work", "Nothing"} 2 | set DlogResult to result 3 | return result --------------------------------------------------------------------------------