diff --git a/app/meteor/meteor.js b/app/meteor/meteor.js index 7f0a29d5d9..d1a0b0004d 100644 --- a/app/meteor/meteor.js +++ b/app/meteor/meteor.js @@ -1,677 +1,681 @@ -var path = require('path'); -var files = require(path.join(__dirname, '..', 'lib', 'files.js')); -var _ = require(path.join(__dirname, '..', 'lib', 'third', 'underscore.js')); -var deploy = require(path.join(__dirname, 'deploy')); -var fs = require("fs"); -var runner = require(path.join(__dirname, 'run.js')); +var Fiber = require('fibers'); +Fiber(function () { -// This code is duplicated in app/server/server.js. -var MIN_NODE_VERSION = 'v0.8.11'; -if (require('semver').lt(process.version, MIN_NODE_VERSION)) { - process.stderr.write( - 'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n'); - process.exit(1); -} + var path = require('path'); + var files = require(path.join(__dirname, '..', 'lib', 'files.js')); + var _ = require('underscore'); + var deploy = require(path.join(__dirname, 'deploy')); + var fs = require("fs"); + var runner = require(path.join(__dirname, 'run.js')); -var usage = function() { - process.stdout.write( -"Usage: meteor [--version] [--help] []\n" + -"\n" + -"With no arguments, 'meteor' runs the project in the current\n" + -"directory in local development mode. You can run it from the root\n" + -"directory of the project or from any subdirectory.\n" + -"\n" + -"Use 'meteor create ' to create a new Meteor project.\n" + -"\n" + -"Commands:\n"); - _.each(Commands, function (cmd) { - if (cmd.help) { - var name = cmd.name + " ".substr(cmd.name.length); - process.stdout.write(" " + name + cmd.help + "\n"); - } - }); - process.stdout.write("\n"); -process.stdout.write( -"See 'meteor help ' for details on a command.\n"); - process.exit(1); -}; - -var require_project = function (cmd, accept_package) { - var app_dir = files.find_upwards(files.is_app_dir); - if (app_dir) - return app_dir; - - var package_dir = files.find_upwards(function (p) { - return files.is_package_dir(p) || files.is_package_collection_dir(p); - }); - if (package_dir) { - if (accept_package) - return package_dir; - - process.stdout.write(cmd + ": Only works on applications, not packages\n"); + // This code is duplicated in app/server/server.js. + var MIN_NODE_VERSION = 'v0.8.11'; + if (require('semver').lt(process.version, MIN_NODE_VERSION)) { + process.stderr.write( + 'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n'); process.exit(1); } - // This is where you end up if you type 'meteor' with no - // args. Be gentle to the noobs.. - process.stdout.write( -cmd + ": You're not in a Meteor project directory.\n" + -"\n" + -"To create a new Meteor project:\n" + -" meteor create \n" + -"For example:\n" + -" meteor create myapp\n" + -"\n" + -"For more help, see 'meteor --help'.\n"); - process.exit(1); -}; - -var find_mongo_port = function (cmd, callback) { - var app_dir = require_project(cmd); - var mongo_runner = require(path.join(__dirname, '..', 'lib', 'mongo_runner.js')); - mongo_runner.find_mongo_port(app_dir, callback); -}; - -Commands = []; - -var findCommand = function (name) { - for (var i = 0; i < Commands.length; i++) - if (Commands[i].name === name) - return Commands[i]; - process.stdout.write("'" + name + "' is not a Meteor command. See " + - "'meteor --help'.\n"); - process.exit(1); -}; - -var getSettings = function (filename) { - var str; - try { - str = fs.readFileSync(filename, "utf8"); - } catch (e) { - throw new Error("Could not find settings file " + filename); - } - if (str.length > 0x10000) { - throw new Error("Settings file must be less than 64 KB long"); - } - // Ensure that the string is parseable in JSON, but there's - // no reason to use the object value of it yet. - if (str.match(/\S/)) { - JSON.parse(str); - return str; - } else { - return ""; - } -}; - -// XXX when the pass unexpected argument or unrecognized flags, print -// an error and fail out - -Commands.push({ - name: "run", - help: "[default] Run this project in local development mode", - func: function (argv) { - // reparse args - // This help logic should probably move to run.js eventually - var opt = require('optimist') - .alias('port', 'p').default('port', 3000) - .describe('port', 'Port to listen on. NOTE: Also uses port N+1 and N+2.') - .boolean('production') - .describe('production', 'Run in production mode. Minify and bundle CSS and JS files.') - .describe('settings', 'Set optional data for Meteor.settings on the server') - .boolean('once') - .usage( -"Usage: meteor run [options]\n" + -"\n" + -"Searches upward from the current directory for the root directory of a\n" + -"Meteor project, then runs that project in local development\n" + -"mode. You can use the application by pointing your web browser at\n" + -"localhost:3000. No internet connection is required.\n" + -"\n" + -"Whenever you change any of the application's source files, the changes\n" + -"are automatically detected and applied to the running application.\n" + -"\n" + -"The application's database persists between runs. It's stored under\n" + -"the .meteor directory in the root of the project.\n"); - - var new_argv = opt.argv; - var settings = ""; - - if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - if (new_argv.settings) { - settings = getSettings(new_argv.settings); - } - - var app_dir = path.resolve(require_project("run", true)); // app or package - - var bundle_opts = { no_minify: !new_argv.production, symlink_dev_bundle: true }; - runner.run(app_dir, bundle_opts, new_argv.port, new_argv.once, settings); - } -}); - -Commands.push({ - name: "help", - func: function (argv) { - if (!argv._.length || argv.help) - usage(); - var cmd = argv._.splice(0,1)[0]; - argv.help = true; - findCommand(cmd).func(argv); - } -}); - -Commands.push({ - name: "create", - help: "Create a new project", - func: function (argv) { - // reparse args - var opt = require('optimist') - .describe('example', 'Example template to use.') - .boolean('list') - .describe('list', 'Show list of available examples.') - .usage( -"Usage: meteor create \n" + -" meteor create --example []\n" + -" meteor create --list\n" + -"\n" + -"Make a subdirectory named and create a new Meteor project\n" + -"there. You can also pass an absolute or relative path.\n" + -"\n" + -"You can pass --example to start off with a copy of one of the Meteor\n" + -"sample applications. Use --list to see the available examples."); - - var new_argv = opt.argv; - var appname; - - var example_dir = path.join(__dirname, '..', '..', 'examples'); - var examples = _.reject(fs.readdirSync(example_dir), function (e) { - return (e === 'unfinished' || e === 'other'); + var usage = function() { + process.stdout.write( + "Usage: meteor [--version] [--help] []\n" + + "\n" + + "With no arguments, 'meteor' runs the project in the current\n" + + "directory in local development mode. You can run it from the root\n" + + "directory of the project or from any subdirectory.\n" + + "\n" + + "Use 'meteor create ' to create a new Meteor project.\n" + + "\n" + + "Commands:\n"); + _.each(Commands, function (cmd) { + if (cmd.help) { + var name = cmd.name + " ".substr(cmd.name.length); + process.stdout.write(" " + name + cmd.help + "\n"); + } }); + process.stdout.write("\n"); + process.stdout.write( + "See 'meteor help ' for details on a command.\n"); + process.exit(1); + }; - if (argv._.length === 1) { - appname = argv._[0]; - } else if (argv._.length === 0 && new_argv.example) { - appname = new_argv.example; - } + var require_project = function (cmd, accept_package) { + var app_dir = files.find_upwards(files.is_app_dir); + if (app_dir) + return app_dir; - if (new_argv['list']) { - process.stdout.write("Available examples:\n"); - _.each(examples, function (e) { - process.stdout.write(" " + e + "\n"); - }); - process.stdout.write("\n" + -"Create a project from an example with 'meteor create --example '.\n") - process.exit(1); - }; + var package_dir = files.find_upwards(function (p) { + return files.is_package_dir(p) || files.is_package_collection_dir(p); + }); + if (package_dir) { + if (accept_package) + return package_dir; - if (argv.help || !appname) { - process.stdout.write(opt.help()); + process.stdout.write(cmd + ": Only works on applications, not packages\n"); process.exit(1); } - if (fs.existsSync(appname)) { - process.stderr.write(appname + ": Already exists\n"); - process.exit(1); + // This is where you end up if you type 'meteor' with no + // args. Be gentle to the noobs.. + process.stdout.write( + cmd + ": You're not in a Meteor project directory.\n" + + "\n" + + "To create a new Meteor project:\n" + + " meteor create \n" + + "For example:\n" + + " meteor create myapp\n" + + "\n" + + "For more help, see 'meteor --help'.\n"); + process.exit(1); + }; + + var find_mongo_port = function (cmd, callback) { + var app_dir = require_project(cmd); + var mongo_runner = require(path.join(__dirname, '..', 'lib', 'mongo_runner.js')); + mongo_runner.find_mongo_port(app_dir, callback); + }; + + Commands = []; + + var findCommand = function (name) { + for (var i = 0; i < Commands.length; i++) + if (Commands[i].name === name) + return Commands[i]; + process.stdout.write("'" + name + "' is not a Meteor command. See " + + "'meteor --help'.\n"); + process.exit(1); + }; + + var getSettings = function (filename) { + var str; + try { + str = fs.readFileSync(filename, "utf8"); + } catch (e) { + throw new Error("Could not find settings file " + filename); } - - if (files.find_app_dir(appname)) { - process.stderr.write( -"You can't create a Meteor project inside another Meteor project.\n"); - process.exit(1); + if (str.length > 0x10000) { + throw new Error("Settings file must be less than 64 KB long"); } + // Ensure that the string is parseable in JSON, but there's + // no reason to use the object value of it yet. + if (str.match(/\S/)) { + JSON.parse(str); + return str; + } else { + return ""; + } + }; - var transform = function (x) { - return x.replace(/~name~/g, path.basename(appname)); - }; + // XXX when the pass unexpected argument or unrecognized flags, print + // an error and fail out - if (new_argv.example) { - if (examples.indexOf(new_argv.example) === -1) { - process.stderr.write(new_argv.example + ": no such example\n\n"); - process.stderr.write("List available applications with 'meteor create --list'.\n"); + Commands.push({ + name: "run", + help: "[default] Run this project in local development mode", + func: function (argv) { + // reparse args + // This help logic should probably move to run.js eventually + var opt = require('optimist') + .alias('port', 'p').default('port', 3000) + .describe('port', 'Port to listen on. NOTE: Also uses port N+1 and N+2.') + .boolean('production') + .describe('production', 'Run in production mode. Minify and bundle CSS and JS files.') + .describe('settings', 'Set optional data for Meteor.settings on the server') + .boolean('once') + .usage( + "Usage: meteor run [options]\n" + + "\n" + + "Searches upward from the current directory for the root directory of a\n" + + "Meteor project, then runs that project in local development\n" + + "mode. You can use the application by pointing your web browser at\n" + + "localhost:3000. No internet connection is required.\n" + + "\n" + + "Whenever you change any of the application's source files, the changes\n" + + "are automatically detected and applied to the running application.\n" + + "\n" + + "The application's database persists between runs. It's stored under\n" + + "the .meteor directory in the root of the project.\n"); + + var new_argv = opt.argv; + var settings = ""; + + if (argv.help) { + process.stdout.write(opt.help()); process.exit(1); + } + if (new_argv.settings) { + settings = getSettings(new_argv.settings); + } + + var app_dir = path.resolve(require_project("run", true)); // app or package + + var bundle_opts = { no_minify: !new_argv.production, symlink_dev_bundle: true }; + runner.run(app_dir, bundle_opts, new_argv.port, new_argv.once, settings); + } + }); + + Commands.push({ + name: "help", + func: function (argv) { + if (!argv._.length || argv.help) + usage(); + var cmd = argv._.splice(0,1)[0]; + argv.help = true; + findCommand(cmd).func(argv); + } + }); + + Commands.push({ + name: "create", + help: "Create a new project", + func: function (argv) { + // reparse args + var opt = require('optimist') + .describe('example', 'Example template to use.') + .boolean('list') + .describe('list', 'Show list of available examples.') + .usage( + "Usage: meteor create \n" + + " meteor create --example []\n" + + " meteor create --list\n" + + "\n" + + "Make a subdirectory named and create a new Meteor project\n" + + "there. You can also pass an absolute or relative path.\n" + + "\n" + + "You can pass --example to start off with a copy of one of the Meteor\n" + + "sample applications. Use --list to see the available examples."); + + var new_argv = opt.argv; + var appname; + + var example_dir = path.join(__dirname, '..', '..', 'examples'); + var examples = _.reject(fs.readdirSync(example_dir), function (e) { + return (e === 'unfinished' || e === 'other'); + }); + + if (argv._.length === 1) { + appname = argv._[0]; + } else if (argv._.length === 0 && new_argv.example) { + appname = new_argv.example; + } + + if (new_argv['list']) { + process.stdout.write("Available examples:\n"); + _.each(examples, function (e) { + process.stdout.write(" " + e + "\n"); + }); + process.stdout.write("\n" + + "Create a project from an example with 'meteor create --example '.\n") + process.exit(1); + }; + + if (argv.help || !appname) { + process.stdout.write(opt.help()); + process.exit(1); + } + + if (fs.existsSync(appname)) { + process.stderr.write(appname + ": Already exists\n"); + process.exit(1); + } + + if (files.find_app_dir(appname)) { + process.stderr.write( + "You can't create a Meteor project inside another Meteor project.\n"); + process.exit(1); + } + + var transform = function (x) { + return x.replace(/~name~/g, path.basename(appname)); + }; + + if (new_argv.example) { + if (examples.indexOf(new_argv.example) === -1) { + process.stderr.write(new_argv.example + ": no such example\n\n"); + process.stderr.write("List available applications with 'meteor create --list'.\n"); + process.exit(1); + } else { + files.cp_r(path.join(example_dir, new_argv.example), appname, { + ignore: [/^local$/] + }); + } } else { - files.cp_r(path.join(example_dir, new_argv.example), appname, { + files.cp_r(path.join(__dirname, 'skel'), appname, { + transform_filename: function (f) { + return transform(f); + }, + transform_contents: function (contents, f) { + if ((/(\.html|\.js|\.css)/).test(f)) + return new Buffer(transform(contents.toString())); + else + return contents; + }, ignore: [/^local$/] }); } - } else { - files.cp_r(path.join(__dirname, 'skel'), appname, { - transform_filename: function (f) { - return transform(f); - }, - transform_contents: function (contents, f) { - if ((/(\.html|\.js|\.css)/).test(f)) - return new Buffer(transform(contents.toString())); - else - return contents; - }, - ignore: [/^local$/] - }); + + process.stderr.write(appname + ": created"); + if (new_argv.example && + new_argv.example !== appname) + process.stderr.write(" (from '" + new_argv.example + "' template)"); + process.stderr.write(".\n\n"); + + process.stderr.write( + "To run your new app:\n" + + " cd " + appname + "\n" + + " meteor\n"); } + }); - process.stderr.write(appname + ": created"); - if (new_argv.example && - new_argv.example !== appname) - process.stderr.write(" (from '" + new_argv.example + "' template)"); - process.stderr.write(".\n\n"); - - process.stderr.write( -"To run your new app:\n" + -" cd " + appname + "\n" + -" meteor\n"); - } -}); - -Commands.push({ - name: "update", - help: "Upgrade to the latest version of Meteor", - func: function (argv) { - if (argv.help) { - process.stdout.write( -"Usage: meteor update\n" + -"\n" + -"Checks to see if a new version of Meteor is available, and if so,\n" + -"downloads and installs it. You must be connected to the internet.\n"); - process.exit(1); - } - - require(path.join(__dirname, 'update.js')); - } -}); - -Commands.push({ - name: "add", - help: "Add a package to this project", - func: function (argv) { - if (argv.help || !argv._.length) { - process.stdout.write( -"Usage: meteor add [package] [package..]\n" + -"\n" + -"Adds packages to your Meteor project. You can add multiple\n" + -"packages with one command. For a list of the available packages, see\n" + -"'meteor list'.\n"); - process.exit(1); - } - - var app_dir = require_project('add'); - var packages = require(path.join(__dirname, '..', 'lib', 'packages.js')); - var project = require(path.join(__dirname, '..', 'lib', 'project.js')); - var all = packages.list(); - var using = {}; - _.each(project.get_packages(app_dir), function (name) { - using[name] = true; - }); - - _.each(argv._, function (name) { - if (!(name in all)) { - process.stderr.write(name + ": no such package\n"); - } else if (name in using) { - process.stderr.write(name + ": already using\n"); - } else { - project.add_package(app_dir, name); - var note = all[name].metadata.summary || ''; - process.stderr.write(name + ": " + note + "\n"); - } - }); - } -}); - -Commands.push({ - name: "remove", - help: "Remove a package from this project", - func: function (argv) { - if (argv.help || !argv._.length) { - process.stdout.write( -"Usage: meteor remove [package] [package..]\n" + -"\n" + -"Removes a package previously added to your Meteor project. For a\n" + -"list of the packages that your application is currently using, see\n" + -"'meteor list --using'.\n"); - process.exit(1); - } - - var app_dir = require_project('remove'); - var packages = require(path.join(__dirname, '..', 'lib', 'packages.js')); - var project = require(path.join(__dirname, '..', 'lib', 'project.js')); - var using = {}; - _.each(project.get_packages(app_dir), function (name) { - using[name] = true; - }); - - _.each(argv._, function (name) { - if (!(name in using)) { - process.stderr.write(name + ": not in project\n"); - } else { - project.remove_package(app_dir, name); - process.stderr.write(name + ": removed\n"); - } - }); - } -}); - -Commands.push({ - name: "list", - help: "List available packages", - func: function (argv) { - if (argv.help) { - process.stdout.write( -"Usage: meteor list [--using]\n" + -"\n" + -"Without arguments, lists all available Meteor packages. To add one\n" + -"of these packages to your project, see 'meteor add'.\n" + -"\n" + -"With --using, list the packages that you have added to your project.\n"); - process.exit(1); - } - - if (argv.using) { - var app_dir = require_project('list --using'); - var using = require(path.join(__dirname, '..', 'lib', 'project.js')).get_packages(app_dir); - - if (using.length) { - _.each(using, function (name) { - process.stdout.write(name + "\n"); - }); - } else { - process.stderr.write( -"This project doesn't use any packages yet. To add some packages:\n" + -" meteor add ...\n" + -"\n" + -"To see available packages:\n" + -" meteor list\n"); - } - return; - } - - var list = require(path.join(__dirname, '..', 'lib', 'packages.js')).list(); - var names = _.keys(list); - names.sort(); - var pkgs = []; - _.each(names, function (name) { - pkgs.push(list[name]); - }); - process.stdout.write("\n" + - require(path.join(__dirname, '..', 'lib', 'packages.js')).format_list(pkgs) + - "\n"); - } -}); - -Commands.push({ - name: "bundle", - help: "Pack this project up into a tarball", - func: function (argv) { - if (argv.help || argv._.length != 1) { - process.stdout.write( -"Usage: meteor bundle \n" + -"\n" + -"Package this project up for deployment. The output is a tarball that\n" + -"includes everything necessary to run the application. See README in\n" + -"the tarball for details.\n"); - process.exit(1); - } - - // XXX if they pass a file that doesn't end in .tar.gz or .tgz, - // add the former for them - - // XXX output, to stderr, the name of the file written to (for - // human comfort, especially since we might change the name) - - // XXX name the root directory in the bundle based on the basename - // of the file, not a constant 'bundle' (a bit obnoxious for - // machines, but worth it for humans) - - var app_dir = path.resolve(require_project("bundle")); - var build_dir = path.join(app_dir, '.meteor', 'local', 'build_tar'); - var bundle_path = path.join(build_dir, 'bundle'); - var output_path = path.resolve(argv._[0]); // get absolute path - - var bundler = require(path.join(__dirname, '..', 'lib', 'bundler.js')); - var errors = bundler.bundle(app_dir, bundle_path); - if (errors) { - process.stdout.write("Errors prevented bundling:\n"); - _.each(errors, function (e) { - process.stdout.write(e + "\n"); - }); - files.rm_recursive(build_dir); - process.exit(1); - } - - var cp = require('child_process'); - cp.execFile('/usr/bin/env', - ['tar', 'czf', output_path, 'bundle'], - {cwd: build_dir}, - function (error, stdout, stderr) { - if (error !== null) { - console.log(JSON.stringify(error)); - process.stderr.write("couldn't run tar\n"); - } else { - process.stdout.write(stdout); - process.stderr.write(stderr); - } - files.rm_recursive(build_dir); - }); - } -}); - -Commands.push({ - name: "mongo", - help: "Connect to the Mongo database for the specified site", - func: function (argv) { - var opt = require('optimist') - .boolean('url') - .boolean('U') - .alias('url', 'U') - .describe('url', 'return a Mongo database URL') - .usage( -"Usage: meteor mongo [--url] [site]\n" + -"\n" + -"Opens a Mongo shell to view or manipulate collections.\n" + -"\n" + -"If site is specified, this is the hosted Mongo database for the deployed\n" + -"Meteor site.\n" + -"\n" + -"If no site is specified, this is the current project's local development\n" + -"database. In this case, the current working directory must be a\n" + -"Meteor project directory, and the Meteor application must already be\n" + -"running.\n" + -"\n" + -"Instead of opening a shell, specifying --url (-U) will return a URL\n" + -"suitable for an external program to connect to the database. For remote\n" + -"databases on deployed applications, the URL is valid for one minute.\n" - ); - - if (argv.help) { - process.stdout.write(opt.help()); - process.exit(1); - } - - var new_argv = opt.argv; - - if (new_argv._.length === 1) { - // localhost mode - find_mongo_port("mongo", function (mongod_port) { - if (!mongod_port) { - process.stdout.write( -"mongo: Meteor isn't running.\n" + -"\n" + -"This command only works while Meteor is running your application\n" + -"locally. Start your application first.\n"); - process.exit(1); - } - - var mongo_url = "mongodb://127.0.0.1:" + mongod_port + "/meteor"; - - if (new_argv.url) - console.log(mongo_url); - else - deploy.run_mongo_shell(mongo_url); - }); - - } else if (new_argv._.length === 2) { - // remote mode - deploy.mongo(new_argv._[1], new_argv.url); - - } else { - // usage - process.stdout.write(opt.help()); - process.exit(1); - } - } -}); - -Commands.push({ - name: "deploy", - help: "Deploy this project to Meteor", - func: function (argv) { - var opt = require('optimist') - .alias('password', 'P') - .boolean('password') - .boolean('P') - .describe('password', 'set a password for this deployment') - .alias('delete', 'D') - .boolean('delete') - .boolean('D') - .describe('delete', "permanently delete this deployment") - .boolean('debug') - .describe('debug', 'deploy in debug mode (don\'t minify, etc)') - .boolean('tests') - .describe('settings', 'set optional data for Meteor.settings on the server') -// .describe('tests', 'deploy the tests instead of the actual application') - .usage( - // XXX document --tests in the future, once we publicly - // support tests -"Usage: meteor deploy [--password] [--settings settings.json] [--debug] [--delete]\n" + -"\n" + -"Deploys the project in your current directory to Meteor's servers.\n" + -"\n" + -"You can deploy to any available name under 'meteor.com'\n" + -"without any additional configuration, for example,\n" + -"'myapp.meteor.com'. If you deploy to a custom domain, such as\n" + -"'myapp.mydomain.com', then you'll also need to configure your domain's\n" + -"DNS records. See the Meteor docs for details.\n" + -"\n" + -"The --settings flag can be used to pass deploy-specific information to\n" + -"the application. It will be available at runtime in Meteor.settings, but only\n" + -"on the server. The argument is the name of a file containing the JSON data\n" + -"to use. The settings will persist across deployments until you again specify\n" + -"a settings file. To unset Meteor.settings, pass an empty settings file.\n" + -"\n" + -"The --delete flag permanently removes a deployed application, including\n" + -"all of its stored data.\n" + -"\n" + -"The --password flag sets an administrative password for the domain. Once\n" + -"set, any subsequent 'deploy', 'logs', or 'mongo' command will prompt for\n" + -"the password. You can change the password with a second 'deploy' command." - ); - - var new_argv = opt.argv; - - if (argv.help || new_argv._.length != 2) { - process.stdout.write(opt.help()); - process.exit(1); - } - - if (new_argv.delete) { - deploy.delete_app(new_argv._[1]); - } else { - var settings = undefined; - if (new_argv.settings) - settings = getSettings(new_argv.settings); - // accept packages iff we're deploying tests - var project_dir = path.resolve(require_project("bundle", new_argv.tests)); - deploy.deploy_app(new_argv._[1], project_dir, new_argv.debug, - new_argv.tests, new_argv.password, settings); - } - } -}); - -Commands.push({ - name: "logs", - help: "Show logs for specified site", - func: function (argv) { - if (argv.help || argv._.length < 1 || argv._.length > 2) { - process.stdout.write( -"Usage: meteor logs \n" + -"\n" + -"Retrieves the server logs for the requested site.\n"); - process.exit(1); - } - - deploy.logs(argv._[0]); - } -}); - -Commands.push({ - name: "reset", - help: "Reset the project state. Erases the local database.", - func: function (argv) { - if (argv.help) { - process.stdout.write( -"Usage: meteor reset\n" + -"\n" + -"Reset the current project to a fresh state. Removes all local\n" + -"data and kills any running meteor development servers.\n"); - process.exit(1); - } - - var app_dir = path.resolve(require_project("reset")); - - find_mongo_port("reset", function (mongod_port) { - if (mongod_port) { + Commands.push({ + name: "update", + help: "Upgrade to the latest version of Meteor", + func: function (argv) { + if (argv.help) { process.stdout.write( -"reset: Meteor is running.\n" + -"\n" + -"This command does not work while Meteor is running your application.\n" + -"Exit the running meteor development server.\n"); + "Usage: meteor update\n" + + "\n" + + "Checks to see if a new version of Meteor is available, and if so,\n" + + "downloads and installs it. You must be connected to the internet.\n"); process.exit(1); } - var local_dir = path.join(app_dir, '.meteor', 'local'); - files.rm_recursive(local_dir); + require(path.join(__dirname, 'update.js')); + } + }); - process.stdout.write("Project reset.\n"); - }); - } -}); + Commands.push({ + name: "add", + help: "Add a package to this project", + func: function (argv) { + if (argv.help || !argv._.length) { + process.stdout.write( + "Usage: meteor add [package] [package..]\n" + + "\n" + + "Adds packages to your Meteor project. You can add multiple\n" + + "packages with one command. For a list of the available packages, see\n" + + "'meteor list'.\n"); + process.exit(1); + } + + var app_dir = require_project('add'); + var packages = require(path.join(__dirname, '..', 'lib', 'packages.js')); + var project = require(path.join(__dirname, '..', 'lib', 'project.js')); + var all = packages.list(); + var using = {}; + _.each(project.get_packages(app_dir), function (name) { + using[name] = true; + }); + + _.each(argv._, function (name) { + if (!(name in all)) { + process.stderr.write(name + ": no such package\n"); + } else if (name in using) { + process.stderr.write(name + ": already using\n"); + } else { + project.add_package(app_dir, name); + var note = all[name].metadata.summary || ''; + process.stderr.write(name + ": " + note + "\n"); + } + }); + } + }); + + Commands.push({ + name: "remove", + help: "Remove a package from this project", + func: function (argv) { + if (argv.help || !argv._.length) { + process.stdout.write( + "Usage: meteor remove [package] [package..]\n" + + "\n" + + "Removes a package previously added to your Meteor project. For a\n" + + "list of the packages that your application is currently using, see\n" + + "'meteor list --using'.\n"); + process.exit(1); + } + + var app_dir = require_project('remove'); + var packages = require(path.join(__dirname, '..', 'lib', 'packages.js')); + var project = require(path.join(__dirname, '..', 'lib', 'project.js')); + var using = {}; + _.each(project.get_packages(app_dir), function (name) { + using[name] = true; + }); + + _.each(argv._, function (name) { + if (!(name in using)) { + process.stderr.write(name + ": not in project\n"); + } else { + project.remove_package(app_dir, name); + process.stderr.write(name + ": removed\n"); + } + }); + } + }); + + Commands.push({ + name: "list", + help: "List available packages", + func: function (argv) { + if (argv.help) { + process.stdout.write( + "Usage: meteor list [--using]\n" + + "\n" + + "Without arguments, lists all available Meteor packages. To add one\n" + + "of these packages to your project, see 'meteor add'.\n" + + "\n" + + "With --using, list the packages that you have added to your project.\n"); + process.exit(1); + } + + if (argv.using) { + var app_dir = require_project('list --using'); + var using = require(path.join(__dirname, '..', 'lib', 'project.js')).get_packages(app_dir); + + if (using.length) { + _.each(using, function (name) { + process.stdout.write(name + "\n"); + }); + } else { + process.stderr.write( + "This project doesn't use any packages yet. To add some packages:\n" + + " meteor add ...\n" + + "\n" + + "To see available packages:\n" + + " meteor list\n"); + } + return; + } + + var list = require(path.join(__dirname, '..', 'lib', 'packages.js')).list(); + var names = _.keys(list); + names.sort(); + var pkgs = []; + _.each(names, function (name) { + pkgs.push(list[name]); + }); + process.stdout.write("\n" + + require(path.join(__dirname, '..', 'lib', 'packages.js')).format_list(pkgs) + + "\n"); + } + }); + + Commands.push({ + name: "bundle", + help: "Pack this project up into a tarball", + func: function (argv) { + if (argv.help || argv._.length != 1) { + process.stdout.write( + "Usage: meteor bundle \n" + + "\n" + + "Package this project up for deployment. The output is a tarball that\n" + + "includes everything necessary to run the application. See README in\n" + + "the tarball for details.\n"); + process.exit(1); + } + + // XXX if they pass a file that doesn't end in .tar.gz or .tgz, + // add the former for them + + // XXX output, to stderr, the name of the file written to (for + // human comfort, especially since we might change the name) + + // XXX name the root directory in the bundle based on the basename + // of the file, not a constant 'bundle' (a bit obnoxious for + // machines, but worth it for humans) + + var app_dir = path.resolve(require_project("bundle")); + var build_dir = path.join(app_dir, '.meteor', 'local', 'build_tar'); + var bundle_path = path.join(build_dir, 'bundle'); + var output_path = path.resolve(argv._[0]); // get absolute path + + var bundler = require(path.join(__dirname, '..', 'lib', 'bundler.js')); + var errors = bundler.bundle(app_dir, bundle_path); + if (errors) { + process.stdout.write("Errors prevented bundling:\n"); + _.each(errors, function (e) { + process.stdout.write(e + "\n"); + }); + files.rm_recursive(build_dir); + process.exit(1); + } + + var cp = require('child_process'); + cp.execFile('/usr/bin/env', + ['tar', 'czf', output_path, 'bundle'], + {cwd: build_dir}, + function (error, stdout, stderr) { + if (error !== null) { + console.log(JSON.stringify(error)); + process.stderr.write("couldn't run tar\n"); + } else { + process.stdout.write(stdout); + process.stderr.write(stderr); + } + files.rm_recursive(build_dir); + }); + } + }); + + Commands.push({ + name: "mongo", + help: "Connect to the Mongo database for the specified site", + func: function (argv) { + var opt = require('optimist') + .boolean('url') + .boolean('U') + .alias('url', 'U') + .describe('url', 'return a Mongo database URL') + .usage( + "Usage: meteor mongo [--url] [site]\n" + + "\n" + + "Opens a Mongo shell to view or manipulate collections.\n" + + "\n" + + "If site is specified, this is the hosted Mongo database for the deployed\n" + + "Meteor site.\n" + + "\n" + + "If no site is specified, this is the current project's local development\n" + + "database. In this case, the current working directory must be a\n" + + "Meteor project directory, and the Meteor application must already be\n" + + "running.\n" + + "\n" + + "Instead of opening a shell, specifying --url (-U) will return a URL\n" + + "suitable for an external program to connect to the database. For remote\n" + + "databases on deployed applications, the URL is valid for one minute.\n" + ); + + if (argv.help) { + process.stdout.write(opt.help()); + process.exit(1); + } + + var new_argv = opt.argv; + + if (new_argv._.length === 1) { + // localhost mode + find_mongo_port("mongo", function (mongod_port) { + if (!mongod_port) { + process.stdout.write( + "mongo: Meteor isn't running.\n" + + "\n" + + "This command only works while Meteor is running your application\n" + + "locally. Start your application first.\n"); + process.exit(1); + } + + var mongo_url = "mongodb://127.0.0.1:" + mongod_port + "/meteor"; + + if (new_argv.url) + console.log(mongo_url); + else + deploy.run_mongo_shell(mongo_url); + }); + + } else if (new_argv._.length === 2) { + // remote mode + deploy.mongo(new_argv._[1], new_argv.url); + + } else { + // usage + process.stdout.write(opt.help()); + process.exit(1); + } + } + }); + + Commands.push({ + name: "deploy", + help: "Deploy this project to Meteor", + func: function (argv) { + var opt = require('optimist') + .alias('password', 'P') + .boolean('password') + .boolean('P') + .describe('password', 'set a password for this deployment') + .alias('delete', 'D') + .boolean('delete') + .boolean('D') + .describe('delete', "permanently delete this deployment") + .boolean('debug') + .describe('debug', 'deploy in debug mode (don\'t minify, etc)') + .boolean('tests') + .describe('settings', 'set optional data for Meteor.settings on the server') + // .describe('tests', 'deploy the tests instead of the actual application') + .usage( + // XXX document --tests in the future, once we publicly + // support tests + "Usage: meteor deploy [--password] [--settings settings.json] [--debug] [--delete]\n" + + "\n" + + "Deploys the project in your current directory to Meteor's servers.\n" + + "\n" + + "You can deploy to any available name under 'meteor.com'\n" + + "without any additional configuration, for example,\n" + + "'myapp.meteor.com'. If you deploy to a custom domain, such as\n" + + "'myapp.mydomain.com', then you'll also need to configure your domain's\n" + + "DNS records. See the Meteor docs for details.\n" + + "\n" + + "The --settings flag can be used to pass deploy-specific information to\n" + + "the application. It will be available at runtime in Meteor.settings, but only\n" + + "on the server. The argument is the name of a file containing the JSON data\n" + + "to use. The settings will persist across deployments until you again specify\n" + + "a settings file. To unset Meteor.settings, pass an empty settings file.\n" + + "\n" + + "The --delete flag permanently removes a deployed application, including\n" + + "all of its stored data.\n" + + "\n" + + "The --password flag sets an administrative password for the domain. Once\n" + + "set, any subsequent 'deploy', 'logs', or 'mongo' command will prompt for\n" + + "the password. You can change the password with a second 'deploy' command." + ); + + var new_argv = opt.argv; + + if (argv.help || new_argv._.length != 2) { + process.stdout.write(opt.help()); + process.exit(1); + } + + if (new_argv.delete) { + deploy.delete_app(new_argv._[1]); + } else { + var settings = undefined; + if (new_argv.settings) + settings = getSettings(new_argv.settings); + // accept packages iff we're deploying tests + var project_dir = path.resolve(require_project("bundle", new_argv.tests)); + deploy.deploy_app(new_argv._[1], project_dir, new_argv.debug, + new_argv.tests, new_argv.password, settings); + } + } + }); + + Commands.push({ + name: "logs", + help: "Show logs for specified site", + func: function (argv) { + if (argv.help || argv._.length < 1 || argv._.length > 2) { + process.stdout.write( + "Usage: meteor logs \n" + + "\n" + + "Retrieves the server logs for the requested site.\n"); + process.exit(1); + } + + deploy.logs(argv._[0]); + } + }); + + Commands.push({ + name: "reset", + help: "Reset the project state. Erases the local database.", + func: function (argv) { + if (argv.help) { + process.stdout.write( + "Usage: meteor reset\n" + + "\n" + + "Reset the current project to a fresh state. Removes all local\n" + + "data and kills any running meteor development servers.\n"); + process.exit(1); + } + + var app_dir = path.resolve(require_project("reset")); + + find_mongo_port("reset", function (mongod_port) { + if (mongod_port) { + process.stdout.write( + "reset: Meteor is running.\n" + + "\n" + + "This command does not work while Meteor is running your application.\n" + + "Exit the running meteor development server.\n"); + process.exit(1); + } + + var local_dir = path.join(app_dir, '.meteor', 'local'); + files.rm_recursive(local_dir); + + process.stdout.write("Project reset.\n"); + }); + } + }); -var main = function() { - var optimist = require('optimist') - .alias("h", "help") - .boolean("h") - .boolean("help") - .boolean("version") - .boolean("debug"); + var main = function() { + var optimist = require('optimist') + .alias("h", "help") + .boolean("h") + .boolean("help") + .boolean("version") + .boolean("debug"); - var argv = optimist.argv; + var argv = optimist.argv; - if (argv.help) { - argv._.splice(0, 0, "help"); - delete argv.help; - } + if (argv.help) { + argv._.splice(0, 0, "help"); + delete argv.help; + } - if (argv.version) { - var updater = require(path.join(__dirname, '..', 'lib', 'updater.js')); - var sha = updater.git_sha(); + if (argv.version) { + var updater = require(path.join(__dirname, '..', 'lib', 'updater.js')); + var sha = updater.git_sha(); - process.stdout.write("Meteor version " + updater.CURRENT_VERSION); + process.stdout.write("Meteor version " + updater.CURRENT_VERSION); - if (files.in_checkout()) - process.stdout.write(" (git checkout)"); - else if (sha) - process.stdout.write(" (" + sha.substr(0, 10) + ")"); + if (files.in_checkout()) + process.stdout.write(" (git checkout)"); + else if (sha) + process.stdout.write(" (" + sha.substr(0, 10) + ")"); - process.stdout.write("\n"); - process.exit(0); - } + process.stdout.write("\n"); + process.exit(0); + } - var cmd = 'run'; - if (argv._.length) - cmd = argv._.splice(0,1)[0]; + var cmd = 'run'; + if (argv._.length) + cmd = argv._.splice(0,1)[0]; - findCommand(cmd).func(argv); -}; + findCommand(cmd).func(argv); + }; -main(); + main(); +}).run();