New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

esm-executable

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

esm-executable

## intro

latest
npmnpm
Version
1.0.1
Version published
Maintainers
1
Created
Source

creating shell scripts with node/esm

intro

Learn how to create an executable standalone script two different ways:

  • node/esm
  • npm package + node/esm

The finished package is available here:

  • On GitHub as @frankg/demo
  • On npm as @frankg/demo

node/esm as a standalone shell script

FILE EXTENSIONS

  • .mjs interpreted as esm
  • .js interpreted as esm when you include "type": "module" in package.json

Since we're creating a standalone script we don't have package.json we use .mjs

import * as os from "node:os";

const { username } = os.userInfo();
console.log(`Hello ${username}!`);

RUNNING WITH NODE

  • node hello.mjs

RUNNING STANDALONE

  • edit script
#!/usr/bin/env node
import * as os from "node:os";

const { username } = os.userInfo();
console.log(`Hello ${username}!`);
  • make it executable -- chmod u+x hello.mjs
  • run it ./hello.mjs

node/esm as an npm package

creating the files

  • create directory mkdir demo; cd demo
  • create package.json file npm init -y
  • add dependencies pn add lodash-es -- creates node_modules folder and package-lock.json file
  • add readme.md file
  • add add two shell scripts
    demo/
        package.json
        package-lock.json
        readme.md
        src/
            homedir.mjs
            versions.mjs
  • add content to package.json
  ...
  "type": "module",
  "bin": {
    "homedir": "./src/homedir.mjs",
    "versions": "./src/versions.mjs"
  },
  ...
  • package.json contents -- confirm required publishing fields: name, version, license
{
  "name": "esm-executable",
  "version": "1.0.0",
  "description": "## intro",
  "main": "index.js",
  "type": "module",
  "bin": {
    "homedir": "./src/homedir.mjs",
    "versions": "./src/versions.mjs"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "lodash-es": "^4.17.21"
  }
}
  • populate homedir.mjs -- you can run with node ./src/homedir.mjs
#!/usr/bin/env node
import { homedir } from "node:os";

console.log("Homedir: " + homedir());
  • populate versions.mjs -- you can run with node ./src/versions.mjs
#!/usr/bin/env node

import { pick } from "lodash-es";

console.log(pick(process.versions, ["node", "v8", "unicode"]));

how npm runs shell scripts

INSTALLING ON UNIX

A script such as homedir.mjs does not need to be executable on Unix because npm installs it via an executable symbolic link:

  • when installing the package globally, the link is added to a directory that’s listed in $PATH.
  • when installing the package locally (as a dependency), the link is added to node_modules/.bin/

publishing the package

Before we publish lets check the configuration

  • .gitignore is honored
  • .npmignore overides .gitignore
  • see files excluded by default:

exclude by default

node_modules
.*.swp
._*
.DS_Store
.git
.gitignore
.npmignore
.npmrc
npm-debug.log

never exclude

package.json
README.md and its variants
CHANGELOG and its variants
LICENSE, LICENCE

Here are some things we can check

  • check which files will be uploaded npm publish --dry-run
  • create an archive package as it's store on the registry npm pack
  • install globally with either of these two: npm link or npm install . -g
  • list globally installed packages npm ls -g -- see the package-name
  • create an npm account if needed
  • upload to the registry npm publish --access public - access only needed first time
  • change access level if needed
  • a new version is required for every upload -- use semver - major.minor.patch

avoiding the .mjs extension

Here's a magic trick to avoid having to use the .mjs extension throught your code.

  • prepend your shell script files with the following snippet:
#!/bin/sh
":"; // ; cat "$0" | node --input-type=module - $@ ; exit $?

UPDATE: I took out the snippet and it still works with this

#!/usr/bin/env node
# see the package installed
npm ls -g

...
├── esm-executable@1.0.1 -> ./../../../../../dev/code/scratch/typescript/esm-executable
...

# find the node path
which node

/Users/frankg/.nvm/versions/node/v18.15.0/bin/node


# show the executable shell scripts
ls /Users/frankg/.nvm/versions/node/v18.15.0/bin

lrwxr-xr-x  1 frankg  staff    49B Sep 11 03:53 homedir -> ../lib/node_modules/esm-executable/src/homedir.js
lrwxr-xr-x  1 frankg  staff    50B Sep 11 03:53 versions -> ../lib/node_modules/esm-executable/src/versions.js

FAQs

Package last updated on 11 Sep 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts