npm.io
0.2.0 • Published 1h ago

@maka/meteor-sdk

Licence
MIT
Version
0.2.0
Deps
39
Size
111.4 MB
Vulns
24
Weekly
0

@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.

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: "METEOR@3.5.1" }); // 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. "METEOR@3.5.1") -- it pins which Meteor release's package versions and build behavior this process uses.

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; real minification, can take
                             // multiple minutes and multiple GB of RAM
                             // for a cold build -- pass "development"
                             // for a fast smoke build
});
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.

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.

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.

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()'s default minifyMode: "production" is slow -- pass "development" for fast iteration.
  • Two rare, environment-specific gaps, both 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; and two processes concurrently installing npm dependencies for different Meteor core packages at the same time can hit npm's own global-cache database is locked error.

License

MIT