├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dest └── apple-java-script.js ├── gulpfile.babel.js ├── package.json ├── src └── apple-java-script.js └── test └── apple-java-script-test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Dmitry Yakimov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppleJavaScript 2 | 3 | ## Installation 4 | 5 | ```shell 6 | npm install apple-java-script 7 | ``` 8 | 9 | ## Usage 10 | 11 | Use `AppleJavaScript` to run AppleScript in JavaScript language 12 | straight from node.js. 13 | 14 | All you have to do is to directly call it. And it will try to parse 15 | the rutruning result. 16 | 17 | ```js 18 | // Getting iTunes Playlists 19 | 20 | var ajs = require('apple-java-script'); 21 | 22 | var playlists = ajs(function() { 23 | var iTunes = Application('iTunes'); 24 | return iTunes.playlists.name(); 25 | }); 26 | 27 | playlists; 28 | // => 29 | // ['Library', 'Music', 'Music Videos', 'Movies', 'Home Videos', 'TV Shows', 'Podcasts', 'iTunes U', 'Audiobooks', 'Books', 'PDFs', 'Audiobooks', 'Genius'] 30 | ``` 31 | 32 | ### Passing variables 33 | 34 | Behind the scenes, AppleJavaScript converts function to the text and 35 | runs it with shell command `osascript`. So the function itself 36 | actually doesn't have acces to outside scope. And neither of global or 37 | outside variables are accessible from the function. To pass vars to it 38 | you have to pass them to function as an argument. 39 | 40 | ```js 41 | // Showing modal and passing message from outer script 42 | 43 | var ajs = require('apple-java-script'); 44 | 45 | var message = 'Hello'; 46 | 47 | try { 48 | ajs(message, function(message) { 49 | var app = Application.currentApplication(); 50 | app.includeStandardAdditions = true; 51 | return app.displayDialog(message); 52 | }); 53 | 54 | console.log('You pushed Ok'); 55 | } catch (e) { 56 | console.log('You pushed Cancel'); 57 | } 58 | ``` 59 | 60 | ### Running in a safer way 61 | 62 | `AppleJavaScript` direct call could be dangerous because it uses 63 | `eval` behind the scenes to parse returned AppleScript value. So you 64 | better know what you are doing and control all inputs. If you are not 65 | sure in that you can use `AppleJavaScript.runSafe`. It works the same 66 | way, but doesn't try to parse returning value. So you'll have to do it 67 | yourself. 68 | 69 | ```js 70 | var ajs = require('apple-java-script'); 71 | 72 | var fn = function() { 73 | let app = Application.currentApplication(); 74 | app.includeStandardAdditions = true; 75 | return app.doShellScript('echo Babylon'); 76 | } 77 | 78 | ajs.runSafe(fn); 79 | // => 80 | // '"Babylon"' 81 | ajs(fn); 82 | // => 83 | // 'Babylon' 84 | ``` 85 | -------------------------------------------------------------------------------- /dest/apple-java-script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _lodash = require('lodash'); 8 | 9 | var _lodash2 = _interopRequireDefault(_lodash); 10 | 11 | var _serializeJavascript = require('serialize-javascript'); 12 | 13 | var _serializeJavascript2 = _interopRequireDefault(_serializeJavascript); 14 | 15 | var _child_process = require('child_process'); 16 | 17 | var _deasync = require('deasync'); 18 | 19 | var _deasync2 = _interopRequireDefault(_deasync); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | /*global module*/ 24 | 25 | var execSync = (0, _deasync2.default)(_child_process.exec); 26 | 27 | var escapeShell = undefined; 28 | 29 | var AppleJavaScript = function AppleJavaScript() { 30 | var result = AppleJavaScript.runSafe.apply(AppleJavaScript, arguments); 31 | return AppleJavaScript.unserialize(result); 32 | }; 33 | 34 | AppleJavaScript.runSafe = function () { 35 | var functionText = AppleJavaScript.build.apply(AppleJavaScript, arguments); 36 | return AppleJavaScript.execSync(functionText); 37 | }; 38 | 39 | AppleJavaScript.build = function () { 40 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 41 | args[_key] = arguments[_key]; 42 | } 43 | 44 | var fn = (0, _lodash2.default)(args).last(); 45 | var argsToPass = args.slice(0, args.length - 1).map(function (v) { 46 | return (0, _serializeJavascript2.default)(v); 47 | }).join(', '); 48 | return '(' + fn.toString() + ')(' + argsToPass + ')'; 49 | }; 50 | 51 | AppleJavaScript.execSync = function (functionText) { 52 | var functionTextEscaped = escapeShell(functionText); 53 | var command = "osascript -s s -l JavaScript -e '" + functionTextEscaped + "'"; 54 | return (0, _lodash2.default)(execSync(command)).trim('\n'); 55 | }; 56 | 57 | AppleJavaScript.unserialize = function (value) { 58 | var result = undefined; 59 | try { 60 | eval('result = ' + value); 61 | } catch (e) { 62 | result = value; 63 | } 64 | return result; 65 | }; 66 | 67 | escapeShell = function (command) { 68 | return command.replace(/'/g, "'\"'\"'"); 69 | }; 70 | 71 | module.exports = AppleJavaScript; 72 | exports.default = AppleJavaScript; -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import babel from 'gulp-babel'; 3 | 4 | import { spawn } from 'child_process'; 5 | 6 | gulp.task('build', () => { 7 | gulp.src(['src/**/*.js']) 8 | .pipe(babel()) 9 | .pipe(gulp.dest('dest')); 10 | }); 11 | 12 | gulp.task('test', () => { 13 | let mocha = spawn('mocha',[ 14 | 'test', 15 | '--compilers', 16 | 'js:babel-core/register', 17 | '--reporter', 18 | 'spec', 19 | '--recursive' 20 | ]); 21 | 22 | mocha.stdout.on('data', function(data) { 23 | process.stdout.write(data); 24 | }); 25 | 26 | mocha.stderr.on('data', function(data) { 27 | process.stdout.write(data); 28 | }); 29 | }); 30 | 31 | gulp.task('default', ['build']); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apple-java-script", 3 | "version": "1.0.1", 4 | "description": "Run AppleScript in JavaScript language straight from node.js", 5 | "main": "dest/apple-java-script.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "deasync": "^0.1.4", 13 | "lodash": "^3.10.1", 14 | "serialize-javascript": "^1.1.2" 15 | }, 16 | "devDependencies": { 17 | "babel-core": "^6.2.1", 18 | "babel-preset-es2015": "^6.1.18", 19 | "chai": "^3.4.1", 20 | "gulp": "^3.9.0", 21 | "gulp-babel": "^6.1.0", 22 | "mocha": "^2.3.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/apple-java-script.js: -------------------------------------------------------------------------------- 1 | /*global module*/ 2 | 3 | import _ from 'lodash'; 4 | import serialize from 'serialize-javascript'; 5 | import { exec } from 'child_process'; 6 | import deasync from 'deasync'; 7 | 8 | let execSync = deasync(exec); 9 | 10 | let escapeShell; 11 | 12 | let AppleJavaScript = (...args) => { 13 | let result = AppleJavaScript.runSafe(...args); 14 | return AppleJavaScript.unserialize(result); 15 | }; 16 | 17 | AppleJavaScript.runSafe = (...args) => { 18 | let functionText = AppleJavaScript.build(...args); 19 | return AppleJavaScript.execSync(functionText); 20 | }; 21 | 22 | AppleJavaScript.build = (...args) => { 23 | let fn = _(args).last(); 24 | let argsToPass = args 25 | .slice(0, args.length - 1) 26 | .map( v => serialize(v)).join(', '); 27 | return `(${fn.toString()})(${argsToPass})`; 28 | }; 29 | 30 | AppleJavaScript.execSync = (functionText) => { 31 | let functionTextEscaped = escapeShell(functionText); 32 | let command = "osascript -s s -l JavaScript -e '" + functionTextEscaped + "'"; 33 | return _(execSync(command)).trim('\n'); 34 | }; 35 | 36 | AppleJavaScript.unserialize = (value) => { 37 | let result; 38 | try { 39 | eval(`result = ${value}`); 40 | } catch (e) { 41 | result = value; 42 | } 43 | return result; 44 | } 45 | 46 | escapeShell = (command) => { 47 | return command.replace(/'/g, "'\"'\"'"); 48 | }; 49 | 50 | module.exports = AppleJavaScript; 51 | export default AppleJavaScript; 52 | -------------------------------------------------------------------------------- /test/apple-java-script-test.js: -------------------------------------------------------------------------------- 1 | /*global describe it beforeEach Application */ 2 | import { expect } from 'chai'; 3 | import ajs from '../src/apple-java-script'; 4 | 5 | describe('AppleJavaScript', () => { 6 | describe('direct call', () => { 7 | it('should run', () => { 8 | ajs(() => {}); 9 | }); 10 | 11 | it('should return result', () => { 12 | let result = ajs(() => { 13 | let app = Application.currentApplication(); 14 | app.includeStandardAdditions = true; 15 | return app.doShellScript('echo Babylon'); 16 | }); 17 | expect(result).to.equal('Babylon'); 18 | }); 19 | }); 20 | 21 | describe('.runSafe', () => { 22 | it('should return unescaped result', () => { 23 | let result = ajs.runSafe(() => { 24 | let app = Application.currentApplication(); 25 | app.includeStandardAdditions = true; 26 | return app.doShellScript('echo Babylon'); 27 | }); 28 | expect(result).to.equal('"Babylon"'); 29 | }); 30 | }); 31 | 32 | describe('.build', () => { 33 | it('makes text string out of function', () => { 34 | let functionText = ajs.build(() => { 35 | return 5 + 5; 36 | }); 37 | expect(functionText).to.equal( 38 | "(function () {\n return 5 + 5;\n })()" 39 | ); 40 | }); 41 | 42 | it('inserts arguments', () => { 43 | let functionText = ajs.build(5, ' times', (x, y) => { 44 | return x.toString() + y; 45 | }); 46 | 47 | expect(functionText).to.equal( 48 | "(function (x, y) {\n return x.toString() + y;\n })(5, \" times\")" 49 | ); 50 | }); 51 | }); 52 | 53 | describe('.execSync', () => { 54 | it('execs stringifyed function and returns it result', () => { 55 | let functionText = '(function(){return 5;})()' 56 | expect(ajs.execSync(functionText)).to.equal('5'); 57 | }); 58 | }); 59 | 60 | describe('.unserialize', () => { 61 | it('unserializes regular js type', () => { 62 | expect(ajs.unserialize('{"a":"hello", "b":[1, 2]}')) 63 | .to.deep.equal({"a":"hello", "b":[1, 2]}); 64 | }); 65 | 66 | it('returns value itself for objects it cannot unserialize', () => { 67 | expect(ajs.unserialize('Application.currentApplication()')) 68 | .to.equal('Application.currentApplication()'); 69 | }); 70 | }); 71 | }); 72 | --------------------------------------------------------------------------------