Giter Club home page Giter Club logo

node-jxa's Introduction

node-jxa

Build Status npm version downloads github

Use your favorite node.js modules, or your own local modules, in your JXA (OSX Javascript automation) scripts. Based on this awesome tip from the JXA Cookbook.

What is JXA?

It's OS X automation, using Javascript.

Applescript has long been the scripting language provided by Apple for automating and integrating apps on Mac OS X. The Internet is replete with Q & A, tips and examples of how to use Applescript to automate various menial tasks, like:

  • Archiving anything to Evernote
  • Capturing an email or Evernote link to your task manager (e.g. OmniFocus)
  • Capture your Safari reading list to your task manager

Since OS X 10.10 (Yosemite), Javascript (called Javascript for Automation, or JXA) is also supported for app automation. This is great news, especially for Javascript developers who want to automate workflows on their Mac.

What is node-jxa?

Based on this awesome tip, node-jxa allows you to use commonJS modules in your JXA scripts. You can require modules installed from npm, or your own local modules:

const _ = require( 'lodash' ); // from npm
const myModule = require( './my-module' );  // local modules too

ES6 module syntax (import from) isn't currently supported, so stick with require(), and use module.exports in your own local modules.

You can also use your favorite Javascript editor instead of the OS X Script Editor, and use workflow that is much more familiar to JS developers (vs. compiling .js files to .scpt binary format).

So long as your editor can launch a shebang'd script, you can run or debug JXA while you edit.

Installation and usage

You'll likely want to install node-jxa globally:

yarn global add node-jxa # or the npm equivalent: npm install -g node-jxa

This will install node-jxa and make it available in your PATH env var.

.. then you can (optionally) use a shebang at the top of your JXA script:

#! /usr/bin/env node-jxa

// ... rest of script

and make your script executable:

chmod u+x my-jxa-script.js

.. so you can run it from the command line (or, using your favorite script launcher, keyboard shortcuts, etc).

./my-jxa-script.js

You can of course use the node-jxa command on the command line; simply provide the jxa script as the first argument:

node-jxa ./my-jxa-script.js

Node.js engine and JXA runtime

Note that your installed node.js engine is only used by node-jxa to bundle up your module dependencies, and to decorate your script with a couple of needed additions (using the Browserify API). The resulting code is then piped to osascript, and your OS X JavaScriptCore (modified for OSA integration) is used to execute it. Node.js is not ultimately used to execute your script.

ES6+ syntax support

Fortunately the supported ES syntax and features is quite modern (depending on your OS X version). As long as your installed node.js engine is recent enough for Browserify to bundle your code, you should be able to use just about any modern ES syntax you like (except for ES6 module syntax, i.e. import from). To see what's supported, check what version of Safari you have, then find its column at the Kangax compatibility site.

Note: the Kangax table has a column for JXA, but it doesn't appear to be current.

Project structure and availability of node modules

All modules you require in your scripts must be installed (i.e. in the node_modules dir) to be available, so Browserify can bundle them into your script.

I suggest managing your node-jxa scripts like any node.js project, with a package.json specifying the needed module dependencies. Simply use yarn or npm to add and remove the libraries you need.

Note that Browserify won't automatically package modules for which the required path is computed at runtime. For example, let myNeededModulePath = './my/needed/module' ; let myModule = require( myNeededModulePath ); will leave the module out and it won't work. This can happen within modules you're getting from npm. Browserify has techniques for handling this, but node-jxa doesn't currently employ them.

Using Applescript libraries

You can use your Applescript libraries in your JXA scripts using the Library global function, like so:

let myAsLib = Library( 'myApplescriptLibary' ) // skip the .scpt suffix

All top-level Applescript routines and handlers in the library will be available as functions on the imported object.

Library files must be located in your Script Libraries folder or in your OSA_LIBRARY_PATH (the latter as of OS X 10.11) to be used in this way.

This works with JXA libraries too - you can use Library() to import .scpt scripts compiled from Javascript. But since node-jxa allows you to require() from your local js files, it's much better to use your js libraries like local node.js modules, using module.exports.

There are several other globals added by the JXA runtime; they are detailed by Apple, here.

Scripting Dictionaries

With node-jxa you can use your favorite JS editor for writing and managing your JXA code, but the OS X Script is still useful for viewing the Scripting Dictionaries for your scriptable apps. Essentially, this is the documentation for the API exposed by the apps' developers. Look for Open Dictionary... on the File Menu, and be sure to select JavaScript in the language selector at the top.

Be prepared for some weirdness

Some things are weird, including but not limited to:

  • trying to console.log a JXA application object will probably crash the process.
  • Element Arrays (arrays provided directly by Applications ( or filtering via whose() ) are weird. A simple trick like array.map( el => el ) will give you a real JS array.
  • property access for JXA objects is expensive. If you're doing many reads, your script can take a long time and may even time out. A caching strategy can help.

Debugging

You can debug your JXA scripts using Safari dev tools. To debug, enable JSContexts support in Safari, then simply include the debugger; command in your script. When you run the script it'll stop and open at that spot in the Safari debugger (from here, you can add additional breakpoints in Safari's debugger).

If you add the --debug (or -d) switch to the node-jxa, command (including in your shebangs), Browserify will include sourcemaps in the bundled code, for a cleaner debugging experience that is more focused on your own code.

Sourcemaps will also be included if the environment variable NODE_DEBUG_JXA is set to true or 1, regardless of whether the --debug or -d switches are used.

Depending on your Mac, you might never notice a difference in performance when including sourcemaps. You'll definitely see more of a performance hit from requiring many large or spurious libraries, regardless of sourcemaps.

Other JXA resources

Note that there are many more resources available for Applescript on the interwebs (or even from Apple) than there is for JXA. When googling for an example or recipe, you'll probably find much more help by looking an Applescript that does what you want, and then converting to Javascript yourself. You can temporarily use an Applescript library if the conversion eludes you for a time.

node-jxa's People

Contributors

johnelm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

node-jxa's Issues

How Do I use Visual Studio Code (VSC) with JXA and Retain JavaScript Code Completion?

First, let me express my tremendous gratitude to John for providing this excellent library/resource.
I look forward to using it.

Now to my question:

How Do I use Visual Studio Code (VSC) with JXA and Retain JavaScript Code Completion?

It is great that this extension provides us with the ability to code, compile, and run JXA files:

AppleScript & JXA for Visual Studio Code

Unfortunately, JavaScript code completion is not active for a JXA file, even though JXA uses core JavaScript. Is there some way to enable it for JXA?

I am brand new to VSC, so if you can, please provide detailed instructions.

I realize this is NOT directly related to this repository, but I'm hoping the expertise is here that can answer this question. I've asked elsewhere without finding a solution.

Thanks.

The 2.1.2 release on NPM includes a reference to a developer-local directory (and doesn't correspond to the Github tag)

Hi. If you install the current (2.1.2) release from NPM, you'll get an error when Node-jxa runs osacompile:

osacompile: couldn't write to file /Users/john/code/node-jxa/deleteme.scpt: No such file or directory

The vars at the top of node-jxa.js look like this in that release:

const HEAD = 'window = this;\nObjC.import("stdlib");\ntry {\n ';
const TAIL = ';\n} catch (e) {\n console.log( e.message );\n $.exit(1); \n}\n$.exit(0);';
const OSA_JXA_CMD = 'osacompile';
const OSA_JXA_CMD_ARGS = ['-l', 'JavaScript', '-o', '/Users/john/code/node-jxa/deleteme.scpt' ];

You can check code in NPM's module package by getting the archive address with npm view node-jxa dist.tarball and downloading the archive.

My guess is you might want to republish the release if NPM allows that, because code for the release seems to be different here on Github.

dependency graph error

I am building an electron app and I am getting the following error in my console when I call var nodejxa = require('node-jxa') from my JavaScript:

/Users/Me/Documents/git/electronHelloWorld/node_modules/node-jxa/node-jxa.js:15 Error: Can't walk dependency graph: Cannot find module '/Users/Me/Documents/git/electronHelloWorld/--field-trial-handle=1718379636,12704840948703548028,9979737565501458108,131072' from '/Users/Me/Documents/git/electronHelloWorld/_fake.js'
    required by /Users/Me/Documents/git/electronHelloWorld/_fake.js
    at /Users/Me/Documents/git/electronHelloWorld/node_modules/resolve/lib/async.js:137
    at load (/Users/Me/Documents/git/electronHelloWorld/node_modules/resolve/lib/async.js:156)
    at onex (/Users/Me/Documents/git/electronHelloWorld/node_modules/resolve/lib/async.js:181)
    at /Users/Me/Documents/git/electronHelloWorld/node_modules/resolve/lib/async.js:15
    at FSReqCallback.oncomplete (fs.js:172)

Feature Request - Add commandline option to generate compiled .scpt file from source

Hi John,

It seems that node-jxa uses browserify to generate a single script file each time it is invoked. For scripts that are called often, or that are substantial in size, this is quite a bit of overhead.

So I was wondering whether the node-jxa command could accept an option that changes its behaviour to instead generate a .scpt file using osacompile command. This .scpt file can then be used and shared as like any other applescript, but also isn't regenerated on each invocation.

Perhaps a -c option which takes a file path for the resulting compiled .scpt file.

I hope this makes sense?

Is this something you would consider adding to this great tool?

Many thanks.

Damo.

fix typos in README

.. ES6 module isn't supported, not aren't.

Should introduce node-debug-jxa along with the node-jxa command.

Add a headsup to README about filesystem limitations

  • can't access the file system, and modules that depend on fs access may have issues
  • browserify won't automatically include modules whose requireing is derived at runtime

why:

  • browserify built for browser, which has obvious security constraints
  • OSA has its own sandboxing constraints
    • (need to check on accessing filesystem outside sandbox folder using ObjC)

stderr output is too verbose

.. includes a dump of the stream meta:

{ Error: Cannot find module '/Users/jelm1/code/node-jxa/blah' from '/Users/jelm1/code/node-jxa'
    at /Users/jelm1/.config/yarn/global/node_modules/browser-resolve/node_modules/resolve/lib/async.js:55:21
    at load (/Users/jelm1/.config/yarn/global/node_modules/browser-resolve/node_modules/resolve/lib/async.js:69:43)
    at onex (/Users/jelm1/.config/yarn/global/node_modules/browser-resolve/node_modules/resolve/lib/async.js:92:31)
    at /Users/jelm1/.config/yarn/global/node_modules/browser-resolve/node_modules/resolve/lib/async.js:22:47
    at FSReqWrap.oncomplete (fs.js:152:21)
  stream:
   Labeled {
     _readableState:
      ReadableState {
        objectMode: true,
        highWaterMark: 16,
        buffer: BufferList { length: 0 },
        length: 0,
        pipes: [Object],
        pipesCount: 1,
        flowing: true,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        destroyed: false,
        defaultEncoding: 'utf8',
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: true,
     domain: null,
     _events:
      { end: [Array],
        error: [Function],
        data: [Function: ondata],
        _mutate: [Object] },
     _eventsCount: 4,
     _maxListeners: undefined,
     _writableState:
      WritableState {
        objectMode: true,
        highWaterMark: 16,
        finalCalled: false,
        needDrain: false,
        ending: true,
        ended: true,
        finished: true,
        destroyed: false,
        decodeStrings: true,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: false,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: true,
        errorEmitted: false,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: false,
     allowHalfOpen: true,
     _options: { objectMode: true },
     _wrapOptions: { objectMode: true },
     _streams: [ [Object] ],
     length: 1,
     label: 'deps' } }

Issue with the JXA Application.currentApplication() Object

I get an error in VSC using Code Runner with this script:

#!/usr/bin/env osascript -l JavaScript

var app = Application.currentApplication();
app.includeStandardAdditions = true;

console.log("Test Get app name");
var appProp = app.properties();  // <== fails on this line
appProp.name;

/*
ERROR MSG:
117:148: execution error: Error on line 6: Error: Message not understood. (-1708)

NOTE:  Even though it says "line 6", the actual line is 7, as line 6 outputs correctly.
For completeness here's the entire Code Runner output:

[Running] /usr/bin/env osascript -l JavaScript "/Users/Shared/Dropbox/SW/DEV/JXA/VSC/Test Using CodeRunner Ext.js"
Test Get app name
/Users/Shared/Dropbox/SW/DEV/JXA/VSC/Test Using CodeRunner Ext.js:117:148: execution error: Error on line 6: Error: Message not understood. (-1708)

[Done] exited with code=1 in 0.11 seconds

*/

This same script, without the shebang, runs fine in Script Editor.

image

What am I doing wrong in VSC?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.