Writing command line programs with meteor

Did you know that you can write command line programs with meteor? We’ve been doing this for quite some time now, since we got tired of of copying our server side meteor code into npm packages just in order to run batch, cron and on-demand jobs that share a lot of code with our webapp (we are DRY OCDs here).

We also preferred meteor’s cleaner requireless approach of writing code, reactive programming using meteor’s mongo and tracker packages, and using meteor’s ddp package, rather than the npm alternatives.

So, let’s see how to do that.

Show me the main’y

Every meteor app needs a single global main function which meteor calls to get the app going.

When you meteor create an app, meteor automatically adds the meteor-platform package to the app. This package automatically includes all the packages needed to create a meteor web app in your app, by api.implying them.

One of the packages it includes is the webapp package, which exports a main function that gets the web server running in your app.

In order to create a command line app, you will need to remove the meteor-platform package, add back the core meteor packages you want to use, and add a global main function. Let’s do that:

meteor create cli
cd cli

# Let's see what packages meteor added for us
cat .meteor/packages

# You will get a list of all the packages that meteor-platform included
meteor remove meteor-platform

# No need for those packages in a clientless app
meteor remove autopublish insecure

# Let's add back the core meteor environment, including the Meteor object and some of it's APIs
meteor add meteor

# We'll want back access to our mongodb collections using meteor's Mongo api
meteor add mongo

# How about some meteor server side reactive programming using meteor's Tracker api
meteor add tracker

# In case we want to access some 3rd party APIs using meteor's HTTP api 
meteor add http

# We don't need any client side or shared files in our server side app
rm cli.*

# Let's create the server folder which will store our main.js
mkdir server
cd server

Now, in the server folder, create a main.js file with your own main function:


main = function(argv) { console.log("I'm a meteor command line program!"); };

Let’s run our app, see what goes. You’ll need to run meteor with –once, since otherwise, everytime your program exits, meteor will restart it:

cd ..
meteor --once

Wuala! You just created your first meteor command line program.

As a learning experience, let’s run the app again, this time without –once, see what happens:

meteor

Familiar message, isn’t it? We got it because when you run meteor, meteor actually starts two programs, not one. The first one is a meteor web proxy app which is also responsible for:

  • Starting your actual meteor app (the 2nd program).

  • Trying to restart it every time it exists, because it assumes it crashed, which is usually the case with web apps, but not with command line programs.

  • Restarting your app every time it detects file changes in your app.

This is also why you get the application is crashing message in your browser, when a meteor web app crashes. The meteor web proxy is the one serving it, not your actual app.

Using the same mongodb in your web app and your command line program(s)

In development mode, every meteor app has it’s own internal mongodb that is located inside an app’s .meteor/local folder.

Therefore, if you want to share a mongodb between your webapp and your command line programs, you will need to use an external mongodb and export the MONGO_URL environment variable so your meteor apps can connect to it. I recommend a free sandbox database from compose.io (formerly mongohq), but there are other alternatives out there as well.

Handling command line arguments – practicalmeteor:mcli to the rescue

Since meteor doesn’t support command line arguments, and relies on Meteor.settings instead, we have created the practicalmeteor:mcli package, which is a package and scripts for easily creating and running command line programs, including support for command line options and arguments. It provides you with the following:

  1. A starter meteor command line program (along the lines of meteor create), which will get you going immediately.

  2. Support for registering multiple commands in the same command line program, using CLI.registerCommand()

  3. Support for registering asynchronous / daemon commands, that don’t exit immediately.

  4. The mcli script, which allows you to run your commands with options and arguments.

  5. Support for easily specifying default values for command line options.

For additional howto info, refer to the package’s README.

Known Issues

  1. You will not be able to use packages that depend directly or indirectly on any of the accounts packages. I’m working on a meteor pull request to solve this issue. You can see the issue here, and an outdated PR here.

  2. As a consequence of the above, the Meteor.users property will not be defined in the Meteor object. To workaround it, you can just define it yourself, pointing to the same collection:


Meteor.users = new Mongo.Collection('users');

Possible Applications

  1. Writing app specific load and stress testing tools using DDP.connect.

  2. Sending handlebars formatted emails to your customers using the excellent handlebars-server package.

  3. Sending spacebar formatted emails using the excellent ssr package from the living community legend arunoda 🙂

  4. mongodb data cleanup, maintenance and “schema” upgrade tools.

  5. Writing npm packages with meteor (YES YOU CAN!). On that, in a future post.

  6. And many many many many many (did i put enough many’s???) more.

Advertisements

12 thoughts on “Writing command line programs with meteor

  1. Hi Ronen, that’s an awesome article. I’m trying to decouple Meteor’s DB layer (livequery, ddp, mongo, minimongo). The purpose is to use Meteor only for data and develop the frontend separately.nnI removed meteor-platform and added those manually but then I had the error with the main function. I understand that creating the function manually solves this but I’m not sure about the –once parameter. In my case the server has to run indefinitely, not only once.nnDo you have any advice on how can I achieve this?

    Like

    1. By returning ‘DEAMON’ from the main function I made it work. Now I have another problem. I call a DDP method from the client but it never reaches the server even if the connection is made in the client.

      Like

      1. Andrei, the meteor DDP server is dependent on the webapp package, which, in command line programs is removed by removing meteor-platform. It seems that you’re trying to build a meteor ddp js library that will connect to a regular meteor web app from the browser, which is not what meteor command line programs are for. See this thread for more info on community requests and efforts to do this:nnhttps://groups.google.com/forum/#!searchin/meteor-core/arunoda$20blaze$20client/meteor-core/LMs3X16MUrU/hoB1EBP9lygJ

        Like

      2. Ronen, I’m not trying to build a command line program. I was using the same solutions for a different problem.nnnI see DDP as a separate package – why do you say it’s dependent on the webapp package? If it is, how can I achieve what I want?

        Like

    2. Hey andrei, glad you liked the article. The –once is only for non-daemon commands, where once exited, you don’t want meteor to rerun them. If you have a daemon, yes, you can return ‘DAEMON’, and don’t use –once if you want meteor to reload your app while coding. Also, take a look at practicalmeteor:mcli, which allows you to register async / daemon commands and then just call CLI.done() or process.exit() once you’re done.

      Like

  2. Hey Ronen, great article. You mentioned making a Meteor app into a NPM package. This is brilliant. I’ve seen a tool called demeteorize that could help with this.nnnI’m trying to include Meteor’s client, what would get sent to the browser, as a package inside another project, in this case, a React Native app. I’d like to do something likennnmyMeteorApp = require(‘meteorClientAppPackage’)n// connect to server somehown…nnn//then use within name spacenmyMeteorApp.Meteor.call(…)nmyMeteorApp.Tracker(…)nmyMeteorApp.Meteor.subscribe(…)nnnAny suggestions or considerations appreciate. Thanks, B

    Like

    1. Babak, sorry, only saw this now. I’m not that familiar with react native, so I don’t know what javascript engine it uses and what it supports / emulates, html / window / dom wise. There are a few popular npm ddp client packages out there people are using. Maybe you can use one of them as a base? Also, Andrei here, last time I talked with him, was able to create a js browser ddp client by extracting it out of a regular meteor build so he could use it with angular (but since then meteor added built-in support for angular). I do think that if react native will prove itself as a proper javascript based cross-platform mobile native UI solution, meteor will have built-in support for it. In a recent project of mine, though, I decided to go the famo.us way, for cross-platform native feeling UI – see here (the demo of the app, not the website): http://excenite.com/.

      Like

    2. Another comment I have on this is that I think a possibly web friendlier approach to have meteor and react native work together is actually creating cordova plugins that allow web code running in the cordova web view to manipulate the react native UI code, so some kind of a bridge between the web view and the react native engine. This way, you can keep using all your client side browser libraries (and meteor), and not deal with having to support the entire node.js and / or browser APIs inside the react native engine, which sounds like a huge challenge to me.

      Like

  3. Thanks Ronen for this article. It does work with an older version of Meteor, but with 1.3 it does not. Any idea how to go about it? I also tried the mcli github repo, same issue. Thanks

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s