Path to extension in production

My R extension’s task assistant defines a simple task that attempts to run a script defined within the extension. While this works fine in development, it doesn’t seem to transfer to production successfully – I just see the error “The file test.R doesn’t exist”.

The code looks like

let testTask = new Task("Test Package");
testTask.setAction(Task.Run, new TaskProcessAction(nova.path.join(nova.extension.path,"Scripts","test.R"), ...)

Am I misunderstanding the API here? If so, would anyone be able to point me at the correct way to obtain this path? Many thanks in advance.

Hmm…Is it possible to run the script using a relative path instead of the entire Nova extension path?
Similar, for example, to referring to other extension files (const { Configuration } = require(‘./configuration.js’).

1 Like

Just spitballing here, but it looks like you are launching a script as an executable. In that case, the failure might be that the executable bit on files does not get transferred when packaging and downloading extensions: you need to chmod +x on installation, with something like this code of mine.

1 Like

Thanks for the suggestion Jason. I don’t think this works, though, because the working directory when the task is run isn’t the extension’s Scripts directory – I assume it’s the directory containing the currently open file or the root of the current workspace or similar.

Thanks Martin! I had assumed that Nova preserved file permissions when it packaged up extensions, but I hadn’t checked. Setting the executable bit by hand with the extension in situ does resolve the issue. I’ll read over your code and implement something similar to set this on installation.

For @logan’s attention, it would be helpful if there were a way to test packaging and deployment without actually releasing an update to the package (assuming there isn’t one already! – I haven’t found one). That would make it easier to ensure everything will work before submitting.

Glad to hear my suggestion was on topic. Feel free to lift my code wholesale if it helps you!

1 Like

No worries Jon. Happy you found the solution!

To keep with my newly found habit of resuscitating old threads for significant Nova API changes, I’d like to note Nova 9 brought us native file mode handling, including a nifty chmod() function. Handling, however, is a bit … iffy, because FileStats.mode returns a six digit octal that JS helpfülly treats as a decimal, but FileSystem.chmod() expects a 3 digit octal number based on the last 3 digits of aforementioned mode.

The following update of my old code nets you the ability to make extension files executable on Nova versions both older and newer:

async function makeExecutable (...paths) {
  paths = paths.filter(path => nova.fs.stat(path)?.isFile())
  if (paths.length) {
    if (nova.version[0] < 9) {
      const options = { args: ['+x', ...paths] }
      const { code, error } = await runAsync('/bin/chmod', options)
      if (code > 0) throw error
    } else {
      // This is not elegant, but it works and is more readable than converting
      // everything to binary.
      for (const path of paths) {
        const mode = nova.fs.stat(path).mode.toString(8).slice(-3)
        const xbits = []
        for (const oct of mode) { xbits.push(Number(oct) | 1) }
        nova.fs.chmod(path, Number(`0o${xbits.join('')}`))
  return paths

Note that, on Nova versions below 9, this will need the “process” entitlement to run chmod and my runAsync module, or an equivalent of your own making. Starting with Nova 9, this uses the native functions and only needs the “filesystem: readwrite” entitlement to work.

Groan … the native Nova 9 version of my code, while being blazing fast, does not work for setting the executable bit on symbolic links, because it modifies the link itself instead of resolving to its target (as bona fide chmod does). This is particularly aggravating because nova.fs.access(path, nova.fs.X_OK) does do the right thing and returns the executable status of the link target. No way to work around this either because, of course, nova.path.normalize(), while able to resolve intermediate symlink elements, does not do so for the final element.

@logan, can we have an API wrapper around destinationOfSymbolicLink(atPath:), please?