Re-imagined firebase init flow for multiple products

* Init now asks which CLI features you want to setup
* Init now creates index.html for Hosting
* Init can now setup the Firebase Functions directory
* Init can now setup Firebase Storage rules deployment
* Init can now be run again in an existing directory
This commit is contained in:
Michael Bleigh
2016-04-08 18:28:00 -07:00
parent 470103cb71
commit 9d3b7deaea
18 changed files with 612 additions and 107 deletions

View File

@@ -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 <dir>', '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!');
});
});

View File

@@ -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;
});

View File

@@ -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.');

View File

@@ -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) {

View File

@@ -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
});
});
};

View File

@@ -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();
});
});
}
});
};

View File

@@ -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);
});
});
};

View File

@@ -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')
};

View File

@@ -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);
});
});
};

View File

@@ -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);
});
};

21
lib/init/index.js Normal file
View File

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

View File

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

View File

@@ -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);
}
};

6
templates/banner.txt Normal file
View File

@@ -0,0 +1,6 @@
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########

View File

@@ -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
// });
// });

View File

@@ -0,0 +1,81 @@
<!doctype html>
<html>
<head>
<title>Page Not Found</title>
<style>
body {
margin: 0 auto;
padding: 1em;
font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 1.1em;
color: #333;
}
@media screen and (min-width: 40em) {
body {
width: 40em;
}
}
h1, h2, h3 {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
}
h1 {
margin: 1em 0;
padding: 0 0 0.25em 0;
border-bottom: 1px dotted #ccc;
text-align: center;
font-size: 3em;
line-height: 1.1em;
}
h2 {
margin: 1em 0 0.45em 0;
padding: 0;
font-size: 1.8em;
}
p {
margin: 0 0 0.5em 0;
line-height: 1.8em;
}
ol {
}
ol li {
margin: 0 0 0.5em 0;
font-size: 0.9em;
line-height: 1.8em;
}
a {
color: #50B1FF;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
code {
display: inline-block;
padding: 0.1em;
background-color: #F8F8F8;
border: 1px solid #DDD;
border-radius: 3px;
font-family: Consolas,"Liberation Mono",Courier,monospace;
font-size: 0.9em;
line-height: 1;
}
.logo {
display: block;
margin: 3em 0;
text-align: center;
}
</style>
</head>
<body>
<h1>Page Not Found</h1>
<p>This specified file was not found on this website. Please check the URL for mistakes and try again.</p>
<h2>Why am I seeing this?</h2>
<p>This page was generated by the Firebase Command-Line Interface. To modify it, edit the <code>404.html</code> file in your project's configured public directory.</p>
<a class="logo" href="//www.firebase.com">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJQAAAAaCAYAAABRhnV8AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3NpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyNWM4MWUxOS0zYWJmLTQyYWYtOTVhMi01MDBiZWU5NzAyMTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkNCQkJBMDlCNkRGMTFFM0JDMTdBQTgyNEFEQURCQTQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkNCQkJBMDhCNkRGMTFFM0JDMTdBQTgyNEFEQURCQTQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZWFkZWM0M2YtMmFjMy00NTEyLTljNzQtYjVmMDJjMWNhNTkxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjI1YzgxZTE5LTNhYmYtNDJhZi05NWEyLTUwMGJlZTk3MDIxOCIvPiA8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" />
</a>
</body>
</html>

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Welcome to Firebase Hosting</title>
<style media="screen">
body {
font-family: Roboto, Arial, sans-serif;
background: #ECEFF1;
color: rgba(0,0,0,0.87);
}
a {
color: rgb(3,155,229);
}
#message {
max-width: 400px;
margin: 40px auto;
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 2px 1px -1px rgba(0,0,0,0.12);
border-radius: 2px;
background: white;
padding: 16px 24px;
}
#message h1 {
font-size: 22px;
font-weight: 500;
text-align: center;
margin: 0 0 16px;
}
#message p {
font-weight: 300;
line-height: 150%;
}
#message ul {
list-style: none;
margin: 16px 0 0;
padding: 0;
}
#message li a {
display: block;
padding: 8px 16px;
text-align: center;
}
</style>
</head>
<body>
<div id="message">
<h1>Welcome to Firebase Hosting</h1>
<p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/hosting">Hosting Documentation</a></li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,7 @@
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if auth!=null;
}
}
}