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

ponte-nova

v0.1.0

Published

Bridging the gap between Node.js and Nova

Readme

Bridging the gap between Node.js and Nova

License Version: npm GitHub branch check runs

Description 🤓

/ˈpõtʃi ˈnɔvɐ/ – ”new bridge”

[!IMPORTANT]

This package is currently in an experimental state and likely not ready for use in production.

NodeJS provides a rich eco-system of packages, many of them useful when developing extensions for Nova, the text-editor by Panic. However, NodeJS and the Nova API aren't fully compatible. Packages relying on built-in modules will not work in Nova. Ponte Nova tries to change that by mapping NodeJS built-ins to their Nova counterparts.

This package provides a Rollup plugin that transforms your code to achieve that. However, the full NodeJS API cannot be covered and some methods have their own caveats. I would love to get feedback from extension developers to improve it, please see the known issues below.

Supported Built-ins

  • node:buffer
  • node:child_process
  • node:crypto
  • node:events
  • node:fs
  • node:fs/promises
  • node:os
  • node:path
  • node:process
  • node:url
  • node:util

Installation 💿

npm install ponte-nova

Usage 🚀

The plugin can be used with any bundler supporting the Rollup plugin API, including Vite, Rolldown, Farm and others.

import ponteNova from "ponte-nova";

export default {
  input: "src/main.ts",
  plugins: [ponteNova()],
  output: {
    file: "extension.js",
    format: "cjs",
  },
};
import { defineConfig } from "vite";
import ponteNova from "ponte-nova";

export default defineConfig({
  build: {
    lib: {
      entry: ["src/main.ts"],
      fileName: "extension",
      formats: ["cjs"],
    },
  },
  plugins: [ponteNova()],
});
import { defineConfig } from "tsdown";
import ponteNova from "ponte-nova";

export default defineConfig({
  entry: ["src/main.ts"],
  format: "cjs",
  plugins: [ponteNova()],
});

Options

| Option | Type | Default | Description | | ----------------- | -------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------- | | customAliases | Array<{ find: string \| RegExp; replacement: string }> | [] | Applied before the built-in aliases, so an entry here overrides the shim for that module. | | skipCommonJS | boolean | false | Skips @rollup/plugin-commonjs, which you then have to add yourself, before this plugin. | | skipNodeResolve | boolean | false | Skips @rollup/plugin-node-resolve, which you then have to add yourself, before this plugin. |

Known Issues 🐞

[!NOTE]

I'd like to go into dialog with extension developers about some of the decisions described below. You are welcome to challenge this decision by opening an issue.

Base Directory

In NodeJS, methods such as path.resolve or fs.mkdir use process.cwd() to determine a base directory. In Ponte Nova, we start from nova.extension.path instead. process.cwd() reports that same directory, and process.chdir() moves it for path and fs too.

Reading files without an encoding

As in NodeJS, fs.readFileSync(path) without an encoding returns a Buffer, and only fs.readFileSync(path, 'utf8') returns a string. Earlier versions returned a string in both cases.

Unsupported Path methods

Since Nova is Macintosh-only, all methods in path.win32 are not supported.

Hashing

Nova has no hashing API, so createHash and createHmac run on hash-wasm. Their signatures are exactly the NodeJS ones — createHash('sha256').update(data).digest('hex') is synchronous and returns a string — and the output is verified against NodeJS for every supported algorithm, digest encoding and HMAC key length.

There is one thing to do first. The only way into hash-wasm is WebAssembly.compile, which is asynchronous, so the compile has to happen before the first digest rather than during it:

const crypto = require('node:crypto');

exports.activate = async () => {
  await crypto.ready();
};

That takes a few milliseconds, and every hash afterwards is synchronous. ready() with no argument compiles every algorithm; pass a list, such as ready(['sha256']), to compile only what you use. A digest attempted before then throws and says so, rather than returning something wrong. The case this does not cover is a bundled package that hashes while it is being imported, before your activate runs.

Supported algorithms: blake2b512, blake2s256, md5, ripemd160, sha1, sha224, sha256, sha384, sha512, sha3-224, sha3-256, sha3-384, sha3-512, sm3. getHashes() reports them.

Note that an extension using createHash grows by roughly 116 kB, which is hash-wasm's WebAssembly embedded as base64.

Unsupported Crypto methods

Ciphers, key derivation and signing have no counterpart here and throw:

createCipher, createCipheriv, createDecipher, createDecipheriv, createDiffieHellman, createSign, createVerify, generateKeyPair, generateKeyPairSync, pbkdf2, pbkdf2Sync, scrypt, scryptSync

Globals

Buffer, process, URL and URLSearchParams are globals in NodeJS, so packages use them without importing anything. Nova has no such globals, and the plugin therefore rewrites every reference into an import of the corresponding shim. Importing node:buffer, node:process or node:url explicitly works too.

Buffer

The implementation extends Uint8Array, as it does in NodeJS, and covers all eight encodings, the numeric accessors, and the usual static helpers. Two details differ:

  • allocUnsafe zero-fills, because there is no uninitialised allocation available. That is slower than Node.js, never less safe.
  • Decoding malformed UTF-8 produces U+FFFD, but not necessarily the same number of them as Node.js. Well-formed input always round-trips.

Process

An extension runs inside the editor rather than as a standalone program, so parts of process have nothing to describe. What maps onto Nova works properly: env reads nova.environment, cwd and chdir share their directory with path and fs, nextTick uses the microtask queue, and stdout/stderr write whole lines to the console.

The rest is worth knowing about:

  • process.exit() throws, because an extension cannot terminate the editor. It stops execution where NodeJS would have, rather than letting code that assumed it was unreachable keep running.
  • process.version reports v20.0.0. No NodeJS runtime is involved, so this is a fiction, but packages parse it to pick a feature set and an empty value makes them fail outright.
  • process.env is a mutable copy. Assignments are visible to the rest of the extension and to nothing else — not the editor, not a subprocess.
  • process.pid is invented at load, so code using it to build unique names still gets uniqueness.
  • on('exit') and friends are accepted and stored, but nothing ever emits them.
  • getuid, getgid and umask are absent rather than stubbed, so that typeof process.getuid === 'function' gives the honest answer. A stub reporting uid 0 would tell a package it is running as root.
  • hrtime is derived from Date.now, so it is accurate to the millisecond and no further.
  • abort, kill, binding and dlopen throw.

Child processes

spawn, exec and execFile run on Nova's Process API and behave as expected: events, piped stdout/stderr, a writable stdin, cwd, env, timeout, maxBuffer, AbortSignal, and the promisified forms of exec and execFile.

The synchronous family cannot exist. Nova reports output and exit status through callbacks and JavaScript cannot block until they arrive, so execSync, execFileSync and spawnSync throw and point at their asynchronous counterparts. fork throws too, since there is no NodeJS runtime to fork into.

Three smaller differences:

  • Nova delivers output a line at a time rather than as a byte stream, so output that ended without a trailing newline gains one. Chunk boundaries always fall on newlines.
  • stdio is accepted and ignored. Every child is piped; Nova offers no equivalent of inherit or ignore, and none of detached, IPC or child.send().
  • A command without a slash in it is launched through /usr/bin/env, which performs the PATH lookup that NodeJS does internally and Nova does not.

Events

EventEmitter is plain JavaScript rather than anything Nova provides, so this is a real implementation and not an approximation: listener order, once unsubscribing before it fires, prependListener, listeners versus rawListeners, the newListener event, and an unhandled 'error' throwing all behave as they do in NodeJS. events.once(emitter, name) returns a promise, rejecting if the emitter errors first.

The shims use it themselves — a child process and its streams are emitters.

Util

promisify (including the promisify.custom protocol, which is how promisify(exec) returns a child process), callbackify, inherits, format, isDeepStrictEqual, types.*, deprecate and debuglog are all present and checked against NodeJS.

TextEncoder and TextDecoder live here too. JavaScriptCore provides neither, so they are built on the Buffer shim; TextDecoder handles UTF-8 only and throws for any other encoding rather than mis-decoding silently.

inspect is the one deliberate approximation. It covers the shapes that turn up in log output — objects, arrays, Map, Set, typed arrays, dates, regular expressions, circular references, depth limiting and the inspect.custom symbol — but it breaks lines at its own discretion, and shows a promise only as <pending>, because a promise's state cannot be read synchronously.

URL

URL and URLSearchParams are web platform APIs rather than language ones, so JavaScriptCore has neither and both are implemented here from the WHATWG algorithm: parsing, relative resolution against a base, percent-encoding, the property setters, and a searchParams that writes back to the URL it came from. The legacy url.parse, url.format and url.resolve are a separate, more permissive parser, as they are in Node.js — url.parse('/just/a/path') has to keep working rather than throw. fileURLToPath, pathToFileURL and urlToHttpOptions are all present.

Both are globals in Node.js, so the plugin injects them the way it injects Buffer and process.

Two things are approximated, and both concern host names only:

  • Internationalised domains are not punycoded. An ASCII host is handled properly; a non-ASCII one is passed through lowercased instead, and domainToASCII reports failure for it. The Unicode mapping tables that IDNA needs are larger than the rest of this package put together.
  • http://0x7f.1/ keeps its host as written where Node.js would report 127.0.0.1.

Approximated FileSystem errors

Nova's FileSystem API throws opaque errors, so the Node.js error codes are reconstructed by inspecting the path after a failure. The common cases — ENOENT, EEXIST, EISDIR, ENOTDIR, ENOTEMPTY, EACCES — come out right, but a failure with an unusual cause will be reported as EACCES.

Constants and errno values follow macOS, matching what Node.js reports there rather than on Linux.

Unsupported FileSystem methods

chmod, chmodSync, chown, chownSync, createReadStream, createWriteStream, fchmod, fchmodSync, fchown, fchownSync, fdatasync, fdatasyncSync, fstat, fstatSync, fsync, fsyncSync, ftruncate, ftruncateSync, futimes, futimesSync, lchmod, lchmodSync, lchown, lchownSync, link, linkSync, lutimes, lutimesSync, readlink, readlinkSync, symlink, symlinkSync, truncate, truncateSync, unwatchFile, utimes, utimesSync, watch, watchFile

Stale OS information

The NodeJS os API is synchronous, but Nova can only read most of these values through its async-only Process API. Values describing the machine rather than its current state — arch, hostname, release, version, machine, totalmem, cpus and uptime — are therefore cached to disk and read back synchronously on the next activation.

On the very first activation, before that cache exists, these methods return fallback values (arm64, empty strings, zeroes). They become accurate once the background refresh completes, and on every activation thereafter.

Unsupported OS methods

getPriority, setPriority

License ©️

This work is licensed under The MIT License.