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

@maka/meteor-sdk

v0.2.20

Published

Programmatic Node API over the Meteor build tool (bundle/run/create/etc.) -- for driving Meteor's build system from plain Node code instead of shelling out to a meteor CLI (there isn't one anymore; see README.md). "dependencies" is generated by scripts/wr

Readme

@maka/meteor-sdk

A programmatic Node.js API over Meteor's build tool -- for driving Meteor's build system (bundle, run, test, create, ...) from plain Node code instead of shelling out to a meteor CLI subprocess. There is no CLI in this package; every operation is a promise-based function you call directly.

This SDK is designed to work with @maka/maka-cli, which builds its run/build/create commands directly on top of this package's API. It's also usable standalone by any Node process that wants programmatic control over a Meteor app's build/run lifecycle.

Requirements

Node.js >=22.5.0. (One core module, node:sqlite, isn't available on earlier versions -- Node 20 cannot run this package at all.)

Install

npm install @maka/meteor-sdk

Quick start

const sdk = require("@maka/meteor-sdk");

await sdk.init({ release: "[email protected]" }); // call once per process,
                                              // matching the app's own
                                              // .meteor/release
const project = await sdk.loadProject("/path/to/app");
const result = await sdk.bundle(project, { outputPath: "/path/to/output" });
console.log(result.starManifest);

process.exit(0); // simplest way to end a one-shot script -- see close()
                  // below if your process needs to keep running afterward

API

init(options)

Call once per process before anything else. options.release must match the target app's own .meteor/release (e.g. "[email protected]") -- it pins which Meteor release's package versions and build behavior this process uses.

Passing a specific release requires that release to be present in the local package catalog (the Meteor warehouse) -- on a machine that has never downloaded it, init rejects with a MeteorSdkError naming the missing release. Embedders that only need connectDdp() (or other non-build APIs) should call init({}) with no release: that uses checkout semantics (the tool copy this package itself ships), needs no warehouse or catalog access, and works on a machine whose only Meteor footprint is this npm package.

loadProject(appDir)

Resolves an app directory into a Project, ready to bundle or run. Read-only: resolves package constraints and versions but doesn't build anything yet.

bundle(project, options)

Produces a deployable bundle (a star.json manifest plus program output), the same output meteor build produces.

await sdk.bundle(project, {
  outputPath: "/path/to/output",
  minifyMode: "production", // DEFAULT IS "development" -- pass
                            // "production" explicitly for deploys.
                            // Real minification can take minutes and
                            // multiple GB of RAM on a cold build.
  buildMode: "production",  // default; also "development"/"test"
  serverArch: undefined,    // default: the build host's arch
  webArchs: undefined,      // default: the project's platform list
  programs: "all",          // "all" (default) | "server" | "client"
});

Split server/client bundles (programs)

programs: "server" writes a bundle with no client programs whose programs/server/config.json still records the intended clientArchs. It boots normally and serves pages with 404 until the client programs exist -- webapp logs a note per reload and starts serving the moment they appear (process restart, or SIGHUP, which autoupdate already wires to a client-program reload plus a hot-code push to connected browsers).

programs: "client" writes a standalone client artifact: a star.json listing only the web programs plus programs/<arch>/ trees that are byte-identical to a full bundle's -- so they are drop-in siblings for a server bundle. Copy programs/<arch> into the server bundle's programs/ directory (never untar the client artifact wholesale over a server bundle: its root star.json would clobber the server's), or point the server at an external location with METEOR_CLIENT_BUNDLE_DIR=/path/to/client-artifact/programs (honored by both webapp's static serving and dynamic import()).

Swap discipline for live servers: activate a new client release with an atomic symlink flip, never by copying into the live directory -- a half-written program.json observed by a reload is a fatal error by design. Client-only deploys are for client-safe changes; anything touching methods, publications or schemas ships server-first or as a full bundle.

createAppRunner(project, options)

Runs an app the way meteor run does, with a stoppable handle instead of a blocking process.

const handle = await sdk.createAppRunner(project, {
  port: 3000,
  mongoUrl: "mongodb://127.0.0.1:27017/myapp", // required -- see below
  once: false, // if true, exits after one run instead of rebuild-on-change
  buildOptions: { minifyMode: "development" },
});

handle.on("exit", (result) => { /* a run ended: crash, rebuild, ... */ });

await handle.start(); // resolves once the proxy + app are both listening
// app is now serving http://localhost:3000
await handle.stop(); // stops the proxy, then the app; no orphaned processes

No Mongo lifecycle management. Unlike meteor run, createAppRunner() does not start or stop a mongod for you -- supply a running Mongo's connection string via mongoUrl. For an app that doesn't use the mongo package at all, pass mongoUrl: null explicitly: no MONGO_URL is set for the app process. (An app that does load the mongo package will crash at startup without one -- the explicit null is how you take responsibility for that.)

There is no separate stdout/stderr event -- all app log output is observable via sdk.on("log", ...) (see below).

testRun(project, options)

Runs an app's own tests, the way plain meteor test does (app test mode -- not meteor test-packages' synthetic-app mode, which this package doesn't cover).

const handle = await sdk.testRun(project, {
  port: 3000,
  mongoUrl: "mongodb://127.0.0.1:27017/myapp",
  driverPackage: "test-in-browser", // required -- must already be in the project
  fullApp: false, // matches `meteor test`'s --full-app
});
// same handle shape as createAppRunner(): start() / stop() / on("exit", ...)

create(targetDir, options)

Scaffolds a new Meteor app from a built-in skeleton template, the way meteor create does.

const result = await sdk.create("/path/to/new-app", {
  skeleton: "react", // default; see sdk.AVAILABLE_SKELETONS for the full list
  appName: "my-app",
  installDependencies: true,
});
// => { appPath, appName, skeleton }

connectDdp(url, options)

Opens a long-lived DDP client connection to a running Meteor server -- the same protocol and client (ddp-client, bundled in this package's isopackets) the tool itself uses to talk to Meteor services. Resolves once the first DDP handshake completes; after that, ddp-client's own reconnect machinery (exponential backoff, automatic re-subscribe, re-send of in-flight method calls) keeps the connection alive until you close() it.

const conn = await sdk.connectDdp("https://app.example.com", {
  headers: { Authorization: "Bearer ..." }, // extra WS handshake headers
  tls: { ca, cert, key, rejectUnauthorized }, // Node tls.connect() options
  firstConnectTimeoutMs: 30000, // reject if never connected by then
  retry: true,                  // default; false = no auto-reconnect
});

const result = await conn.call("someMethod", arg1, arg2); // concurrent-safe
const sub = await conn.subscribe("pubName", ...args); // resolves on ready
conn.on("connected", ({ reconnect }) => { /* every (re)connect */ });
conn.on("disconnected", ({ error }) => { /* transport drop */ });
conn.status();     // ddp-client status snapshot
conn.disconnect(); // temporary offline (stops retrying)...
conn.reconnect();  // ...and back
conn.raw;          // the underlying ddp-client connection
conn.close();      // permanent -- no reconnects after this

The URL's scheme picks the transport: https:///wss:// gives you a TLS websocket; HTTPS_PROXY/NO_PROXY are honored. call() rejects with the server's own Meteor.Error (structured error/reason/ details fields intact); subscribe() rejects with the server's sub error if the subscription fails before first becoming ready. Logging in to an accounts-enabled server is a plain method call: await conn.call("login", { resume: token }).

close()

Releases file-watch resources opened by loadProject()/bundle()/etc.

For a one-shot script/CLI command that's about to exit anyway, prefer skipping close() entirely and calling process.exit() directly once your work is done, rather than awaiting close() first -- verified to sidestep the issue below cleanly, with no downside for a process that isn't doing anything else afterward. close() still exists for longer-lived embedders that need to release watch handles mid-process without exiting.

If you do call it: with the default native watcher backend, the process can occasionally hang on Windows due to a @parcel/watcher native-addon limitation outside this package's control (its background thread doesn't release on unsubscribe(), and its public API has no lower-level shutdown hook) -- and, observed specifically when the host process is launched from Git Bash/MSYS2 on Windows (not from PowerShell/cmd), that same untorn-down thread can crash the process outright (SIGSEGV) during teardown instead of just hanging. Workarounds, in addition to skipping close() above: call process.exit() after close() resolves regardless of whether it hangs, or set METEOR_MODERN='{"watcher":false}' to use the plain polling watcher instead, which doesn't exhibit either symptom.

setConsoleOutput(enabled)

By default the tool's Console renders informational output to this process's stdout/stderr and emits it as log events. An embedder with its own progress UI (spinners, status lines) can call sdk.setConsoleOutput(false) to suppress the terminal rendering -- newline-terminated Console writes landing between spinner redraws garble the terminal otherwise -- and paint the emitted events itself. Scoping it to a phase (off around a build, back on after) is the intended use; events keep flowing either way.

setProgressDisplay(enabled)

The tool's own interactive progress bar (the classic meteor run "Building the application" bar) is disabled by default for embedders. sdk.setProgressDisplay(true) turns it back on for long build phases -- it renders only when this process's stdout is a real TTY, and falls back to no display otherwise, so it's always safe to call. Turn it off again with false when your own UI takes over the terminal.

Events

sdk.on("log", ({ level, args }) => { /* ... */ });
sdk.off("log", listener);

Structured log events for the handful of informational messages Meteor's build tooling emits outside of any build result.

Errors

Every exported function rejects with a real Error -- MeteorSdkError for usage errors (calling something before init(), an unknown skeleton, ...) or MeteorBuildError for build failures (with a .messages array of the underlying build errors) -- never a raw exit code or an unstructured message set.

npm dependency cache

Every bundled Meteor core package ships with its npm-shrinkwrap.json but without node_modules, so the first build after a fresh SDK install historically paid a serial npm install per package (the wall of "<package>: updating npm dependencies ..." lines). The SDK now keeps a per-user cache of completed node_modules trees at ~/.meteor-npm-cache, keyed by shrinkwrap content + Node compatibility version + platform/arch. On the first build after an SDK update, packages whose dependencies didn't change restore from the cache by parallel directory copy instead of spawning npm -- silently and near-instantly. Cache misses are handled in parallel too: once loadProject() knows the app's package set (from .meteor/versions), the remaining installs run through a small npm worker pool (bounded to 3 processes -- concurrent npm invocations contend on npm's shared content cache), and build plugins' own npm trees (.npm/plugin/<name>) are covered the same way.

Both prewarm stages (the global restore pass in init(), and the app-scoped restore+install pass in loadProject()) are fully awaited before any build proceeds -- this matters specifically for createAppRunner()'s watched run: its file watcher treats each package's npm-shrinkwrap.json as a build dependency, so an install finishing after the watcher was already set up would look like a source change and trigger a spurious restart. Resolving every dependency up front, before the watcher exists, means there's nothing left for it to react to. Cache writes happen in the background off the build's critical path, entries are pruned to the 3 newest per package, and everything is best-effort: any cache problem falls back to a normal npm install, and a stale restore is caught by the existing shrinkwrap-vs-installed verification.

Control it with METEOR_PACKAGE_NPM_CACHE: unset means enabled at the default location, 0/false/no/off disables it, and any other value is used as the cache root directory. Set METEOR_WATCH_DEBUG=1 to log detailed file-watcher activity (which path changed, what fired, why) if you ever need to diagnose unexpected rebuilds.

Known limitations

  • No Cordova, no publish/package-server admin operations, no springboarding/multi-release-per-process. Out of scope for this SDK.
  • createAppRunner()/testRun() don't manage a Mongo process -- bring your own mongoUrl.
  • bundle() defaults to minifyMode: "development" (fast, no real minification) -- deploys must pass "production" explicitly, and that mode is legitimately slow on a cold build. (Earlier revisions of this README claimed production was the default; the code never agreed.)
  • A rare, environment-specific gap, out of scope for this package to fix: a from-scratch install can hit an npm/Windows spawn issue when a dependency needs a native rebuild for the first time.
  • Two different SDK processes each doing a from-scratch install at the same time can still hit npm's own global-cache database is locked error -- the npm worker pool above only bounds concurrency within one process.

License

MIT