npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

libseal

v0.2.0

Published

Seal a directory of JavaScript modules into one password-protected file, then open, mount, and load them.

Readme

libseal

Seal a folder of JavaScript into one password-protected .libseal file. Later, open that file, or mount it inside this package and require the modules.

Node's built-in crypto does the work. There are no runtime dependencies.

Anyone who has the file and the password can read the source. Use libseal to keep a library encrypted on disk or in an artifact store. It does not hide code from someone you have already given the password to.

Install

npm install libseal

Node 18 or newer. The command is libseal. The same operations are available from JavaScript.

Sample

This repository already has a library and a small app.

library/greeter.js     the module you seal
libseal-sample/        seals it, then loads it

library/greeter.js exports hello(name). The sample password is a plain string:

const password = "sample-password";

From libseal-sample/:

node index.js

That writes greeter.libseal, decrypts it into this package, and prints Hello, Ada.

The mounted file is:

node_modules/libseal/modules/greeter/greeter.js

In this repo the package lives at libseal/, so the same path is libseal/modules/greeter/greeter.js.

modulePath: "greeter" is the file inside that folder. The folder name comes from the sealed file: greeter.libseal becomes modules/greeter/. Leave root unset and the files stay inside the libseal package. Pass root: __dirname when you want them next to your own app instead.

const path = require("node:path");
const { sealSync, loadSealedSync } = require("libseal");

const password = "sample-password";

sealSync({
  from: path.join(__dirname, "..", "library"),
  to: path.join(__dirname, "greeter.libseal"),
  password,
});

const greeter = loadSealedSync({
  file: path.join(__dirname, "greeter.libseal"),
  modulePath: "greeter",
  password,
  force: true,
});

console.log(greeter.hello("Ada"));

Another password

Prefer not to put the password in source. Set LIBSEAL_PASSWORD and omit password on the call. The sample password and this one are only examples. Use your own.

export LIBSEAL_PASSWORD="open-sesame"
libseal seal ./library -o greeter.libseal
libseal mount greeter.libseal

mount decrypts into the package modules/ folder and prints the module names it found. The CLI never accepts the password as a flag. If LIBSEAL_PASSWORD is unset, it asks on the terminal and does not echo what you type.

The same variable works from JavaScript:

const { loadSealedSync } = require("libseal");

const greeter = loadSealedSync({
  file: "./greeter.libseal",
  modulePath: "greeter",
  force: true,
});

console.log(greeter.hello("Ada"));

Password lookup order:

  1. The password option, such as "sample-password".
  2. LIBSEAL_PASSWORD, such as "open-sesame".
  3. A hidden prompt, on the async functions and the CLI only.

sealSync, openSync, mountSync, and loadSealedSync do not prompt. Pass password, or set LIBSEAL_PASSWORD, before calling them.

Seal and open

const { sealSync, openSync } = require("libseal");

sealSync({
  from: "./library",
  to: "./greeter.libseal",
  password: "sample-password",
});

openSync({
  file: "./greeter.libseal",
  into: "./restored",
  password: "sample-password",
});

seal returns { to, bytes }. open returns { into }.

from is the directory to pack. to is the .libseal file to write. file is that sealed file. into is the folder to restore. force: true replaces into when it already contains files. Without force, a non-empty destination throws and is left as it was.

A wrong password, a truncated file, or a file edited after sealing all throw. Extraction writes a temporary directory first and moves it into place only after every file succeeds.

Empty directories and symlinks are not stored. Regular files keep their permission bits.

scrypt overrides the key cost: { N, r, p }. The default is { N: 16384, r: 8, p: 1 }. N must be a power of two from 1024 through 1048576. r is 1–32. p is 1–16.

Mount and load

Each sealed file gets its own folder, so a second library does not overwrite the first.

modules/
  greeter/
    greeter.js
  billing/
    quote.js
const { mountSync, readModule } = require("libseal");

const mounted = mountSync({
  file: "./greeter.libseal",
  password: "sample-password",
  force: true,
});

const greeter = readModule("greeter/greeter");
console.log(greeter.hello("Ada"));
console.log(mounted.modules);

mount returns { store, name, installDir, modules }.

| Field | Default | Meaning | | --- | --- | --- | | file | | The .libseal file | | password | LIBSEAL_PASSWORD, then a prompt | | | root | This package | Directory that contains the store | | store | "modules" | Folder name, or an absolute path | | name | File name without its extension | Subfolder inside the store | | force | false | Replace that subfolder when it already has files |

readModule(modulePath) loads one file from the store and returns its exports. "greeter/greeter" is modules/greeter/greeter.js. The .js extension is optional. Absolute paths and any .. segment are rejected. reload: true drops the require cache and runs the file again.

loadSealed is mount followed by readModule. Its modulePath is relative to the installed library, so "greeter" means the greeter.js inside modules/greeter/, not modules/greeter itself.

listSync() returns the library names in the store. listSync({ name: "greeter" }) returns the entries inside that library: directories, plus .js, .cjs, .mjs, .json, and .node files with the extension removed. A missing directory returns [].

releaseModule("greeter/greeter") removes one module from the require cache. It returns true when an entry was removed. The files on disk stay. Mount again with force: true to replace them.

readModule and loadSealed call require. The module runs in your process. Load libraries you sealed yourself. There is no postinstall script. Nothing is decrypted unless your code asks for it.

File format

A .libseal file is a gzip archive encrypted with AES-256-GCM. The key comes from scrypt. The header stores the scrypt cost, a random salt, a random IV, and the authentication tag. The password is not in the file. Sealing the same directory twice produces different ciphertext.

Every extracted path has to stay inside the destination directory.

Tests

npm test