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; + } + } +}