Skip to content

πŸ’Ύ πŸ“¦ βœ… Unversal Script Executor, zero install necessary

License

Notifications You must be signed in to change notification settings

jeff-hykin/deno-guillotine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

90 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

What is this for?

Making cross platform scripts that have 0 install steps / instructions

  • The user does not need to install Curl/Node/Deno, nor anything else
  • The script does not need to "target" an OS; it is a single script, the same file for all OS's
  • The script is not a binary and is not compiled/mangled; it is readable/editable JavaScript
  • The script will not touch anything installed by the user (no pollution/side-effects)
  • The script will auto-download a specific version of Deno to an isolated folder, and then run itself using that exact version of Deno to ensure consistent/reproducable behavior.
  • Any Deno.land module can be imported/used, no need to reinvent the wheel or bundle code.

This is possible because of some rare builtin tools that allow for a single file to be valid bash, and valid powershell, AND valid JavaScript (based on this StackOverflow answer Is it possible to write one script that runs in bash/shell and PowerShell?)

How do I make an installer script?

  1. Write a Deno script, lets call it install.js
console.log("Hello World")
  1. Install the cli helper (guillotine) to make your script portable
deno install -Afg https://deno.land/x/deno_guillotine/main/deno-guillotine.js
  1. Use it to make your script portable
deno-guillotine ./your_script.js
# if you have a particular version of deno you want to use, include it as the second argument
deno-guillotine ./your_script.js 1.44.4
# if you want to add an deno-run argument like --no-npm do it like this:
deno-guillotine ./your_script.js --add-arg '--no-npm' --add-arg '--unstable'
  1. Profit
  • Two files will have been generated, which I'll explain in a moment. More importantly though, typing ./install in the command line will now try an execute the script (even on Windows)

    • On Linux/Mac and other half-decent operating systems supported by Deno (incuding Arm Linux) there is no catch.
    • On Windows there is one catch; a fresh Windows install will block execution of all powershell scripts by default.
      Set-ExecutionPolicy unrestricted will need to be run in an admin terminal before powershell scripts can be executed. After that, it follows the same process as the other operating systems (downloads the specific version of Deno if needed, and executes itself using that version).
  • Two files are generated, but one is just a symlink to the other. And we can get away with a single file (with some compromises):

    • Technically install.ps1 is the only file needed. Typing ./install.ps1 on any OS will execute correctly.
      However, I find the .ps1 ugly. And the .ps1 is only needed for Windows. The other file (the symlink) is what makes it possible to do ./install on all systems:
      • On Windows, if the file is called ./install.ps1 then typing ./install in the command line naturally execute it (no change needed).
      • On Linux/Mac we can make a ./install file that is just a relative symlink to install.ps1. Volia, typing ./install now executes the ./install.ps1 file.
    • If you don't care about Windows supoort, delete the non-ps1 file (the symlink), and then just rename the .ps1 file so that it doesn't have a .ps1.

How can something be valid Powershell, Bash, and Deno all at the same time?

I wrote out an explaination here that covers the basics, and it was fairly straightforward to add support for JavaScript on top of Bash/Powershell. In particular, I just took the offical Deno install script and compressed it to fit inline at the top of a file.

How do I verify this isn't malicious?

Glad you asked!

  1. Verify the installer script
    • Start by looking at ./main/1_deno_installer.sh
    • Then compare that to ./main/2_readable.ps1
      • Read the commits to see explainations of changes (it has to be changed manually)
      • ex:
        • the PATH modification that deno usually performs was commented out (we don't want to affect the user's system)
        • the deno-version is automatically supplied (instead of grabbing latest)
        • a bunch of semicolons need to be added so that the newlines can be removed (so the script can be compressed at the top of a file)
        • the install location is changed from $HOME/.deno/ to $HOME/.deno/${version}/ (we don't want to affect the user's system)
        • if curl doesn't exist, we fall back on wget (some systems don't have curl)
        • if unzip doesn't exist, we try to install it for the user (some systems don't start with unzip)
        • etc
    • Once ./main/2_readable.ps1 is verified, look at ./run/readable_to_inline.js. It
      • this file-reads the readable version
      • deletes comments
      • deletes newlines
      • generates ./main/3_inlined.ps1 which is the shell + powershell aspect
      • JavaScript-escapes those contents
      • makes a javascript function that accepts the deno version and the args (like --allow-all or --unstable)
      • puts that javascript function inside of ./main/4_inlined.js
    • Once those have been looked open up deno-guillotine-api.js (which imports ./main/4_inlined.js)
    • Finally deno-guillotine.js imports enhanceScript from deno-guillotine-api.js
  2. Verify the main JavaScript
    • Look at deno-guillotine-api.js (no permissions needed)
    • Look at deno-guillotine.js (needs file permissions because its the CLI script)
  3. Bundle deno-guillotine.js, and inspect the dependencies
    • GoodJs is a permissionless/frontend utility library I maintain
    • FileSystem is a quality of life wrapper around Deno's file system and path. I'd like to remove it from guillotine to make guillotine easier to verify, but thats future work for me.
  • Footnote:
    • Deno didn't always have official arm64 support
    • Older versions of guillotine got arm64 support using LukeChannings script
    • Now guillotine (as of v1.0.0.5) uses the official deno arm64 installer

Can I generate these files client-side?

Not sure why you would, but actually yes you can. There is a pure-function API to enable this functionality from within any JavaScript runtime.

import { enhanceScript } from "https://deno.land/x/deno_guillotine/main/deno-guillotine-api.js"

const { newContents, symlinkPath, normalPath, ps1Path } = enhanceScript({
    filePath: "./my_cli_scipt.js",
    jsFileContent: `console.log("Hello World")`,
    denoVersion: "1.44.4",
    additionalArgs: [ "--no-npm", "--unstable" ],
    additionalArgsForUnix: [ ],
    additionalArgsForWindows: [ ],
    baseArgs: [ "--quiet", "-A", "--no-lock", ],
})

Deno.writeTextFileSync(ps1Path, newContents)
Deno.symlinkSync(symlinkPath, normalPath)

About

πŸ’Ύ πŸ“¦ βœ… Unversal Script Executor, zero install necessary

Resources

License

Stars

Watchers

Forks

Packages

No packages published