mirror of
https://github.com/zhigang1992/firebase-tools.git
synced 2026-01-12 22:47:24 +08:00
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:
153
commands/init.js
153
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 <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!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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.');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
30
lib/init/features/database.js
Normal file
30
lib/init/features/database.js
Normal 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
|
||||
});
|
||||
});
|
||||
};
|
||||
61
lib/init/features/functions.js
Normal file
61
lib/init/features/functions.js
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
58
lib/init/features/hosting.js
Normal file
58
lib/init/features/hosting.js
Normal 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
10
lib/init/features/index.js
Normal file
10
lib/init/features/index.js
Normal 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')
|
||||
};
|
||||
56
lib/init/features/project.js
Normal file
56
lib/init/features/project.js
Normal 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
30
lib/init/features/storage.js
Normal file
30
lib/init/features/storage.js
Normal 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
21
lib/init/index.js
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
17
lib/utils.js
17
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);
|
||||
}
|
||||
};
|
||||
|
||||
6
templates/banner.txt
Normal file
6
templates/banner.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
######## #### ######## ######## ######## ### ###### ########
|
||||
## ## ## ## ## ## ## ## ## ## ##
|
||||
###### ## ######## ###### ######## ######### ###### ######
|
||||
## ## ## ## ## ## ## ## ## ## ##
|
||||
## #### ## ## ######## ######## ## ## ###### ########
|
||||
12
templates/init/functions/index.js
Normal file
12
templates/init/functions/index.js
Normal 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
|
||||
// });
|
||||
// });
|
||||
81
templates/init/hosting/404.html
Normal file
81
templates/init/hosting/404.html
Normal 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>
|
||||
60
templates/init/hosting/index.html
Normal file
60
templates/init/hosting/index.html
Normal 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>
|
||||
7
templates/init/storage/storage.rules
Normal file
7
templates/init/storage/storage.rules
Normal file
@@ -0,0 +1,7 @@
|
||||
service firebase.storage {
|
||||
match /b/{bucket}/o {
|
||||
match /{allPaths=**} {
|
||||
allow read, write: if auth!=null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user