From 02aa6206cb07e8f7dc60ee2076519052eebd0ee2 Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Fri, 23 May 2014 15:00:24 -0700 Subject: [PATCH 1/8] adding 'delete-site' command to delete firebaseapp.com subdomain --- bin/firebase | 3 ++ lib/app.js | 131 ++++++++++++++++++++++++++++++++++++++------------ lib/upload.js | 34 +++++++++++++ 3 files changed, 136 insertions(+), 32 deletions(-) diff --git a/bin/firebase b/bin/firebase index d94f4f2e6de..719b23d3df2 100755 --- a/bin/firebase +++ b/bin/firebase @@ -69,6 +69,9 @@ if (argv._.length === 0) { case 'deploy': app.deploy(argv); break; + case 'delete-site': + app.deleteSite(argv); + break; case 'open': app.open(argv); break; diff --git a/lib/app.js b/lib/app.js index a84d5edc6b6..11f09a12497 100644 --- a/lib/app.js +++ b/lib/app.js @@ -50,6 +50,27 @@ function getPrompt(schema, onComplete, idx, results) { }); } +function getSettings(argv) { + var settingsFile = path.resolve('./firebase.json'); + var settingsJSON, settings; + if (!fs.existsSync(settingsFile)) { + console.log(chalk.red('Initialization Error') + ' - Directory not ' + + 'initialized'); + process.exit(1); + } + try { + settingsJSON = fs.readFileSync(settingsFile); + settings = JSON.parse(settingsJSON); + } catch (err) { + console.log(chalk.red('Initialization Error') +' - Could not read ' + + 'firebase.json settings file'); + process.exit(1); + } + util._extend(settings, argv); + + return settings; +} + module.exports = { init: function(argv) { auth.listFirebases(argv).then(function(res) { @@ -334,22 +355,7 @@ module.exports = { console.log(chalk.red('Login Error')); process.exit(1); } - var settingsFile = path.resolve('./firebase.json'); - var settingsJSON, settings; - if (!fs.existsSync(settingsFile)) { - console.log(chalk.red('Initialization Error') + ' - Directory not ' + - 'initialized'); - process.exit(1); - } - try { - settingsJSON = fs.readFileSync(settingsFile); - settings = JSON.parse(settingsJSON); - } catch (err) { - console.log(chalk.red('Initialization Error') +' - Could not read ' + - 'firebase.json settings file'); - process.exit(1); - } - util._extend(settings, argv); + var settings = getSettings(argv); if (typeof(settings.firebase) !== 'string') { console.log(chalk.red('Initialization Error') +' - Could not read ' + 'firebase.json settings file'); @@ -440,7 +446,7 @@ module.exports = { message = argv.message; } - upload.send(settings.firebase, settings['public'], settings.ignore || defaultSettings.ignore, directoryRef.name(), message, function(err, directory) { + upload.send(settings.firebase, settings['public'], settings.ignore, directoryRef.name(), message, function(err, directory) { if (err) { console.log(chalk.red('Deploy Error') + ' - Couldn\'t upload app'); console.log(err); @@ -451,22 +457,83 @@ module.exports = { }); }); }, + deleteSite: function(argv) { + auth.requireLogin(argv, function(err) { + if (err) { + console.log(chalk.red('Login Error')); + process.exit(1); + } + var settings = getSettings(argv); + if (typeof(settings.firebase) !== 'string') { + console.log(chalk.red('Initialization Error') +' - Could not read ' + + 'firebase.json settings file'); + process.exit(1); + } + auth.checkCanAccess(settings.firebase, function(err, tokens) { + if (err) { + console.log(chalk.red('Permission Error') + ' - You do not have ' + + 'permission to use this Firebase'); + process.exit(1); + } + var firebaseRef = new Firebase(api.realtimeUrl.replace(/\/\//, '//firebase.')); + firebaseRef.auth(tokens.firebaseToken, function(error, result) { + if (error) { + console.log('Firebase authentication failed!'); + process.exit(1); + } + }); + var directoryRef = firebaseRef + .child('hosting/versions') + .child(settings.firebase) + .push(); + var bar = null; + var total = 0; + directoryRef.on('value', function(snapshot) { + var status = snapshot.child('status').val(); + if (status === 'deploying') { + if (!bar && snapshot.hasChild('fileCount')) { + total = snapshot.child('fileCount').val(); + bar = new ProgressBar(chalk.yellow('progress: :percent'), { + total: total + }); + } + if (bar) { + var uploadedCount = snapshot.hasChild('uploadedCount') ? snapshot.child('uploadedCount').val() : 0; + bar.update(uploadedCount / total); + } + } else if (status === 'removed') { + console.log(chalk.green('Sucessfully removed')); + process.exit(0); + } else if (status === 'failed') { + if (bar) { + bar.terminate(); + } + var message = chalk.red('Deploy Failed'); + if (snapshot.hasChild('statusMessage')) { + message += ' - ' + snapshot.child('statusMessage').val(); + } + console.log(message); + process.exit(1); + } + }); + + var message = null; + if (argv.message && (typeof(argv.message) === 'string')) { + message = argv.message; + } + + upload.deleteSite(settings.firebase, directoryRef.name(), message, function(err, directory) { + if (err) { + console.log(chalk.red('Deploy Error') + ' - Couldn\'t upload app'); + console.log(err); + process.exit(1); + } + }); + }); + }); + }, open: function(argv) { - var settingsFile = path.resolve('./firebase.json'); - var settingsJSON, settings; - if (!fs.existsSync(settingsFile)) { - console.log(chalk.red('Initialization Error') + ' - Directory not ' + - 'initialized'); - process.exit(1); - } - try { - settingsJSON = fs.readFileSync(settingsFile); - settings = JSON.parse(settingsJSON); - } catch (err) { - console.log(chalk.red('Initialization Error') +' - Could not read ' + - 'firebase.json settings file'); - process.exit(1); - } + var settings = getSettings(argv); if (typeof(settings.firebase) !== 'string') { console.log(chalk.red('Initialization Error') +' - Could not read ' + 'firebase.json settings file'); diff --git a/lib/upload.js b/lib/upload.js index b5b585cccd5..f359c4eec95 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -59,6 +59,40 @@ module.exports = { var url = api.uploadUrl + '/upload/' + firebase + '?' + params.join('&') var readStream = fs.createReadStream(filename); + var r = request.put({ + url: url, + json: true + }, function(err, response, body) { + fs.unlink(filename); + var failed = (err || !body || !body.success); + setTimeout(callback, 0, failed, body ? body.directory : undefined); + }); + var form = r.form(); + form.append('site', readStream); + }); + }, + deleteSite: function(firebase, pushId, message, callback) { + var writeStream = temp.createWriteStream({ suffix: '.tar.gz' }), + filename = writeStream.path, + publicDir = temp.mkdirSync(); + + console.log('Preparing to delete site...'); + + fstreamIgnore({ + path: publicDir, + type: 'Directory' + }).pipe(tar.Pack()) + .pipe(zlib.createGzip()) + .pipe(writeStream); + + writeStream.once('finish', function() { + var params = ['id=' + encodeURIComponent(pushId), 'fileCount=0', 'token=' + auth.token]; + if (message) { + params.push('message=' + encodeURIComponent(message)); + } + var url = api.uploadUrl + '/upload/' + firebase + '?' + params.join('&') + var readStream = fs.createReadStream(filename); + var r = request.put({ url: url, json: true From 0469db7df346fb83727b0a3941b410814f8b9c24 Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Fri, 23 May 2014 15:31:30 -0700 Subject: [PATCH 2/8] documenting new delete-site command --- lib/help.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/help.js b/lib/help.js index f7f7b17f580..4d99c6e5a9d 100644 --- a/lib/help.js +++ b/lib/help.js @@ -133,6 +133,23 @@ module.exports = { ' currently logged in, they are prompted to do so - see\n' + ' `firebase login --help` for more details.\n'); break; + case 'delete-site': + console.log('\n' + + ' firebase delete-site\n' + + ' Deletes the current app from Firebase Hosting and displays a \n' + + ' \'Site not Found\' page as if the site had never been deployed to.\n' + + '\n' + + ' Optional command line parameters:\n' + + '\n' + + ' -m, --message An optional version message\n' + + '\n' + + ' -f, --firebase Overrides the Firebase setting in the firebase.json\n' + + '\n' + + ' Deletes the site associated with the Firebase detailed in the firebase.json\n' + + ' settings file. The current user must have access to the Firebase, and if\n' + + ' the user is not currently logged in, they are prompted to do so - see\n' + + ' `firebase login --help` for more details.\n'); + break; case 'open': console.log('\n' + ' firebase open\n' + @@ -167,6 +184,10 @@ module.exports = { ' list\n' + ' Lists the Firebases available to the currently logged in user.\n' + '\n' + + ' delete-site\n' + + ' Deletes the current app from Firebase Hosting and displays a \n' + + ' \'Site not Found\' page as if the site had never been deployed to.\n' + + '\n' + ' login\n' + ' Logs you into Firebase. All commands that require login will prompt\n' + ' you if you\'re not currently logged in.\n' + From 4db6c14c44b97cfa631b2442d4386c7ccdf8373c Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Fri, 23 May 2014 15:45:47 -0700 Subject: [PATCH 3/8] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38a8479ac22..d56f532bd7b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "firebase-tools", "preferGlobal": true, - "version": "1.0.4", + "version": "1.0.5", "description": "The Firebase Command Line Tools", "keywords": [ "firebase" From c718a57d8744ee7cf70206d6e38a2aff23ccc3e5 Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Wed, 28 May 2014 09:49:33 -0700 Subject: [PATCH 4/8] silent mode for scripting --- bin/firebase | 2 ++ lib/app.js | 23 +++++++++++++++-------- lib/auth.js | 48 +++++++++++++++++++++++++++++++----------------- lib/firebase.js | 2 +- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/bin/firebase b/bin/firebase index 719b23d3df2..4ceeb6b26a9 100755 --- a/bin/firebase +++ b/bin/firebase @@ -6,9 +6,11 @@ var argv = require('optimist') .alias('m', 'message') .alias('p', 'public') .alias('r', 'rules') + .alias('s', 'silent') .alias('t', 'template') .alias('v', 'version') .boolean('d') + .boolean('s') .argv, prompt = require('prompt'), firebase = require('../lib/firebase'), diff --git a/lib/app.js b/lib/app.js index 11f09a12497..a032588d13a 100644 --- a/lib/app.js +++ b/lib/app.js @@ -24,26 +24,33 @@ var defaultSettings = { temp.track(); -function getPrompt(schema, onComplete, idx, results) { +function getPrompt(argv, schema, onComplete, index, results) { if (!Array.isArray(schema)) { console.log(chalk.red('An error occurred')); process.exit(1); } onComplete = typeof onComplete !== 'function' ? function() {} : onComplete; - idx = typeof idx !== 'number' ? 0 : idx; + index = typeof index !== 'number' ? 0 : index; results = typeof results !== 'object' ? {} : results; - var item = schema[idx]; + var item = schema[index]; + if (argv.silent) { + if (!prompt.override[item.name] || (item.pattern && !item.pattern.test(prompt.override[item.name]))) { + console.log(chalk.red('Input Error') + ' - Not enough or invalid parameters specified while in silent mode'); + console.log('Required ' + chalk.bold(item.name) + ' parameter missing or invalid'); + process.exit(1); + } + } if (typeof item.beforeValue === 'function') { item.beforeValue(results); } - prompt.get(schema[idx], function (error, result) { + prompt.get(schema[index], function (error, result) { if (error) { console.log(chalk.red('Input Error')); process.exit(1); } results[item.name] = result[item.name]; - if (++idx < schema.length) { - getPrompt(schema, onComplete, idx, results); + if (++index < schema.length) { + getPrompt(argv, schema, onComplete, index, results); } else { onComplete(results); } @@ -131,7 +138,7 @@ module.exports = { } }]; - getPrompt(schema, function(results) { + getPrompt(argv, schema, function(results) { if (path.relative('.', results['public']).match(/^\./)) { console.log(chalk.red('init cancelled - the public directory must be within the current working directory')); process.exit(1); @@ -221,7 +228,7 @@ module.exports = { type: 'string' }]; - getPrompt(schema, function(results) { + getPrompt(argv, schema, function(results) { var firebase = results.firebase; var dir = firebase; var projectDir = path.resolve(dir); diff --git a/lib/auth.js b/lib/auth.js index d919ab4655d..20c9bc646c3 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -16,15 +16,15 @@ var auth = { var that = this; if (argv.email && argv.password) { - this._attemptLogin(this.maxRetries, callback); + this._attemptLogin(argv, this.maxRetries, callback); } else if ((this.email.length === 0) || (this.token.length === 0)) { console.log('Please sign into your Firebase account to continue...'); - this._attemptLogin(this.maxRetries, callback); + this._attemptLogin(argv, this.maxRetries, callback); } else { this._validate(function(err) { if (err) { console.log('Please sign into your Firebase account to continue...'); - that._attemptLogin(that.maxRetries, callback); + that._attemptLogin(argv, that.maxRetries, callback); } else { setTimeout(callback, 0, null, that.email, that.token); } @@ -55,20 +55,24 @@ var auth = { } ); }, - login: function(callback) { - this._attemptLogin(this.maxRetries, callback); + login: function(argv, callback) { + this._attemptLogin(argv, this.maxRetries, callback); }, - _attemptLogin: function(tries, callback) { + _attemptLogin: function(argv, tries, callback) { var that = this; if (tries > 0) { if (tries !== this.maxRetries) { + if (argv.silent) { + console.log(chalk.red('Input Error') + ' - Email or password incorrect'); + process.exit(1); + } console.log('Email or password incorrect, please try again'); delete prompt.override.email; delete prompt.override.password; } - this._loginRequest(function(err, email, token) { + this._loginRequest(argv, function(err, email, token) { if (err) { - that._attemptLogin(tries - 1, callback); + that._attemptLogin(argv, tries - 1, callback); } else { setTimeout(callback, 0, null, email, token); } @@ -78,28 +82,38 @@ var auth = { process.exit(1); } }, - _loginRequest: function(callback) { + _loginRequest: function(argv, callback) { var that = this, - schema = { - properties: { - email: { + schema = [ + { + name: 'email', description: 'Email:', pattern: /@/, message: 'Must be a valid email address', required: true, type: 'string' - }, - password: { + },{ + name: 'password', description: 'Password:', hidden: true, required: true, type: 'string' } - } - }; + ]; if (this.email.length > 0) { - schema.properties.email.default = this.email; + schema[0].default = this.email; + } + + if (argv.silent) { + for (var i in schema) { + var item = schema[i]; + if (!prompt.override[item.name] || (item.pattern && !item.pattern.test(prompt.override[item.name]))) { + console.log(chalk.red('Input Error') + ' - Not enough or invalid parameters specified while in silent mode'); + console.log('Required ' + chalk.bold(item.name) + ' parameter missing or invalid'); + process.exit(1); + } + } } prompt.get(schema, function(err, result) { diff --git a/lib/firebase.js b/lib/firebase.js index 01deafd35ca..e6ef8dbb773 100644 --- a/lib/firebase.js +++ b/lib/firebase.js @@ -4,7 +4,7 @@ var auth = require('./auth'), module.exports = { login: function(argv) { - auth.login(function(err) { + auth.login(argv, function(err) { if (err) { console.log(chalk.red('Login Unsuccessful')); process.exit(1); From 1da79869ae25b757d2b1f302e1220027a6192b7f Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Wed, 4 Jun 2014 17:50:32 -0700 Subject: [PATCH 5/8] Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d56f532bd7b..72b62c783bd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "firebase-tools", "preferGlobal": true, - "version": "1.0.5", + "version": "1.0.6", "description": "The Firebase Command Line Tools", "keywords": [ "firebase" From de6e741762d9b906091d95a0b067ebe02d4e16c2 Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Wed, 4 Jun 2014 18:21:49 -0700 Subject: [PATCH 6/8] Adding changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..47e098b8754 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# v1.0.6 +- Adds `-s` functionality to all commands for silent mode while scripting - commands will error with non-zero status code instead of waiting for prompt if not enough information supplied +- `delete-site` command as a convenience method for removing a site from hosting. Site shows up as 'Site Not Found' as if never deployed to + +# v1.0.5 +- Gracefully handles error caused by symlinks in public directory until isaacs/fstream#16 fix + +# v1.0.4 +- NPM artifact fix + +# v1.0.3 +- Allows command line params in `firebase deploy` to override `firebase.json` settings + +# v1.0.2 +- Enforces node 0.10.x and above after shown not to work on previous versions + +# v1.0.1 +- Fixes bug with `firebase bootstrap` on windows +- Adds 'ignore' to `firebase.json` to allow files to be ignored on deploy +- Prioritizes `--email` and `--password` command line arguments over current auth token if both passed \ No newline at end of file From 0d2e049683945f1e1b23b928a8e4b6eef208879d Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Wed, 4 Jun 2014 18:22:53 -0700 Subject: [PATCH 7/8] improving changelog formatting --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47e098b8754..69dc38eedc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,20 @@ -# v1.0.6 +## v1.0.6 - Adds `-s` functionality to all commands for silent mode while scripting - commands will error with non-zero status code instead of waiting for prompt if not enough information supplied - `delete-site` command as a convenience method for removing a site from hosting. Site shows up as 'Site Not Found' as if never deployed to -# v1.0.5 +## v1.0.5 - Gracefully handles error caused by symlinks in public directory until isaacs/fstream#16 fix -# v1.0.4 +## v1.0.4 - NPM artifact fix -# v1.0.3 +## v1.0.3 - Allows command line params in `firebase deploy` to override `firebase.json` settings -# v1.0.2 +## v1.0.2 - Enforces node 0.10.x and above after shown not to work on previous versions -# v1.0.1 +## v1.0.1 - Fixes bug with `firebase bootstrap` on windows - Adds 'ignore' to `firebase.json` to allow files to be ignored on deploy - Prioritizes `--email` and `--password` command line arguments over current auth token if both passed \ No newline at end of file From 816f047a81b186e8d473bc983b7ec540fb11bb0a Mon Sep 17 00:00:00 2001 From: Chris Raynor Date: Wed, 4 Jun 2014 18:25:08 -0700 Subject: [PATCH 8/8] fix link to fstream issue --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69dc38eedc7..ef366bb0858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ - `delete-site` command as a convenience method for removing a site from hosting. Site shows up as 'Site Not Found' as if never deployed to ## v1.0.5 -- Gracefully handles error caused by symlinks in public directory until isaacs/fstream#16 fix +- Gracefully handles error caused by symlinks in public directory until [isaacs/fstream#16](https://github.com/isaacs/fstream/pull/16) fix ## v1.0.4 - NPM artifact fix