diff --git a/commands/init.js b/commands/init.js
index c5c166d6..83819f54 100644
--- a/commands/init.js
+++ b/commands/init.js
@@ -1,112 +1,103 @@
'use strict';
+
+var chalk = require('chalk');
+var fs = require('fs');
+var homeDir = require('user-home');
+var path = require('path');
+var RSVP = require('rsvp');
+
var Command = require('../lib/command');
var Config = require('../lib/config');
-var prompt = require('../lib/prompt');
-var fs = require('fs-extra');
-var fsutils = require('../lib/fsutils');
-var path = require('path');
-var defaultConfig = require('../templates/firebase.json');
-var _ = require('lodash');
+var init = require('../lib/init');
var logger = require('../lib/logger');
-var homeDir = require('user-home');
-var chalk = require('chalk');
-var api = require('../lib/api');
+var prompt = require('../lib/prompt');
var requireAuth = require('../lib/requireAuth');
-var RSVP = require('rsvp');
-var FirebaseError = require('../lib/error');
var utils = require('../lib/utils');
-var NEW_PROJECT = '[create a new project]';
+var BANNER_TEXT = fs.readFileSync(__dirname + '/../templates/banner.txt', 'utf8');
var _isOutside = function(from, to) {
return path.relative(from, to).match(/^\.\./);
};
-module.exports = new Command('init')
- .description('set up a Firebase app in the current directory')
- .option('-p, --public
', 'the name of your app\'s public directory')
+module.exports = new Command('init [feature]')
+ .description('setup a Firebase project in the current directory')
.before(requireAuth)
- .action(function(options) {
+ .action(function(feature, options) {
var cwd = options.cwd || process.cwd();
+ var warnings = [];
+ var warningText = '';
if (_isOutside(homeDir, cwd)) {
- utils.logWarning(chalk.bold.yellow('Caution!') + ' Initializing outside your home directory');
+ warnings.push('You are currently outside your home directory');
}
if (cwd === homeDir) {
- utils.logWarning(chalk.bold.yellow('Caution!') + ' Initializing directly at your home directory');
+ warnings.push('You are initializing your home directory as a Firebase project');
}
var config = Config.load(options, true);
- if (config) {
- return RSVP.reject(new FirebaseError('Cannot run init, already inside a project directory:\n\n' + chalk.bold(config.projectDir)));
+ var existingConfig = !!config;
+ if (!existingConfig) {
+ config = new Config({}, {projectDir: cwd, cwd: cwd});
+ } else {
+ warnings.push('You are initializing in an existing Firebase project directory');
}
- var fileCount = _.difference(fs.readdirSync(cwd), ['firebase-debug.log']).length;
- if (fileCount !== 0) {
- utils.logWarning('Initializing in a directory with ' + chalk.bold(fileCount) + ' files');
- logger.warn();
+ if (warnings.length) {
+ warningText = '\nBefore we get started, keep in mind:\n\n ' + chalk.yellow.bold('* ') + warnings.join('\n ' + chalk.yellow.bold('* ')) + '\n';
}
- return api.getProjects().then(function(projects) {
- var projectIds = Object.keys(projects).sort();
- var nameOptions = [NEW_PROJECT].concat(projectIds);
+ if (process.platform === 'darwin') {
+ BANNER_TEXT = BANNER_TEXT.replace(/#/g, '🔥');
+ }
+ logger.info(chalk.yellow.bold(BANNER_TEXT) +
+ '\nYou\'re about to initialize a Firebase project in this directory:\n\n ' + chalk.bold(config.projectDir) + '\n' +
+ warningText
+ );
- return prompt(options, [
+ var setup = {
+ config: config._src,
+ rcfile: config.readProjectFile('.firebaserc', {
+ json: true,
+ fallback: {}
+ })
+ };
+
+ var chooseFeatures;
+ var choices = [
+ {name: 'database', label: 'Database: Deploy rules for your Firebase Realtime Database', checked: true},
+ {name: 'functions', label: 'Functions: Configure and deploy Firebase Functions', checked: true},
+ {name: 'hosting', label: 'Hosting: Configure and deploy Firebase Hosting sites', checked: true},
+ {name: 'storage', label: 'Storage: Deploy rules for Firebase Storage', checked: true}
+ ];
+ // TODO: Splice out functions when no preview enabled.
+ if (feature) {
+ setup.features = [feature];
+ chooseFeatures = RSVP.resolve();
+ } else {
+ chooseFeatures = prompt(setup, [
{
- type: 'list',
- name: 'project',
- message: 'What Firebase project do you want to use?',
- validate: function(answer) {
- if (!_.includes(nameOptions, answer)) {
- return 'Must specify a Firebase to which you have access';
- }
- return true;
- },
- choices: nameOptions
- },
- {
- type: 'input',
- name: 'public',
- message: 'What directory should be the public root?',
- default: 'public',
- validate: function(answer) {
- if (_isOutside(cwd, answer)) {
- return 'Must be within the current directory';
- }
- return true;
- },
- filter: function(input) {
- input = path.relative(cwd, input);
- if (input === '') { input = '.'; }
- return input;
- },
- when: function(answers) {
- return answers.project !== NEW_PROJECT;
- }
+ type: 'checkbox',
+ name: 'features',
+ message: 'What Firebase CLI features do you want to setup for this folder?',
+ choices: prompt.convertLabeledListChoices(choices)
}
- ]).then(function() {
- if (options.project === NEW_PROJECT) {
- logger.info('Please visit', chalk.underline('https://firebase.google.com'), 'to create a new project, then run', chalk.bold('firebase init'), 'again.');
- return RSVP.resolve();
- }
- var absPath = path.resolve(cwd, options.public || '.');
- if (!fsutils.dirExistsSync(absPath)) {
- fs.mkdirsSync(absPath);
- logger.info(chalk.green('✔ '), 'Public directory', chalk.bold(options.public), 'has been created');
- }
-
- var publicPath = path.relative(cwd, options.public);
- if (publicPath === '') {
- publicPath = '.';
- }
- var out = JSON.stringify(_.extend({}, defaultConfig, {
- firebase: options.project,
- public: publicPath
- }), undefined, 2);
-
- fs.writeFileSync(path.join(cwd, 'firebase.json'), out);
- logger.info('Firebase initialized, configuration written to firebase.json');
- return path.resolve(path.join(cwd, 'firebase.json'));
+ ]);
+ }
+ return chooseFeatures.then(function() {
+ setup.features = setup.features.map(function(feat) {
+ return prompt.listLabelToValue(feat, choices);
});
+ console.log(setup.features);
+ setup.features.push('project');
+ return init(setup, config, options);
+ }).then(function() {
+ logger.info();
+ utils.logBullet('Writing configuration info to ' + chalk.bold('firebase.json') + '...');
+ config.writeProjectFile('firebase.json', setup.config);
+ utils.logBullet('Writing project information to ' + chalk.bold('.firebaserc') + '...');
+ config.writeProjectFile('.firebaserc', setup.rcfile);
+ logger.info();
+ utils.logSuccess('Firebase initialization complete!');
});
});
diff --git a/commands/prefs-token.js b/commands/prefs-token.js
index ee9e88ad..b52591df 100644
--- a/commands/prefs-token.js
+++ b/commands/prefs-token.js
@@ -2,14 +2,14 @@
var Command = require('../lib/command');
var logger = require('../lib/logger');
-var configstore = require('../lib/configstore');
var requireAuth = require('../lib/requireAuth');
module.exports = new Command('prefs:token')
.description('print the currently logged in user\'s access token')
.before(requireAuth)
- .action(function() {
- var token = configstore.get('tokens').refresh_token;
+ .option('--temporary', 'only provide a temporary access token that will expire in an hour')
+ .action(function(options) {
+ var token = options.temporary ? options.tokens.access_token : options.tokens.refresh_token;
logger.info(token);
return token;
});
diff --git a/commands/use.js b/commands/use.js
index 48a2d400..582d9912 100644
--- a/commands/use.js
+++ b/commands/use.js
@@ -2,7 +2,6 @@
var Command = require('../lib/command');
var logger = require('../lib/logger');
-var configstore = require('../lib/configstore');
var requireAuth = require('../lib/requireAuth');
var api = require('../lib/api');
var chalk = require('chalk');
@@ -12,17 +11,6 @@ var fs = require('fs');
var path = require('path');
var prompt = require('../lib/prompt');
-var makeActive = function(projectDir, newActive) {
- var activeProjects = configstore.get('activeProjects') || {};
- if (newActive) {
- activeProjects[projectDir] = newActive;
- } else {
- _.unset(activeProjects, projectDir);
- }
-
- configstore.set('activeProjects', activeProjects);
-};
-
var writeAlias = function(projectDir, rc, alias, projectId) {
if (projectId) {
_.set(rc, ['projects', alias], projectId);
@@ -89,10 +77,10 @@ module.exports = new Command('use [alias_or_project_id]')
return utils.reject('Unable to use alias ' + chalk.bold(newActive) + ', ' + verifyMessage(aliasedProject));
}
- makeActive(options.projectRoot, newActive);
+ utils.makeActiveProject(options.projectRoot, newActive);
logger.info('Now using alias', chalk.bold(newActive), '(' + aliasedProject + ')');
} else if (projects[newActive]) { // exact project id specified
- makeActive(options.projectRoot, newActive);
+ utils.makeActiveProject(options.projectRoot, newActive);
logger.info('Now using project', chalk.bold(newActive));
} else { // no alias or project recognized
return utils.reject('Invalid project selection, ' + verifyMessage(newActive));
@@ -125,14 +113,14 @@ module.exports = new Command('use [alias_or_project_id]')
}
]).then(function() {
writeAlias(options.projectRoot, options.rc, results.alias, results.project);
- makeActive(options.projectRoot, results.alias);
+ utils.makeActiveProject(options.projectRoot, results.alias);
logger.info();
logger.info('Created alias', chalk.bold(results.alias), 'for', results.project + '.');
logger.info('Now using alias', chalk.bold(results.alias) + ' (' + results.project + ')');
});
});
} else if (options.clear) { // firebase use --clear
- makeActive(options.projectRoot, null);
+ utils.makeActiveProject(options.projectRoot, null);
options.projectAlias = null;
options.project = null;
logger.info('Cleared active project.');
diff --git a/lib/config.js b/lib/config.js
index 3bcfdc71..48802faa 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -1,20 +1,25 @@
'use strict';
-var cjson = require('cjson');
-var FirebaseError = require('./error');
var _ = require('lodash');
-var resolveProjectPath = require('./resolveProjectPath');
+var chalk = require('chalk');
+var cjson = require('cjson');
+var fs = require('fs-extra');
var path = require('path');
+var RSVP = require('rsvp');
+
+var detectProjectRoot = require('./detectProjectRoot');
+var FirebaseError = require('./error');
var fsutils = require('./fsutils');
var loadCJSON = require('./loadCJSON');
var parseBoltRules = require('./parseBoltRules');
+var prompt = require('./prompt');
+var resolveProjectPath = require('./resolveProjectPath');
var triggerRulesGenerator = require('./triggerRulesGenerator');
-var detectProjectRoot = require('./detectProjectRoot');
-var chalk = require('chalk');
var utils = require('./utils');
+
var Config = function(src, options) {
this.options = options || {};
- this.projectDir = detectProjectRoot(options.cwd);
+ this.projectDir = options.projectDir || detectProjectRoot(options.cwd);
this._src = src;
this.data = {};
@@ -144,6 +149,54 @@ Config.prototype.path = function(pathName) {
return outPath;
};
+Config.prototype.readProjectFile = function(p, options) {
+ try {
+ var content = fs.readFileSync(this.path(p), 'utf8');
+ if (options.json) {
+ return JSON.parse(content);
+ }
+ return content;
+ } catch (e) {
+ if (options.fallback) {
+ return options.fallback;
+ }
+ throw e;
+ }
+};
+
+Config.prototype.writeProjectFile = function(p, content) {
+ if (typeof content !== 'string') {
+ content = JSON.stringify(content, null, 2) + '\n';
+ }
+
+ fs.ensureFileSync(this.path(p));
+ fs.writeFileSync(this.path(p), content, 'utf8');
+};
+
+Config.prototype.askWriteProjectFile = function(p, content) {
+ var writeTo = this.path(p);
+ var next;
+ if (fsutils.fileExistsSync(writeTo)) {
+ next = prompt.once({
+ type: 'confirm',
+ message: 'File ' + chalk.underline(p) + ' already exists. Overwrite?',
+ default: false
+ });
+ } else {
+ next = RSVP.resolve(true);
+ }
+
+ var self = this;
+ return next.then(function(result) {
+ if (result) {
+ self.writeProjectFile(p, content);
+ utils.logSuccess('Wrote ' + chalk.bold(p));
+ } else {
+ utils.logBullet('Skipping write of ' + chalk.bold(p));
+ }
+ });
+};
+
Config.load = function(options, allowMissing) {
var pd = detectProjectRoot(options.cwd);
if (pd) {
diff --git a/lib/init/features/database.js b/lib/init/features/database.js
new file mode 100644
index 00000000..39dc60f3
--- /dev/null
+++ b/lib/init/features/database.js
@@ -0,0 +1,30 @@
+'use strict';
+
+var chalk = require('chalk');
+
+var prompt = require('../../prompt');
+var logger = require('../../logger');
+
+module.exports = function(setup, config) {
+ setup.config.database = {};
+
+ logger.info();
+ logger.info('Rules allow you to define how your data should be structured and when');
+ logger.info('your data can be read from and written to. You can keep these rules in your');
+ logger.info('project directory and publish them with ' + chalk.bold('firebase deploy') + '.');
+ logger.info();
+
+ return prompt(setup.config.database, [
+ {
+ type: 'input',
+ name: 'rules',
+ message: 'What file should be used for Firebase Database rules?',
+ default: 'database.rules.json'
+ }
+ ]).then(function() {
+ return config.askWriteProjectFile(setup.config.database.rules, {
+ '.read': true,
+ '.write': true
+ });
+ });
+};
diff --git a/lib/init/features/functions.js b/lib/init/features/functions.js
new file mode 100644
index 00000000..5e019fd8
--- /dev/null
+++ b/lib/init/features/functions.js
@@ -0,0 +1,61 @@
+'use strict';
+
+var chalk = require('chalk');
+var fs = require('fs');
+var RSVP = require('rsvp');
+var spawn = require('child_process').spawn;
+
+var logger = require('../../logger');
+var prompt = require('../../prompt');
+
+var INDEX_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/functions/index.js', 'utf8');
+
+module.exports = function(setup, config) {
+ logger.info();
+ logger.info('A ' + chalk.bold('functions') + ' directory will be created in your project with a Node.js');
+ logger.info('package pre-configured. Functions can be deployed with ' + chalk.bold('firebase deploy') + '.');
+ logger.info();
+
+ setup.functions = {};
+ return config.askWriteProjectFile('functions/package.json', {
+ name: 'functions',
+ description: 'Firebase Functions',
+ dependencies: {
+ 'firebase': '>= 2.4.2',
+ 'firebase-functions': 'https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-0.1.0-alpha.h.2.tar.gz'
+ }
+ }).then(function() {
+ return config.askWriteProjectFile('functions/index.js', INDEX_TEMPLATE);
+ }).then(function() {
+ return prompt(setup.functions, [
+ {
+ name: 'npm',
+ type: 'confirm',
+ message: 'Do you want to install dependencies with npm now?',
+ default: true
+ }
+ ]);
+ }).then(function() {
+ if (setup.functions.npm) {
+ return new RSVP.Promise(function(resolve) {
+ var installer = spawn('npm', ['install'], {
+ cwd: config.projectDir + '/functions',
+ stdio: 'inherit'
+ });
+
+ installer.on('error', function(err) {
+ logger.debug(err.stack);
+ });
+
+ installer.on('close', function(code) {
+ if (code === 0) {
+ return resolve();
+ }
+ logger.info();
+ logger.error('NPM install failed, continuing with Firebase initialization...');
+ return resolve();
+ });
+ });
+ }
+ });
+};
diff --git a/lib/init/features/hosting.js b/lib/init/features/hosting.js
new file mode 100644
index 00000000..03c5713e
--- /dev/null
+++ b/lib/init/features/hosting.js
@@ -0,0 +1,58 @@
+'use strict';
+
+var chalk = require('chalk');
+var fs = require('fs');
+var RSVP = require('rsvp');
+
+var logger = require('../../logger');
+var prompt = require('../../prompt');
+
+var INDEX_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/hosting/index.html', 'utf8');
+var MISSING_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/hosting/404.html', 'utf8');
+module.exports = function(setup, config) {
+ setup.hosting = {
+ ignore: [
+ 'firebase.json',
+ '**/.*',
+ '**/node_modules/**'
+ ]
+ };
+
+ logger.info();
+ logger.info('Your ' + chalk.bold('public') + ' directory is the folder (relative to your project directory) that');
+ logger.info('will contain Hosting assets to uploaded with ' + chalk.bold('firebase deploy') + '. If you');
+ logger.info('have a build process for your assets, use your build\'s output directory.');
+ logger.info();
+
+ return prompt(setup.hosting, [
+ {
+ name: 'public',
+ type: 'input',
+ default: 'public',
+ message: 'What do you want to use as your public directory?'
+ },
+ {
+ name: 'spa',
+ type: 'confirm',
+ default: false,
+ message: 'Configure as a single-page app (rewrite all urls to /index.html)?'
+ }
+ ]).then(function() {
+ setup.config.hosting = {public: setup.hosting.public};
+
+ var next;
+ if (setup.hosting.spa) {
+ setup.config.hosting.rewrites = [
+ {source: '**', destination: '/index.html'}
+ ];
+ next = RSVP.resolve();
+ } else {
+ // SPA doesn't need a 404 page since everything is index.html
+ next = config.askWriteProjectFile(setup.hosting.public + '/404.html', MISSING_TEMPLATE);
+ }
+
+ return next.then(function() {
+ return config.askWriteProjectFile(setup.hosting.public + '/index.html', INDEX_TEMPLATE);
+ });
+ });
+};
diff --git a/lib/init/features/index.js b/lib/init/features/index.js
new file mode 100644
index 00000000..8c0194e1
--- /dev/null
+++ b/lib/init/features/index.js
@@ -0,0 +1,10 @@
+'use strict';
+
+module.exports = {
+ database: require('./database'),
+ functions: require('./functions'),
+ hosting: require('./hosting'),
+ storage: require('./storage'),
+ // always runs, sets up .firebaserc
+ project: require('./project')
+};
diff --git a/lib/init/features/project.js b/lib/init/features/project.js
new file mode 100644
index 00000000..5fdb7017
--- /dev/null
+++ b/lib/init/features/project.js
@@ -0,0 +1,56 @@
+'use strict';
+
+var chalk = require('chalk');
+
+var _ = require('lodash');
+var api = require('../../api');
+var prompt = require('../../prompt');
+var logger = require('../../logger');
+var utils = require('../../utils');
+
+var NO_PROJECT = '[don\'t setup a default project]';
+var NEW_PROJECT = '[create a new project]';
+
+module.exports = function(setup, config) {
+ setup.project = {};
+
+ logger.info();
+ logger.info('Now that your Firebase project directory is set up, it\'s time to associate');
+ logger.info('it with a project. You can create multiple project aliases by running');
+ logger.info(chalk.bold('firebase use --add') + ', but for now we\'ll just set up a default project.');
+ logger.info();
+
+ return api.getProjects().then(function(projects) {
+ var projectIds = Object.keys(projects).sort();
+ var nameOptions = [NO_PROJECT, NEW_PROJECT].concat(projectIds);
+
+ if (_.has(setup.rcfile, 'projects.default')) {
+ utils.logBullet('.firebaserc already has a default project, skipping');
+ return undefined;
+ }
+
+ return prompt.once({
+ type: 'list',
+ name: 'id',
+ message: 'What Firebase project do you want to associate as default?',
+ validate: function(answer) {
+ if (!_.includes(nameOptions, answer)) {
+ return 'Must specify a Firebase to which you have access.';
+ }
+ return true;
+ },
+ choices: nameOptions
+ }).then(function(projectId) {
+ if (projectId === NEW_PROJECT) {
+ logger.info('Please visit', chalk.underline('https://firebase.google.com'), 'to create a new project, then run', chalk.bold('firebase init'), 'again.');
+ return;
+ } else if (projectId === NO_PROJECT) {
+ return;
+ }
+
+ // write "default" alias and activate it immediately
+ _.set(setup.rcfile, 'projects.default', projectId);
+ utils.makeActiveProject(config.projectDir, projectId);
+ });
+ });
+};
diff --git a/lib/init/features/storage.js b/lib/init/features/storage.js
new file mode 100644
index 00000000..78bae9c6
--- /dev/null
+++ b/lib/init/features/storage.js
@@ -0,0 +1,30 @@
+'use strict';
+
+var chalk = require('chalk');
+var fs = require('fs');
+
+var prompt = require('../../prompt');
+var logger = require('../../logger');
+
+var RULES_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/storage/storage.rules', 'utf8');
+
+module.exports = function(setup, config) {
+ setup.config.storage = {};
+
+ logger.info();
+ logger.info('Rules allow you to define how and when to allow uploads and downloads via');
+ logger.info('Firebase Storage. You can keep these rules in your project directory and');
+ logger.info('publish them with ' + chalk.bold('firebase deploy') + '.');
+ logger.info();
+
+ return prompt(setup.config.storage, [
+ {
+ type: 'input',
+ name: 'rules',
+ message: 'What file should be used for Firebase Storage rules?',
+ default: 'storage.rules'
+ }
+ ]).then(function() {
+ return config.writeProjectFile(setup.config.storage.rules, RULES_TEMPLATE);
+ });
+};
diff --git a/lib/init/index.js b/lib/init/index.js
new file mode 100644
index 00000000..95df6760
--- /dev/null
+++ b/lib/init/index.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var _ = require('lodash');
+var chalk = require('chalk');
+var RSVP = require('rsvp');
+
+var logger = require('../logger');
+var features = require('./features');
+
+var init = function(setup, config, options) {
+ var nextFeature = setup.features.shift();
+ if (nextFeature) {
+ logger.info(chalk.bold('\n' + chalk.gray('=== ') + _.capitalize(nextFeature) + ' Setup'));
+ return RSVP.resolve(features[nextFeature](setup, config, options)).then(function() {
+ return init(setup, config, options);
+ });
+ }
+ return RSVP.resolve();
+};
+
+module.exports = init;
diff --git a/lib/prompt.js b/lib/prompt.js
index 414a01fe..788d33f9 100644
--- a/lib/prompt.js
+++ b/lib/prompt.js
@@ -5,7 +5,7 @@ var _ = require('lodash');
var FirebaseError = require('./error');
var RSVP = require('rsvp');
-module.exports = function(options, questions) {
+var prompt = function(options, questions) {
return new RSVP.Promise(function(resolve, reject) {
var prompts = [];
for (var i = 0; i < questions.length; i++) {
@@ -29,3 +29,27 @@ module.exports = function(options, questions) {
});
});
};
+
+/**
+ * Allow a one-off prompt when we don't need to ask a bunch of questions.
+ */
+prompt.once = function(question) {
+ question.name = question.name || 'question';
+ return prompt({}, [question]).then(function(answers) { return answers[question.name]; });
+};
+
+prompt.convertLabeledListChoices = function(choices) {
+ return choices.map(function(choice) {
+ return {checked: choice.checked, name: choice.label};
+ });
+};
+
+prompt.listLabelToValue = function(label, choices) {
+ for (var i = 0; i < choices.length; i++) {
+ if (choices[i].label === label) {
+ return choices[i].name;
+ }
+ }
+};
+
+module.exports = prompt;
diff --git a/lib/utils.js b/lib/utils.js
index c248e5e2..52c9c9c0 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -3,6 +3,7 @@
var _ = require('lodash');
var logger = require('./logger');
var chalk = require('chalk');
+var configstore = require('./configstore');
var FirebaseError = require('./error');
var RSVP = require('rsvp');
var Readable = require('stream').Readable;
@@ -119,5 +120,21 @@ module.exports = {
s.push(text);
s.push(null);
return s;
+ },
+
+ /**
+ * Sets the active project alias or id in the specified directory.
+ * @param {String} projectDir the project directory
+ * @param {String} newActive the new active project alias or id
+ */
+ makeActiveProject: function(projectDir, newActive) {
+ var activeProjects = configstore.get('activeProjects') || {};
+ if (newActive) {
+ activeProjects[projectDir] = newActive;
+ } else {
+ _.unset(activeProjects, projectDir);
+ }
+
+ configstore.set('activeProjects', activeProjects);
}
};
diff --git a/templates/banner.txt b/templates/banner.txt
new file mode 100644
index 00000000..80648bef
--- /dev/null
+++ b/templates/banner.txt
@@ -0,0 +1,6 @@
+
+ ######## #### ######## ######## ######## ### ###### ########
+ ## ## ## ## ## ## ## ## ## ## ##
+ ###### ## ######## ###### ######## ######### ###### ######
+ ## ## ## ## ## ## ## ## ## ## ##
+ ## #### ## ## ######## ######## ## ## ###### ########
diff --git a/templates/init/functions/index.js b/templates/init/functions/index.js
new file mode 100644
index 00000000..5ce6c3d4
--- /dev/null
+++ b/templates/init/functions/index.js
@@ -0,0 +1,12 @@
+var functions = require('firebase-functions');
+
+// // converts the "text" key of messages pushed to /messages to uppercase
+// exports.upperCaser = functions.database().path('/messages/{id}').on('value', function(event) {
+// // prevent infinite loops
+// if (event.data.child('uppercased').val()) { return; }
+//
+// return event.ref().update({
+// text: event.data.child('text').val().toUpperCase(),
+// uppercased: true
+// });
+// });
diff --git a/templates/init/hosting/404.html b/templates/init/hosting/404.html
new file mode 100644
index 00000000..19126b5d
--- /dev/null
+++ b/templates/init/hosting/404.html
@@ -0,0 +1,81 @@
+
+
+
+
+ Page Not Found
+
+
+
+ Page Not Found
+ This specified file was not found on this website. Please check the URL for mistakes and try again.
+ Why am I seeing this?
+ This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.
+
+
+
+
+
diff --git a/templates/init/hosting/index.html b/templates/init/hosting/index.html
new file mode 100644
index 00000000..a6a8e2b8
--- /dev/null
+++ b/templates/init/hosting/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+ Welcome to Firebase Hosting
+
+
+
+
+
Welcome to Firebase Hosting
+
You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!
+
+
+
+
diff --git a/templates/init/storage/storage.rules b/templates/init/storage/storage.rules
new file mode 100644
index 00000000..6cf80aea
--- /dev/null
+++ b/templates/init/storage/storage.rules
@@ -0,0 +1,7 @@
+service firebase.storage {
+ match /b/{bucket}/o {
+ match /{allPaths=**} {
+ allow read, write: if auth!=null;
+ }
+ }
+}