From 02f5e2eec9c558253e193e577fa45eeba1d9329a Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 10 Apr 2018 18:29:02 -0700 Subject: [PATCH] Switch from outdated ESLint to Prettier (#731) --- .eslintignore | 2 + .eslintrc | 148 +----- .gitignore | 1 + .prettierignore | 2 + .prettierrc | 4 + .travis.yml | 1 + bin/firebase | 107 ++-- commands/auth-export.js | 68 +-- commands/auth-import.js | 145 +++--- commands/database-get.js | 112 +++-- commands/database-profile.js | 52 +- commands/database-push.js | 87 ++-- commands/database-remove.js | 65 +-- commands/database-set.js | 100 ++-- commands/database-update.js | 100 ++-- commands/deploy.js | 72 +-- commands/experimental-functions-shell.js | 20 +- commands/firestore-delete.js | 121 +++-- commands/firestore-indexes-list.js | 48 +- commands/functions-config-clone.js | 56 ++- commands/functions-config-get.js | 28 +- commands/functions-config-legacy.js | 72 +-- commands/functions-config-set.js | 41 +- commands/functions-config-unset.js | 53 +- commands/functions-log.js | 89 ++-- commands/functions-shell.js | 18 +- commands/help.js | 25 +- commands/hosting-disable.js | 74 +-- commands/index.js | 82 +-- commands/init.js | 203 +++++--- commands/kits-install.js | 80 +-- commands/kits-uninstall.js | 180 ++++--- commands/list.js | 64 ++- commands/login-ci.js | 32 +- commands/login.js | 83 +-- commands/logout.js | 40 +- commands/open.js | 130 +++-- commands/serve.js | 53 +- commands/setup-web.js | 24 +- commands/target-apply.js | 33 +- commands/target-clear.js | 20 +- commands/target-remove.js | 24 +- commands/target.js | 30 +- commands/tools-migrate.js | 70 +-- commands/use.js | 153 +++--- gulpfile.js | 58 +-- index.js | 68 +-- lib/RulesDeploy.js | 88 ++-- lib/accountExporter.js | 119 +++-- lib/accountImporter.js | 286 ++++++----- lib/acquireRefs.js | 26 +- lib/api.js | 217 ++++---- lib/auth.js | 312 +++++++----- lib/checkDupHostingKeys.js | 54 +- lib/checkFirebaseSDKVersion.js | 55 +- lib/checkValidTargetFilters.js | 34 +- lib/command.js | 129 +++-- lib/config.js | 144 +++--- lib/configstore.js | 6 +- lib/deploy/database/index.js | 6 +- lib/deploy/database/prepare.js | 77 +-- lib/deploy/database/release.js | 33 +- lib/deploy/firestore/deploy.js | 50 +- lib/deploy/firestore/index.js | 8 +- lib/deploy/firestore/prepare.js | 31 +- lib/deploy/firestore/release.js | 16 +- lib/deploy/functions/deploy.js | 58 ++- lib/deploy/functions/index.js | 8 +- lib/deploy/functions/prepare.js | 89 ++-- lib/deploy/functions/release.js | 474 +++++++++++------- lib/deploy/hosting/deploy.js | 179 ++++--- lib/deploy/hosting/index.js | 6 +- lib/deploy/hosting/prepare.js | 29 +- lib/deploy/index.js | 119 +++-- lib/deploy/lifecycleHooks.js | 94 ++-- lib/deploy/storage/deploy.js | 8 +- lib/deploy/storage/index.js | 8 +- lib/deploy/storage/prepare.js | 24 +- lib/deploy/storage/release.js | 26 +- lib/detectProjectRoot.js | 8 +- lib/ensureApiEnabled.js | 72 +-- lib/ensureDefaultCredentials.js | 29 +- lib/error.js | 6 +- lib/errorOut.js | 12 +- lib/extractTriggers.js | 18 +- lib/fetchMOTD.js | 63 +-- lib/fetchWebSetup.js | 22 +- lib/filterTargets.js | 26 +- lib/firestore/delete.js | 243 ++++----- lib/firestore/encodeFirestoreValue.js | 40 +- lib/firestore/indexes.js | 108 ++-- lib/fsAsync.js | 107 ++-- lib/fsutils.js | 6 +- lib/functionsConfig.js | 125 +++-- lib/functionsConfigClone.js | 58 ++- lib/functionsEmulator.js | 253 ++++++---- lib/functionsShellCommandAction.js | 89 ++-- lib/gcp/cloudfunctions.js | 271 ++++++---- lib/gcp/cloudlogging.js | 36 +- lib/gcp/index.js | 10 +- lib/gcp/rules.js | 126 ++--- lib/gcp/runtimeconfig.js | 119 ++--- lib/gcp/storage.js | 57 ++- lib/getInstanceId.js | 14 +- lib/getProjectId.js | 33 +- lib/getProjectNumber.js | 24 +- lib/handlePreviewToggles.js | 34 +- lib/hosting/functionsProxy.js | 88 ++-- lib/hosting/implicitInit.js | 12 +- lib/hosting/initMiddleware.js | 16 +- lib/identifierToProjectId.js | 8 +- lib/init/features/database.js | 131 +++-- lib/init/features/firestore.js | 62 +-- lib/init/features/functions/index.js | 61 +-- lib/init/features/functions/javascript.js | 69 +-- .../features/functions/npm-dependencies.js | 32 +- lib/init/features/functions/typescript.js | 81 +-- lib/init/features/hosting.js | 88 ++-- lib/init/features/index.js | 14 +- lib/init/features/project.js | 84 ++-- lib/init/features/storage.js | 31 +- lib/init/index.js | 14 +- lib/kits/deploy.js | 92 ++-- lib/kits/index.js | 8 +- lib/kits/pollKits.js | 48 +- lib/kits/prepareKitsConfig.js | 132 ++--- lib/kits/prepareKitsUpload.js | 232 +++++---- lib/listFiles.js | 10 +- lib/loadCJSON.js | 12 +- lib/localFunction.js | 151 +++--- lib/logError.js | 24 +- lib/logger.js | 6 +- lib/parseBoltRules.js | 27 +- lib/parseTriggers.js | 34 +- lib/pollOperations.js | 84 ++-- lib/prepareFirebaseRules.js | 88 ++-- lib/prepareFunctionsUpload.js | 153 +++--- lib/prepareUpload.js | 81 +-- lib/previews.js | 19 +- lib/profileReport.js | 264 +++++----- lib/profiler.js | 92 ++-- lib/prompt.js | 33 +- lib/rc.js | 91 ++-- lib/requireAccess.js | 95 ++-- lib/requireAuth.js | 57 ++- lib/requireConfig.js | 10 +- lib/resolveProjectPath.js | 6 +- lib/responseToError.js | 16 +- lib/rtdb.js | 42 +- lib/scopes.js | 16 +- lib/serve/functions.js | 6 +- lib/serve/hosting.js | 63 ++- lib/serve/index.js | 38 +- lib/track.js | 30 +- lib/triggerParser.js | 45 +- lib/utils.js | 74 +-- lib/validateJsonRules.js | 8 +- lib/validator.js | 25 +- package.json | 17 +- scripts/assets/functions_to_test.js | 34 +- scripts/test-functions-config.js | 114 +++-- scripts/test-functions-deploy.js | 337 +++++++------ test/.eslintrc | 8 - test/helpers/index.js | 11 +- test/lib/accountExporter.spec.js | 130 ++--- test/lib/accountImporter.spec.js | 184 ++++--- test/lib/command.spec.js | 110 ++-- test/lib/config.spec.js | 129 ++--- test/lib/extractTriggers.spec.js | 67 +-- test/lib/fsAsync.spec.js | 111 ++-- test/lib/functionsConfig.spec.js | 56 ++- test/lib/identifierToProjectId.spec.js | 38 +- test/lib/listFiles.spec.js | 30 +- test/lib/localFunction.spec.js | 43 +- test/lib/profilerReport.spec.js | 85 ++-- test/lib/rc.spec.js | 174 ++++--- 176 files changed, 7043 insertions(+), 5598 deletions(-) create mode 100644 .eslintignore create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 test/.eslintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..9dff9645 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +/templates +/node_modules diff --git a/.eslintrc b/.eslintrc index c5263609..203b116e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,148 +1,8 @@ { - "env": { - "node": true, - }, + "plugins": [ + "prettier" + ], "rules": { - /** - * Strict mode - */ - "strict": [2, "global"], // http://eslint.org/docs/rules/strict - - /** - * Variables - */ - "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define - "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow - "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names - "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars - "vars": "local", - "args": "after-used" - }], - "no-undef": 2, - - /** - * Node.js - */ - "no-process-exit": 0, // http://eslint.org/docs/rules/no-process-exit - "no-sync": 0, // http://eslint.org/docs/rules/no-sync - - /** - * Possible errors - */ - "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle - "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign - "no-console": 0, // http://eslint.org/docs/rules/no-console - "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger - "no-alert": 1, // http://eslint.org/docs/rules/no-alert - "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition - "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys - "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case - "no-empty": 2, // http://eslint.org/docs/rules/no-empty - "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign - "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast - "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi - "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign - "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations - "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp - "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace - "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls - "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays - "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable - "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan - "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var - - /** - * Best practices - */ - "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return - "curly": [2, "all"], // http://eslint.org/docs/rules/curly - "default-case": 2, // http://eslint.org/docs/rules/default-case - "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation - "allowKeywords": true - }], - "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq - "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in - "no-caller": 2, // http://eslint.org/docs/rules/no-caller - "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return - "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null - "no-eval": 2, // http://eslint.org/docs/rules/no-eval - "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native - "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind - "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough - "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal - "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval - "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks - "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func - "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str - "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign - "no-new": 2, // http://eslint.org/docs/rules/no-new - "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func - "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers - "no-octal": 2, // http://eslint.org/docs/rules/no-octal - "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape - //"no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign - "no-proto": 2, // http://eslint.org/docs/rules/no-proto - "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare - "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign - "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url - "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare - "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences - "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal - "no-with": 2, // http://eslint.org/docs/rules/no-with - "radix": 2, // http://eslint.org/docs/rules/radix - //"vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top - "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife - "yoda": 2, // http://eslint.org/docs/rules/yoda - - /** - * Style - */ - "indent": [2, 2], // http://eslint.org/docs/rules/indent - "brace-style": [2, // http://eslint.org/docs/rules/brace-style - "1tbs", { - "allowSingleLine": true - }], - "quotes": [ - 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes - ], - "camelcase": [2, { // http://eslint.org/docs/rules/camelcase - "properties": "never" - }], - "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing - "before": false, - "after": true - }], - "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style - "eol-last": 2, // http://eslint.org/docs/rules/eol-last - //"func-names": 1, // http://eslint.org/docs/rules/func-names - "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing - "beforeColon": false, - "afterColon": true - }], - "new-cap": [2, { // http://eslint.org/docs/rules/new-cap - "newIsCap": true - }], - "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines - "max": 2 - }], - "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary - "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object - "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func - "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces - "no-extra-parens": 2, // http://eslint.org/docs/rules/no-extra-parens - "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle - "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var - "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks - "semi": [2, "always"], // http://eslint.org/docs/rules/semi - "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing - "before": false, - "after": true - }], - "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords - "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks - "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren - "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops - "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case - "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-comment + "prettier/prettier": "error" } } diff --git a/.gitignore b/.gitignore index 5c30c06d..f71da71d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.vscode node_modules /coverage +/.nyc_output firebase-debug.log npm-debug.log package-lock.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..9dff9645 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +/templates +/node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8d9d718c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "trailingComma": "es5", + "printWidth": 100 +} diff --git a/.travis.yml b/.travis.yml index 8b77a029..b7b8970b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: node_js node_js: - '4.5' - '5.10' +- '6' - stable sudo: false after_script: diff --git a/bin/firebase b/bin/firebase index f61bce69..531c40c2 100755 --- a/bin/firebase +++ b/bin/firebase @@ -1,79 +1,78 @@ #!/usr/bin/env node -'use strict'; +"use strict"; -var pkg = require('../package.json'); -var updateNotifier = require('update-notifier')({pkg: pkg}); -updateNotifier.notify({defer: true}); +var pkg = require("../package.json"); +var updateNotifier = require("update-notifier")({ pkg: pkg }); +updateNotifier.notify({ defer: true }); -var client = require('..'); -var errorOut = require('../lib/errorOut'); -var winston = require('winston'); -var logger = require('../lib/logger'); -var fs = require('fs'); -var fsutils = require('../lib/fsutils'); -var path = require('path'); -var chalk = require('chalk'); -var configstore = require('../lib/configstore'); -var _ = require('lodash'); +var client = require(".."); +var errorOut = require("../lib/errorOut"); +var winston = require("winston"); +var logger = require("../lib/logger"); +var fs = require("fs"); +var fsutils = require("../lib/fsutils"); +var path = require("path"); +var chalk = require("chalk"); +var configstore = require("../lib/configstore"); +var _ = require("lodash"); var args = process.argv.slice(2); -var handlePreviewToggles = require('../lib/handlePreviewToggles'); -var utils = require('../lib/utils'); +var handlePreviewToggles = require("../lib/handlePreviewToggles"); +var utils = require("../lib/utils"); var cmd; -var logFilename = path.join(process.cwd(), '/firebase-debug.log'); -logger.add(winston.transports.Console, { - level: process.env.DEBUG ? 'debug' : 'info', - showLevel: false, - colorize: true -}).add(winston.transports.File, { - level: 'debug', - filename: logFilename, - json: false, - formatter: function(input) { - input.message = chalk.stripColor(input.message); - return [ - '[' + input.level + ']', - input.message - ].join(' '); - } -}); +var logFilename = path.join(process.cwd(), "/firebase-debug.log"); +logger + .add(winston.transports.Console, { + level: process.env.DEBUG ? "debug" : "info", + showLevel: false, + colorize: true, + }) + .add(winston.transports.File, { + level: "debug", + filename: logFilename, + json: false, + formatter: function(input) { + input.message = chalk.stripColor(input.message); + return ["[" + input.level + "]", input.message].join(" "); + }, + }); var debugging = false; -if (_.includes(args, '--debug')) { - logger.transports.console.level = 'debug'; +if (_.includes(args, "--debug")) { + logger.transports.console.level = "debug"; debugging = true; } -logger.debug(_.repeat('-', 70)); -logger.debug('Command: ', process.argv.join(' ')); -logger.debug('CLI Version: ', pkg.version); -logger.debug('Platform: ', process.platform); -logger.debug('Node Version: ', process.version); -logger.debug('Time: ', new Date().toString()); +logger.debug(_.repeat("-", 70)); +logger.debug("Command: ", process.argv.join(" ")); +logger.debug("CLI Version: ", pkg.version); +logger.debug("Platform: ", process.platform); +logger.debug("Node Version: ", process.version); +logger.debug("Time: ", new Date().toString()); if (utils.envOverrides.length) { - logger.debug('Env Overrides:', utils.envOverrides.join(', ')); + logger.debug("Env Overrides:", utils.envOverrides.join(", ")); } -logger.debug(_.repeat('-', 70)); +logger.debug(_.repeat("-", 70)); logger.debug(); -require('../lib/fetchMOTD')(); +require("../lib/fetchMOTD")(); -process.on('exit', function(code) { +process.on("exit", function(code) { code = process.exitCode || code; if (!debugging && code < 2 && fsutils.fileExistsSync(logFilename)) { fs.unlinkSync(logFilename); } if (code > 0 && process.stdout.isTTY) { - var lastError = configstore.get('lastError') || 0; + var lastError = configstore.get("lastError") || 0; var timestamp = Date.now(); if (lastError > timestamp - 120000) { var help; if (code === 1 && cmd) { - var commandName = _.get(_.last(cmd.args), '_name', '[command]'); - help = 'Having trouble? Try ' + chalk.bold('firebase ' + commandName + ' --help'); + var commandName = _.get(_.last(cmd.args), "_name", "[command]"); + help = "Having trouble? Try " + chalk.bold("firebase " + commandName + " --help"); } else { - help = 'Having trouble? Try again or contact support with contents of firebase-debug.log'; + help = "Having trouble? Try again or contact support with contents of firebase-debug.log"; } if (cmd) { @@ -81,14 +80,14 @@ process.on('exit', function(code) { console.log(help); } } - configstore.set('lastError', timestamp); + configstore.set("lastError", timestamp); } else { - configstore.del('lastError'); + configstore.del("lastError"); } }); -require('exit-code'); +require("exit-code"); -process.on('uncaughtException', function(err) { +process.on("uncaughtException", function(err) { errorOut(client, err); }); @@ -96,7 +95,7 @@ if (!handlePreviewToggles(args)) { cmd = client.cli.parse(process.argv); // determine if there are any non-option arguments. if not, display help args = args.filter(function(arg) { - return arg.indexOf('-') < 0; + return arg.indexOf("-") < 0; }); if (!args.length) { client.cli.help(); diff --git a/commands/auth-export.js b/commands/auth-export.js index cacae029..648f3a5a 100644 --- a/commands/auth-export.js +++ b/commands/auth-export.js @@ -1,43 +1,45 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var fs = require('fs'); -var os = require('os'); +var chalk = require("chalk"); +var fs = require("fs"); +var os = require("os"); -var Command = require('../lib/command'); -var accountExporter = require('../lib/accountExporter'); -var getProjectId = require('../lib/getProjectId'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); +var Command = require("../lib/command"); +var accountExporter = require("../lib/accountExporter"); +var getProjectId = require("../lib/getProjectId"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); var MAX_BATCH_SIZE = 1000; var validateOptions = accountExporter.validateOptions; var serialExportUsers = accountExporter.serialExportUsers; -module.exports = new Command('auth:export [dataFile]') - .description('Export accounts from your Firebase project into a data file') - .option( - '--format ', 'Format of exported data (csv, json). Ignored if [dataFile] has format extension.') - .before(requireAccess) - .action(function(dataFile, options) { - var projectId = getProjectId(options); - var checkRes = validateOptions(options, dataFile); - if (!checkRes.format) { - return checkRes; +module.exports = new Command("auth:export [dataFile]") + .description("Export accounts from your Firebase project into a data file") + .option( + "--format ", + "Format of exported data (csv, json). Ignored if [dataFile] has format extension." + ) + .before(requireAccess) + .action(function(dataFile, options) { + var projectId = getProjectId(options); + var checkRes = validateOptions(options, dataFile); + if (!checkRes.format) { + return checkRes; + } + var exportOptions = checkRes; + var writeStream = fs.createWriteStream(dataFile); + if (exportOptions.format === "json") { + writeStream.write('{"users": [' + os.EOL); + } + exportOptions.writeStream = writeStream; + exportOptions.batchSize = MAX_BATCH_SIZE; + logger.info("Exporting accounts to " + chalk.bold(dataFile)); + return serialExportUsers(projectId, exportOptions).then(function() { + if (exportOptions.format === "json") { + writeStream.write("]}"); } - var exportOptions = checkRes; - var writeStream = fs.createWriteStream(dataFile); - if (exportOptions.format === 'json') { - writeStream.write('{"users": [' + os.EOL); - } - exportOptions.writeStream = writeStream; - exportOptions.batchSize = MAX_BATCH_SIZE; - logger.info('Exporting accounts to ' + chalk.bold(dataFile)); - return serialExportUsers(projectId, exportOptions).then(function() { - if (exportOptions.format === 'json') { - writeStream.write(']}'); - } - writeStream.end(); - }); + writeStream.end(); }); + }); diff --git a/commands/auth-import.js b/commands/auth-import.js index 6dfe6dea..07e91de6 100644 --- a/commands/auth-import.js +++ b/commands/auth-import.js @@ -1,18 +1,18 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var csv = require('csv-streamify'); -var chalk = require('chalk'); -var fs = require('fs'); -var jsonStream = require('JSONStream'); -var _ = require('lodash'); +var RSVP = require("rsvp"); +var csv = require("csv-streamify"); +var chalk = require("chalk"); +var fs = require("fs"); +var jsonStream = require("JSONStream"); +var _ = require("lodash"); -var Command = require('../lib/command'); -var accountImporter = require('../lib/accountImporter'); -var getProjectId = require('../lib/getProjectId'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var accountImporter = require("../lib/accountImporter"); +var getProjectId = require("../lib/getProjectId"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); +var utils = require("../lib/utils"); var MAX_BATCH_SIZE = 1000; var validateOptions = accountImporter.validateOptions; @@ -20,17 +20,25 @@ var validateUserJson = accountImporter.validateUserJson; var transArrayToUser = accountImporter.transArrayToUser; var serialImportUsers = accountImporter.serialImportUsers; -module.exports = new Command('auth:import [dataFile]') - .description('import users into your Firebase project from a data file(.csv or .json)') - .option('--hash-algo ', 'specify the hash algorithm used in password for these accounts') - .option('--hash-key ', 'specify the key used in hash algorithm') - .option('--salt-separator ', - 'specify the salt separator which will be appended to salt when verifying password. only used by SCRYPT now.') - .option('--rounds ', 'specify how many rounds for hash calculation.') - .option('--mem-cost ', 'specify the memory cost for firebase scrypt, or cpu/memory cost for standard scrypt') - .option('--parallelization ', 'specify the parallelization for standard scrypt.') - .option('--block-size ', 'specify the block size (normally is 8) for standard scrypt.') - .option('--dk-len ', 'specify derived key length for standard scrypt.') +module.exports = new Command("auth:import [dataFile]") + .description("import users into your Firebase project from a data file(.csv or .json)") + .option( + "--hash-algo ", + "specify the hash algorithm used in password for these accounts" + ) + .option("--hash-key ", "specify the key used in hash algorithm") + .option( + "--salt-separator ", + "specify the salt separator which will be appended to salt when verifying password. only used by SCRYPT now." + ) + .option("--rounds ", "specify how many rounds for hash calculation.") + .option( + "--mem-cost ", + "specify the memory cost for firebase scrypt, or cpu/memory cost for standard scrypt" + ) + .option("--parallelization ", "specify the parallelization for standard scrypt.") + .option("--block-size ", "specify the block size (normally is 8) for standard scrypt.") + .option("--dk-len ", "specify derived key length for standard scrypt.") .before(requireAccess) .action(function(dataFile, options) { var projectId = getProjectId(options); @@ -40,12 +48,12 @@ module.exports = new Command('auth:import [dataFile]') } var hashOptions = checkRes; - if (!_.endsWith(dataFile, '.csv') && !_.endsWith(dataFile, '.json')) { - return utils.reject('Data file must end with .csv or .json', {exit: 1}); + if (!_.endsWith(dataFile, ".csv") && !_.endsWith(dataFile, ".json")) { + return utils.reject("Data file must end with .csv or .json", { exit: 1 }); } var stats = fs.statSync(dataFile); var fileSizeInBytes = stats.size; - logger.info('Processing ' + chalk.bold(dataFile) + ' (' + fileSizeInBytes + ' bytes)'); + logger.info("Processing " + chalk.bold(dataFile) + " (" + fileSizeInBytes + " bytes)"); var inStream = fs.createReadStream(dataFile); var batches = []; @@ -53,47 +61,52 @@ module.exports = new Command('auth:import [dataFile]') return new RSVP.Promise(function(resolve, reject) { var parser; var counter = 0; - if (dataFile.endsWith('.csv')) { - parser = csv({objectMode: true}); - parser.on('data', function(line) { - counter++; - var user = transArrayToUser(line.map( - function(str) { - // Ignore starting '|'' and trailing '|'' - var newStr = str.trim().replace(/^["|'](.*)["|']$/, '$1'); - return newStr === '' ? undefined : newStr; - })); - currentBatch.push(user); - if (currentBatch.length === MAX_BATCH_SIZE) { - batches.push(currentBatch); - currentBatch = []; - } - }).on('end', function() { - if (currentBatch.length) { - batches.push(currentBatch); - } - return resolve(batches); - }); + if (dataFile.endsWith(".csv")) { + parser = csv({ objectMode: true }); + parser + .on("data", function(line) { + counter++; + var user = transArrayToUser( + line.map(function(str) { + // Ignore starting '|'' and trailing '|'' + var newStr = str.trim().replace(/^["|'](.*)["|']$/, "$1"); + return newStr === "" ? undefined : newStr; + }) + ); + currentBatch.push(user); + if (currentBatch.length === MAX_BATCH_SIZE) { + batches.push(currentBatch); + currentBatch = []; + } + }) + .on("end", function() { + if (currentBatch.length) { + batches.push(currentBatch); + } + return resolve(batches); + }); inStream.pipe(parser); } else { - parser = jsonStream.parse(['users', {emitKey: true}]); - parser.on('data', function(pair) { - counter++; - var res = validateUserJson(pair.value); - if (res.error) { - return reject(res.error); - } - currentBatch.push(pair.value); - if (currentBatch.length === MAX_BATCH_SIZE) { - batches.push(currentBatch); - currentBatch = []; - } - }).on('end', function() { - if (currentBatch.length) { - batches.push(currentBatch); - } - return resolve(batches); - }); + parser = jsonStream.parse(["users", { emitKey: true }]); + parser + .on("data", function(pair) { + counter++; + var res = validateUserJson(pair.value); + if (res.error) { + return reject(res.error); + } + currentBatch.push(pair.value); + if (currentBatch.length === MAX_BATCH_SIZE) { + batches.push(currentBatch); + currentBatch = []; + } + }) + .on("end", function() { + if (currentBatch.length) { + batches.push(currentBatch); + } + return resolve(batches); + }); inStream.pipe(parser); } }).then(function(userListArr) { diff --git a/commands/database-get.js b/commands/database-get.js index b70ea9e4..de6013ec 100644 --- a/commands/database-get.js +++ b/commands/database-get.js @@ -1,16 +1,16 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var request = require('request'); -var api = require('../lib/api'); -var responseToError = require('../lib/responseToError'); -var FirebaseError = require('../lib/error'); -var RSVP = require('rsvp'); -var utils = require('../lib/utils'); -var querystring = require('querystring'); -var _ = require('lodash'); -var fs = require('fs'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var request = require("request"); +var api = require("../lib/api"); +var responseToError = require("../lib/responseToError"); +var FirebaseError = require("../lib/error"); +var RSVP = require("rsvp"); +var utils = require("../lib/utils"); +var querystring = require("querystring"); +var _ = require("lodash"); +var fs = require("fs"); var _applyStringOpts = function(dest, src, keys, jsonKeys) { _.forEach(keys, function(key) { @@ -34,39 +34,54 @@ var _applyStringOpts = function(dest, src, keys, jsonKeys) { }); }; -module.exports = new Command('database:get ') - .description('fetch and print JSON data at the specified path') - .option('-o, --output ', 'save output to the specified file') - .option('--pretty', 'pretty print response') - .option('--shallow', 'return shallow response') - .option('--export', 'include priorities in the output response') - .option('--order-by ', 'select a child key by which to order results') - .option('--order-by-key', 'order by key name') - .option('--order-by-value', 'order by primitive value') - .option('--limit-to-first ', 'limit to the first results') - .option('--limit-to-last ', 'limit to the last results') - .option('--start-at ', 'start results at (based on specified ordering)') - .option('--end-at ', 'end results at (based on specified ordering)') - .option('--equal-to ', 'restrict results to (based on specified ordering)') +module.exports = new Command("database:get ") + .description("fetch and print JSON data at the specified path") + .option("-o, --output ", "save output to the specified file") + .option("--pretty", "pretty print response") + .option("--shallow", "return shallow response") + .option("--export", "include priorities in the output response") + .option("--order-by ", "select a child key by which to order results") + .option("--order-by-key", "order by key name") + .option("--order-by-value", "order by primitive value") + .option("--limit-to-first ", "limit to the first results") + .option("--limit-to-last ", "limit to the last results") + .option("--start-at ", "start results at (based on specified ordering)") + .option("--end-at ", "end results at (based on specified ordering)") + .option("--equal-to ", "restrict results to (based on specified ordering)") .before(requireAccess) .action(function(path, options) { - if (!_.startsWith(path, '/')) { - return utils.reject('Path must begin with /', {exit: 1}); + if (!_.startsWith(path, "/")) { + return utils.reject("Path must begin with /", { exit: 1 }); } - var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + '.json?'; + var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + ".json?"; var query = {}; - if (options.shallow) { query.shallow = 'true'; } - if (options.pretty) { query.print = 'pretty'; } - if (options.export) { query.format = 'export'; } - if (options.orderByKey) { options.orderBy = '$key'; } - if (options.orderByValue) { options.orderBy = '$value'; } - _applyStringOpts(query, options, ['limitToFirst', 'limitToLast'], ['orderBy', 'startAt', 'endAt', 'equalTo']); + if (options.shallow) { + query.shallow = "true"; + } + if (options.pretty) { + query.print = "pretty"; + } + if (options.export) { + query.format = "export"; + } + if (options.orderByKey) { + options.orderBy = "$key"; + } + if (options.orderByValue) { + options.orderBy = "$value"; + } + _applyStringOpts( + query, + options, + ["limitToFirst", "limitToLast"], + ["orderBy", "startAt", "endAt", "equalTo"] + ); url += querystring.stringify(query); var reqOptions = { - url: url + url: url, }; return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { @@ -74,39 +89,42 @@ module.exports = new Command('database:get ') var fileOut = !!options.output; var outStream = fileOut ? fs.createWriteStream(options.output) : process.stdout; var erroring; - var errorResponse = ''; + var errorResponse = ""; var response; - request.get(reqOptionsWithToken) - .on('response', function(res) { + request + .get(reqOptionsWithToken) + .on("response", function(res) { response = res; if (response.statusCode >= 400) { erroring = true; } }) - .on('data', function(chunk) { + .on("data", function(chunk) { if (erroring) { errorResponse += chunk; } else { outStream.write(chunk); } }) - .on('end', function() { - outStream.write('\n'); + .on("end", function() { + outStream.write("\n"); if (erroring) { try { var data = JSON.parse(errorResponse); return reject(responseToError(response, data)); } catch (e) { - return reject(new FirebaseError('Malformed JSON response', { - exit: 2, - original: e - })); + return reject( + new FirebaseError("Malformed JSON response", { + exit: 2, + original: e, + }) + ); } } return resolve(); }) - .on('error', reject); + .on("error", reject); }); }); }); diff --git a/commands/database-profile.js b/commands/database-profile.js index 6998229a..943cf61f 100644 --- a/commands/database-profile.js +++ b/commands/database-profile.js @@ -1,33 +1,45 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); +var _ = require("lodash"); -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var utils = require('../lib/utils'); -var profiler = require('../lib/profiler'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var utils = require("../lib/utils"); +var profiler = require("../lib/profiler"); -var description = 'profile the Realtime Database and generate a usage report'; +var description = "profile the Realtime Database and generate a usage report"; -module.exports = new Command('database:profile') +module.exports = new Command("database:profile") .description(description) - .option('-o, --output ', 'save the output to the specified file') - .option('-d, --duration ', 'collect database usage information for the specified number of seconds') - .option('--raw', 'output the raw stats collected as newline delimited json') - .option('--no-collapse', 'prevent collapsing similar paths into $wildcard locations') - .option('-i, --input ', 'generate the report based on the specified file instead ' + - 'of streaming logs from the database') + .option("-o, --output ", "save the output to the specified file") + .option( + "-d, --duration ", + "collect database usage information for the specified number of seconds" + ) + .option("--raw", "output the raw stats collected as newline delimited json") + .option("--no-collapse", "prevent collapsing similar paths into $wildcard locations") + .option( + "-i, --input ", + "generate the report based on the specified file instead " + + "of streaming logs from the database" + ) .before(requireAccess) .action(function(options) { // Validate options if (options.raw && options.input) { - return utils.reject('Cannot specify both an input file and raw format', {exit: 1}); + return utils.reject("Cannot specify both an input file and raw format", { + exit: 1, + }); } else if (options.parent.json && options.raw) { - return utils.reject('Cannot output raw data in json format', {exit: 1}); - } else if (options.input && _.has(options, 'duration')) { - return utils.reject('Cannot specify a duration for input files', {exit: 1}); - } else if (_.has(options, 'duration') && options.duration <= 0) { - return utils.reject('Must specify a positive number of seconds', {exit: 1}); + return utils.reject("Cannot output raw data in json format", { exit: 1 }); + } else if (options.input && _.has(options, "duration")) { + return utils.reject("Cannot specify a duration for input files", { + exit: 1, + }); + } else if (_.has(options, "duration") && options.duration <= 0) { + return utils.reject("Must specify a positive number of seconds", { + exit: 1, + }); } return profiler(options); diff --git a/commands/database-push.js b/commands/database-push.js index 3be4421a..47daa1f4 100644 --- a/commands/database-push.js +++ b/commands/database-push.js @@ -1,30 +1,31 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var request = require('request'); -var api = require('../lib/api'); -var responseToError = require('../lib/responseToError'); -var FirebaseError = require('../lib/error'); -var RSVP = require('rsvp'); -var utils = require('../lib/utils'); -var chalk = require('chalk'); -var logger = require('../lib/logger'); -var fs = require('fs'); -var Firebase = require('firebase'); -var _ = require('lodash'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var request = require("request"); +var api = require("../lib/api"); +var responseToError = require("../lib/responseToError"); +var FirebaseError = require("../lib/error"); +var RSVP = require("rsvp"); +var utils = require("../lib/utils"); +var chalk = require("chalk"); +var logger = require("../lib/logger"); +var fs = require("fs"); +var Firebase = require("firebase"); +var _ = require("lodash"); -module.exports = new Command('database:push [infile]') - .description('add a new JSON object to a list of data in your Firebase') - .option('-d, --data ', 'specify escaped JSON directly') +module.exports = new Command("database:push [infile]") + .description("add a new JSON object to a list of data in your Firebase") + .option("-d, --data ", "specify escaped JSON directly") .before(requireAccess) .action(function(path, infile, options) { - if (!_.startsWith(path, '/')) { - return utils.reject('Path must begin with /', {exit: 1}); + if (!_.startsWith(path, "/")) { + return utils.reject("Path must begin with /", { exit: 1 }); } - var inStream = utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin); - var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + '.json?'; + var inStream = + utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin); + var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + ".json?"; if (!infile && !options.data) { utils.explainStdin(); @@ -32,32 +33,38 @@ module.exports = new Command('database:push [infile]') var reqOptions = { url: url, - json: true + json: true, }; return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { return new RSVP.Promise(function(resolve, reject) { - inStream.pipe(request.post(reqOptionsWithToken, function(err, res, body) { - logger.info(); - if (err) { - return reject(new FirebaseError('Unexpected error while pushing data', {exit: 2})); - } else if (res.statusCode >= 400) { - return reject(responseToError(res, body)); - } + inStream.pipe( + request.post(reqOptionsWithToken, function(err, res, body) { + logger.info(); + if (err) { + return reject( + new FirebaseError("Unexpected error while pushing data", { + exit: 2, + }) + ); + } else if (res.statusCode >= 400) { + return reject(responseToError(res, body)); + } - if (!_.endsWith(path, '/')) { - path += '/'; - } + if (!_.endsWith(path, "/")) { + path += "/"; + } - var consoleUrl = utils.consoleUrl(options.project, '/database/data' + path + body.name); - var refurl = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + body.name; + var consoleUrl = utils.consoleUrl(options.project, "/database/data" + path + body.name); + var refurl = + utils.addSubdomain(api.realtimeOrigin, options.instance) + path + body.name; - utils.logSuccess('Data pushed successfully'); - logger.info(); - logger.info(chalk.bold('View data at:'), consoleUrl); - return resolve(new Firebase(refurl)); - })); + utils.logSuccess("Data pushed successfully"); + logger.info(); + logger.info(chalk.bold("View data at:"), consoleUrl); + return resolve(new Firebase(refurl)); + }) + ); }); }); }); - diff --git a/commands/database-remove.js b/commands/database-remove.js index b8bb707a..5a90f28d 100644 --- a/commands/database-remove.js +++ b/commands/database-remove.js @@ -1,51 +1,60 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var request = require('request'); -var api = require('../lib/api'); -var responseToError = require('../lib/responseToError'); -var FirebaseError = require('../lib/error'); -var RSVP = require('rsvp'); -var utils = require('../lib/utils'); -var prompt = require('../lib/prompt'); -var chalk = require('chalk'); -var _ = require('lodash'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var request = require("request"); +var api = require("../lib/api"); +var responseToError = require("../lib/responseToError"); +var FirebaseError = require("../lib/error"); +var RSVP = require("rsvp"); +var utils = require("../lib/utils"); +var prompt = require("../lib/prompt"); +var chalk = require("chalk"); +var _ = require("lodash"); -module.exports = new Command('database:remove ') - .description('remove data from your Firebase at the specified path') - .option('-y, --confirm', 'pass this option to bypass confirmation prompt') +module.exports = new Command("database:remove ") + .description("remove data from your Firebase at the specified path") + .option("-y, --confirm", "pass this option to bypass confirmation prompt") .before(requireAccess) .action(function(path, options) { - if (!_.startsWith(path, '/')) { - return utils.reject('Path must begin with /', {exit: 1}); + if (!_.startsWith(path, "/")) { + return utils.reject("Path must begin with /", { exit: 1 }); } - return prompt(options, [{ - type: 'confirm', - name: 'confirm', - default: false, - message: 'You are about to remove all data at ' + chalk.cyan(utils.addSubdomain(api.realtimeOrigin, options.instance) + path) + '. Are you sure?' - }]).then(function() { + return prompt(options, [ + { + type: "confirm", + name: "confirm", + default: false, + message: + "You are about to remove all data at " + + chalk.cyan(utils.addSubdomain(api.realtimeOrigin, options.instance) + path) + + ". Are you sure?", + }, + ]).then(function() { if (!options.confirm) { - return utils.reject('Command aborted.', {exit: 1}); + return utils.reject("Command aborted.", { exit: 1 }); } - var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + '.json?'; + var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + ".json?"; var reqOptions = { url: url, - json: true + json: true, }; return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { return new RSVP.Promise(function(resolve, reject) { request.del(reqOptionsWithToken, function(err, res, body) { if (err) { - return reject(new FirebaseError('Unexpected error while removing data', {exit: 2})); + return reject( + new FirebaseError("Unexpected error while removing data", { + exit: 2, + }) + ); } else if (res.statusCode >= 400) { return reject(responseToError(res, body)); } - utils.logSuccess('Data removed successfully'); + utils.logSuccess("Data removed successfully"); return resolve(); }); }); diff --git a/commands/database-set.js b/commands/database-set.js index 60bdfcd9..ead92e32 100644 --- a/commands/database-set.js +++ b/commands/database-set.js @@ -1,41 +1,48 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var request = require('request'); -var api = require('../lib/api'); -var responseToError = require('../lib/responseToError'); -var FirebaseError = require('../lib/error'); -var RSVP = require('rsvp'); -var utils = require('../lib/utils'); -var chalk = require('chalk'); -var logger = require('../lib/logger'); -var fs = require('fs'); -var prompt = require('../lib/prompt'); -var _ = require('lodash'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var request = require("request"); +var api = require("../lib/api"); +var responseToError = require("../lib/responseToError"); +var FirebaseError = require("../lib/error"); +var RSVP = require("rsvp"); +var utils = require("../lib/utils"); +var chalk = require("chalk"); +var logger = require("../lib/logger"); +var fs = require("fs"); +var prompt = require("../lib/prompt"); +var _ = require("lodash"); -module.exports = new Command('database:set [infile]') - .description('store JSON data at the specified path via STDIN, arg, or file') - .option('-d, --data ', 'specify escaped JSON directly') - .option('-y, --confirm', 'pass this option to bypass confirmation prompt') +module.exports = new Command("database:set [infile]") + .description("store JSON data at the specified path via STDIN, arg, or file") + .option("-d, --data ", "specify escaped JSON directly") + .option("-y, --confirm", "pass this option to bypass confirmation prompt") .before(requireAccess) .action(function(path, infile, options) { - if (!_.startsWith(path, '/')) { - return utils.reject('Path must begin with /', {exit: 1}); + if (!_.startsWith(path, "/")) { + return utils.reject("Path must begin with /", { exit: 1 }); } - return prompt(options, [{ - type: 'confirm', - name: 'confirm', - default: false, - message: 'You are about to overwrite all data at ' + chalk.cyan(utils.addSubdomain(api.realtimeOrigin, options.instance) + path) + '. Are you sure?' - }]).then(function() { + return prompt(options, [ + { + type: "confirm", + name: "confirm", + default: false, + message: + "You are about to overwrite all data at " + + chalk.cyan(utils.addSubdomain(api.realtimeOrigin, options.instance) + path) + + ". Are you sure?", + }, + ]).then(function() { if (!options.confirm) { - return utils.reject('Command aborted.', {exit: 1}); + return utils.reject("Command aborted.", { exit: 1 }); } - var inStream = utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin); - var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + '.json?'; + var inStream = + utils.stringToStream(options.data) || + (infile ? fs.createReadStream(infile) : process.stdin); + var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + ".json?"; if (!infile && !options.data) { utils.explainStdin(); @@ -43,24 +50,33 @@ module.exports = new Command('database:set [infile]') var reqOptions = { url: url, - json: true + json: true, }; return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { return new RSVP.Promise(function(resolve, reject) { - inStream.pipe(request.put(reqOptionsWithToken, function(err, res, body) { - logger.info(); - if (err) { - return reject(new FirebaseError('Unexpected error while setting data', {exit: 2})); - } else if (res.statusCode >= 400) { - return reject(responseToError(res, body)); - } + inStream.pipe( + request.put(reqOptionsWithToken, function(err, res, body) { + logger.info(); + if (err) { + return reject( + new FirebaseError("Unexpected error while setting data", { + exit: 2, + }) + ); + } else if (res.statusCode >= 400) { + return reject(responseToError(res, body)); + } - utils.logSuccess('Data persisted successfully'); - logger.info(); - logger.info(chalk.bold('View data at:'), utils.consoleUrl(options.project, '/database/data' + path)); - return resolve(); - })); + utils.logSuccess("Data persisted successfully"); + logger.info(); + logger.info( + chalk.bold("View data at:"), + utils.consoleUrl(options.project, "/database/data" + path) + ); + return resolve(); + }) + ); }); }); }); diff --git a/commands/database-update.js b/commands/database-update.js index b67e0dd1..e4d96ae6 100644 --- a/commands/database-update.js +++ b/commands/database-update.js @@ -1,41 +1,48 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var request = require('request'); -var api = require('../lib/api'); -var responseToError = require('../lib/responseToError'); -var FirebaseError = require('../lib/error'); -var RSVP = require('rsvp'); -var utils = require('../lib/utils'); -var chalk = require('chalk'); -var logger = require('../lib/logger'); -var fs = require('fs'); -var prompt = require('../lib/prompt'); -var _ = require('lodash'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var request = require("request"); +var api = require("../lib/api"); +var responseToError = require("../lib/responseToError"); +var FirebaseError = require("../lib/error"); +var RSVP = require("rsvp"); +var utils = require("../lib/utils"); +var chalk = require("chalk"); +var logger = require("../lib/logger"); +var fs = require("fs"); +var prompt = require("../lib/prompt"); +var _ = require("lodash"); -module.exports = new Command('database:update [infile]') - .description('update some of the keys for the defined path in your Firebase') - .option('-d, --data ', 'specify escaped JSON directly') - .option('-y, --confirm', 'pass this option to bypass confirmation prompt') +module.exports = new Command("database:update [infile]") + .description("update some of the keys for the defined path in your Firebase") + .option("-d, --data ", "specify escaped JSON directly") + .option("-y, --confirm", "pass this option to bypass confirmation prompt") .before(requireAccess) .action(function(path, infile, options) { - if (!_.startsWith(path, '/')) { - return utils.reject('Path must begin with /', {exit: 1}); + if (!_.startsWith(path, "/")) { + return utils.reject("Path must begin with /", { exit: 1 }); } - return prompt(options, [{ - type: 'confirm', - name: 'confirm', - default: false, - message: 'You are about to modify data at ' + chalk.cyan(utils.addSubdomain(api.realtimeOrigin, options.instance) + path) + '. Are you sure?' - }]).then(function() { + return prompt(options, [ + { + type: "confirm", + name: "confirm", + default: false, + message: + "You are about to modify data at " + + chalk.cyan(utils.addSubdomain(api.realtimeOrigin, options.instance) + path) + + ". Are you sure?", + }, + ]).then(function() { if (!options.confirm) { - return utils.reject('Command aborted.', {exit: 1}); + return utils.reject("Command aborted.", { exit: 1 }); } - var inStream = utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin); - var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + '.json?'; + var inStream = + utils.stringToStream(options.data) || + (infile ? fs.createReadStream(infile) : process.stdin); + var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + path + ".json?"; if (!infile && !options.data) { utils.explainStdin(); @@ -43,24 +50,33 @@ module.exports = new Command('database:update [infile]') var reqOptions = { url: url, - json: true + json: true, }; return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { return new RSVP.Promise(function(resolve, reject) { - inStream.pipe(request.patch(reqOptionsWithToken, function(err, res, body) { - logger.info(); - if (err) { - return reject(new FirebaseError('Unexpected error while setting data', {exit: 2})); - } else if (res.statusCode >= 400) { - return reject(responseToError(res, body)); - } + inStream.pipe( + request.patch(reqOptionsWithToken, function(err, res, body) { + logger.info(); + if (err) { + return reject( + new FirebaseError("Unexpected error while setting data", { + exit: 2, + }) + ); + } else if (res.statusCode >= 400) { + return reject(responseToError(res, body)); + } - utils.logSuccess('Data updated successfully'); - logger.info(); - logger.info(chalk.bold('View data at:'), utils.consoleUrl(options.project, '/database/data' + path)); - return resolve(); - })); + utils.logSuccess("Data updated successfully"); + logger.info(); + logger.info( + chalk.bold("View data at:"), + utils.consoleUrl(options.project, "/database/data" + path) + ); + return resolve(); + }) + ); }); }); }); diff --git a/commands/deploy.js b/commands/deploy.js index 17382d11..8efc8060 100644 --- a/commands/deploy.js +++ b/commands/deploy.js @@ -1,45 +1,49 @@ -'use strict'; +"use strict"; -var acquireRefs = require('../lib/acquireRefs'); -var chalk = require('chalk'); -var checkDupHostingKeys = require('../lib/checkDupHostingKeys'); -var checkValidTargetFilters = require('../lib/checkValidTargetFilters'); -var checkFirebaseSDKVersion = require('../lib/checkFirebaseSDKVersion'); -var Command = require('../lib/command'); -var deploy = require('../lib/deploy'); -var logger = require('../lib/logger'); -var requireConfig = require('../lib/requireConfig'); -var scopes = require('../lib/scopes'); -var utils = require('../lib/utils'); -var filterTargets = require('../lib/filterTargets'); +var acquireRefs = require("../lib/acquireRefs"); +var chalk = require("chalk"); +var checkDupHostingKeys = require("../lib/checkDupHostingKeys"); +var checkValidTargetFilters = require("../lib/checkValidTargetFilters"); +var checkFirebaseSDKVersion = require("../lib/checkFirebaseSDKVersion"); +var Command = require("../lib/command"); +var deploy = require("../lib/deploy"); +var logger = require("../lib/logger"); +var requireConfig = require("../lib/requireConfig"); +var scopes = require("../lib/scopes"); +var utils = require("../lib/utils"); +var filterTargets = require("../lib/filterTargets"); // in order of least time-consuming to most time-consuming -var VALID_TARGETS = ['database', 'storage', 'firestore', 'functions', 'hosting']; +var VALID_TARGETS = ["database", "storage", "firestore", "functions", "hosting"]; -module.exports = new Command('deploy') - .description('deploy code and assets to your Firebase project') - .option('-p, --public ', 'override the Hosting public directory specified in firebase.json') - .option('-m, --message ', 'an optional message describing this deploy') - .option('--only ', 'only deploy to specified, comma-separated targets (e.g. "hosting,storage"). For functions, ' + - 'can specify filters with colons to scope function deploys to only those functions (e.g. "--only functions:func1,functions:func2"). ' + - 'When filtering based on export groups (the exported module object keys), use dots to specify group names ' + - '(e.g. "--only functions:group1.subgroup1,functions:group2)"') - .option('--except ', 'deploy to all targets except specified (e.g. "database")') +module.exports = new Command("deploy") + .description("deploy code and assets to your Firebase project") + .option("-p, --public ", "override the Hosting public directory specified in firebase.json") + .option("-m, --message ", "an optional message describing this deploy") + .option( + "--only ", + 'only deploy to specified, comma-separated targets (e.g. "hosting,storage"). For functions, ' + + 'can specify filters with colons to scope function deploys to only those functions (e.g. "--only functions:func1,functions:func2"). ' + + "When filtering based on export groups (the exported module object keys), use dots to specify group names " + + '(e.g. "--only functions:group1.subgroup1,functions:group2)"' + ) + .option("--except ", 'deploy to all targets except specified (e.g. "database")') .before(requireConfig) .before(function(options) { - return acquireRefs(options, [scopes.CLOUD_PLATFORM]) - .catch(function(err) { - if (options.config.has('functions')) { - throw err; - } + return acquireRefs(options, [scopes.CLOUD_PLATFORM]).catch(function(err) { + if (options.config.has("functions")) { + throw err; + } - logger.info(); - utils.logWarning(chalk.bold('Your CLI authentication needs to be updated to take advantage of new features.')); - utils.logWarning(chalk.bold('Please run ' + chalk.underline('firebase login --reauth'))); - logger.info(); + logger.info(); + utils.logWarning( + chalk.bold("Your CLI authentication needs to be updated to take advantage of new features.") + ); + utils.logWarning(chalk.bold("Please run " + chalk.underline("firebase login --reauth"))); + logger.info(); - return acquireRefs(options, []); - }); + return acquireRefs(options, []); + }); }) .before(checkDupHostingKeys) .before(checkValidTargetFilters) diff --git a/commands/experimental-functions-shell.js b/commands/experimental-functions-shell.js index 56909411..7d83a249 100644 --- a/commands/experimental-functions-shell.js +++ b/commands/experimental-functions-shell.js @@ -1,14 +1,16 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var requireConfig = require('../lib/requireConfig'); -var scopes = require('../lib/scopes'); -var action = require('../lib/functionsShellCommandAction'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var requireConfig = require("../lib/requireConfig"); +var scopes = require("../lib/scopes"); +var action = require("../lib/functionsShellCommandAction"); -module.exports = new Command('experimental:functions:shell') - .description('launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)') - .option('-p, --port ', 'the port on which to emulate functions (default: 5000)', 5000) +module.exports = new Command("experimental:functions:shell") + .description( + "launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)" + ) + .option("-p, --port ", "the port on which to emulate functions (default: 5000)", 5000) .before(requireConfig) .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(action); diff --git a/commands/firestore-delete.js b/commands/firestore-delete.js index 5b114fb9..d6fcad21 100644 --- a/commands/firestore-delete.js +++ b/commands/firestore-delete.js @@ -1,91 +1,114 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var Command = require('../lib/command'); -var FirestoreDelete = require('../lib/firestore/delete'); -var prompt = require('../lib/prompt'); -var requireAccess = require('../lib/requireAccess'); -var RSVP = require('rsvp'); -var scopes = require('../lib/scopes'); -var utils = require('../lib/utils'); +var chalk = require("chalk"); +var Command = require("../lib/command"); +var FirestoreDelete = require("../lib/firestore/delete"); +var prompt = require("../lib/prompt"); +var requireAccess = require("../lib/requireAccess"); +var RSVP = require("rsvp"); +var scopes = require("../lib/scopes"); +var utils = require("../lib/utils"); var _getConfirmationMessage = function(deleteOp, options) { if (options.allCollections) { - return 'You are about to delete ' + chalk.bold.yellow.underline('YOUR ENTIRE DATABASE') - + '. Are you sure?'; + return ( + "You are about to delete " + + chalk.bold.yellow.underline("YOUR ENTIRE DATABASE") + + ". Are you sure?" + ); } if (deleteOp.isDocumentPath) { // Recursive document delete if (options.recursive) { - return 'You are about to delete the document at ' + chalk.cyan(deleteOp.path) - + ' and all of its subcollections. Are you sure?'; + return ( + "You are about to delete the document at " + + chalk.cyan(deleteOp.path) + + " and all of its subcollections. Are you sure?" + ); } // Shallow document delete - return 'You are about to delete the document at ' + chalk.cyan(deleteOp.path) - + '. Are you sure?'; + return ( + "You are about to delete the document at " + chalk.cyan(deleteOp.path) + ". Are you sure?" + ); } // Recursive collection delete if (options.recursive) { - return 'You are about to delete all documents in the collection at ' + chalk.cyan(deleteOp.path) - + ' and all of their subcollections. ' - + 'Are you sure?'; + return ( + "You are about to delete all documents in the collection at " + + chalk.cyan(deleteOp.path) + + " and all of their subcollections. " + + "Are you sure?" + ); } // Shallow collection delete - return 'You are about to delete all documents in the collection at ' + chalk.cyan(deleteOp.path) - + '. Are you sure?'; + return ( + "You are about to delete all documents in the collection at " + + chalk.cyan(deleteOp.path) + + ". Are you sure?" + ); }; -module.exports = new Command('firestore:delete [path]') - .description('Delete data from Cloud Firestore.') - .option('-r, --recursive', 'Recursive. Delete all documents and subcollections. ' - + 'Any action which would result in the deletion of child documents will fail if ' - + 'this argument is not passed. May not be passed along with --shallow.') - .option('--shallow', 'Shallow. Delete only parent documents and ignore documents in ' - + 'subcollections. Any action which would orphan documents will fail if this argument ' - + 'is not passed. May not be passed along with -r.') - .option('--all-collections', 'Delete all. Deletes the entire Firestore database, ' - + 'including all collections and documents. Any other flags or arguments will be ignored.') - .option('-y, --yes', 'No confirmation. Otherwise, a confirmation prompt will appear.') +module.exports = new Command("firestore:delete [path]") + .description("Delete data from Cloud Firestore.") + .option( + "-r, --recursive", + "Recursive. Delete all documents and subcollections. " + + "Any action which would result in the deletion of child documents will fail if " + + "this argument is not passed. May not be passed along with --shallow." + ) + .option( + "--shallow", + "Shallow. Delete only parent documents and ignore documents in " + + "subcollections. Any action which would orphan documents will fail if this argument " + + "is not passed. May not be passed along with -r." + ) + .option( + "--all-collections", + "Delete all. Deletes the entire Firestore database, " + + "including all collections and documents. Any other flags or arguments will be ignored." + ) + .option("-y, --yes", "No confirmation. Otherwise, a confirmation prompt will appear.") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(function(path, options) { // Guarantee path if (!path && !options.allCollections) { - return utils.reject('Must specify a path.', {exit: 1}); + return utils.reject("Must specify a path.", { exit: 1 }); } var deleteOp = new FirestoreDelete(options.project, path, { recursive: options.recursive, shallow: options.shallow, batchSize: 50, - allCollections: options.allCollections + allCollections: options.allCollections, }); var checkPrompt; if (options.yes) { checkPrompt = RSVP.resolve({ confirm: true }); } else { - checkPrompt = prompt(options, [{ - type: 'confirm', - name: 'confirm', - default: false, - message: _getConfirmationMessage(deleteOp, options) - }]); + checkPrompt = prompt(options, [ + { + type: "confirm", + name: "confirm", + default: false, + message: _getConfirmationMessage(deleteOp, options), + }, + ]); } - return checkPrompt - .then(function(res) { - if (!res.confirm) { - return utils.reject('Command aborted.', {exit: 1}); - } + return checkPrompt.then(function(res) { + if (!res.confirm) { + return utils.reject("Command aborted.", { exit: 1 }); + } - if (options.allCollections) { - return deleteOp.deleteDatabase(); - } + if (options.allCollections) { + return deleteOp.deleteDatabase(); + } - return deleteOp.execute(); - }); + return deleteOp.execute(); + }); }); diff --git a/commands/firestore-indexes-list.js b/commands/firestore-indexes-list.js index e49979c3..54997c11 100644 --- a/commands/firestore-indexes-list.js +++ b/commands/firestore-indexes-list.js @@ -1,11 +1,11 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var firestoreIndexes = require('../lib/firestore/indexes.js'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var logger = require('../lib/logger'); -var _ = require('lodash'); +var Command = require("../lib/command"); +var firestoreIndexes = require("../lib/firestore/indexes.js"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var logger = require("../lib/logger"); +var _ = require("lodash"); var _prettyPrint = function(indexes) { indexes.forEach(function(index) { @@ -16,27 +16,29 @@ var _prettyPrint = function(indexes) { var _makeJsonSpec = function(indexes) { return { indexes: indexes.map(function(index) { - return _.pick(index, ['collectionId', 'fields']); - }) + return _.pick(index, ["collectionId", "fields"]); + }), }; }; -module.exports = new Command('firestore:indexes') - .description('List indexes in your project\'s Cloud Firestore database.') - .option('--pretty', 'Pretty print. When not specified the indexes are printed in the ' - + 'JSON specification format.') +module.exports = new Command("firestore:indexes") + .description("List indexes in your project's Cloud Firestore database.") + .option( + "--pretty", + "Pretty print. When not specified the indexes are printed in the " + + "JSON specification format." + ) .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(function(options) { - return firestoreIndexes.list(options.project) - .then(function(indexes) { - var jsonSpec = _makeJsonSpec(indexes); + return firestoreIndexes.list(options.project).then(function(indexes) { + var jsonSpec = _makeJsonSpec(indexes); - if (options.pretty) { - _prettyPrint(indexes); - } else { - logger.info(JSON.stringify(jsonSpec, undefined, 2)); - } + if (options.pretty) { + _prettyPrint(indexes); + } else { + logger.info(JSON.stringify(jsonSpec, undefined, 2)); + } - return jsonSpec; - }); + return jsonSpec; + }); }); diff --git a/commands/functions-config-clone.js b/commands/functions-config-clone.js index 6ca442b1..92127c15 100644 --- a/commands/functions-config-clone.js +++ b/commands/functions-config-clone.js @@ -1,43 +1,53 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var Command = require('../lib/command'); -var functionsConfig = require('../lib/functionsConfig'); -var functionsConfigClone = require('../lib/functionsConfigClone'); -var getProjectId = require('../lib/getProjectId'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var utils = require('../lib/utils'); -var logger = require('../lib/logger'); +var chalk = require("chalk"); +var Command = require("../lib/command"); +var functionsConfig = require("../lib/functionsConfig"); +var functionsConfigClone = require("../lib/functionsConfigClone"); +var getProjectId = require("../lib/getProjectId"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var utils = require("../lib/utils"); +var logger = require("../lib/logger"); -module.exports = new Command('functions:config:clone') - .description('clone environment config from another project') - .option('--from ', 'the project from which to clone configuration') - .option('--only ', 'a comma-separated list of keys to clone') - .option('--except ', 'a comma-separated list of keys to not clone') +module.exports = new Command("functions:config:clone") + .description("clone environment config from another project") + .option("--from ", "the project from which to clone configuration") + .option("--only ", "a comma-separated list of keys to clone") + .option("--except ", "a comma-separated list of keys to not clone") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .before(functionsConfig.ensureApi) .action(function(options) { var projectId = getProjectId(options); if (!options.from) { - return utils.reject('Must specify a source project in ' + chalk.bold('--from ') + ' option.'); + return utils.reject( + "Must specify a source project in " + chalk.bold("--from ") + " option." + ); } else if (options.from === projectId) { - return utils.reject('From project and destination can\'t be the same project.'); + return utils.reject("From project and destination can't be the same project."); } else if (options.only && options.except) { - return utils.reject('Cannot use both --only and --except at the same time.'); + return utils.reject("Cannot use both --only and --except at the same time."); } var only; var except; if (options.only) { - only = options.only.split(','); + only = options.only.split(","); } else if (options.except) { - except = options.except.split(','); + except = options.except.split(","); } return functionsConfigClone(options.from, projectId, only, except).then(function() { - utils.logSuccess('Cloned functions config from ' + chalk.bold(options.from) + ' into ' + chalk.bold(projectId)); - logger.info('\nPlease deploy your functions for the change to take effect by running ' - + chalk.bold('firebase deploy --only functions') + '\n'); + utils.logSuccess( + "Cloned functions config from " + + chalk.bold(options.from) + + " into " + + chalk.bold(projectId) + ); + logger.info( + "\nPlease deploy your functions for the change to take effect by running " + + chalk.bold("firebase deploy --only functions") + + "\n" + ); }); }); diff --git a/commands/functions-config-get.js b/commands/functions-config-get.js index 5ca496ae..9291efd0 100644 --- a/commands/functions-config-get.js +++ b/commands/functions-config-get.js @@ -1,28 +1,30 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var Command = require('../lib/command'); -var getProjectId = require('../lib/getProjectId'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var functionsConfig = require('../lib/functionsConfig'); +var _ = require("lodash"); +var Command = require("../lib/command"); +var getProjectId = require("../lib/getProjectId"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var functionsConfig = require("../lib/functionsConfig"); function _materialize(projectId, path) { if (_.isUndefined(path)) { return functionsConfig.materializeAll(projectId); } - var parts = path.split('.'); + var parts = path.split("."); var configId = parts[0]; - var configName = _.join(['projects', projectId, 'configs', configId], '/'); + var configName = _.join(["projects", projectId, "configs", configId], "/"); return functionsConfig.materializeConfig(configName, {}).then(function(result) { - var query = _.chain(parts).join('.').value(); + var query = _.chain(parts) + .join(".") + .value(); return query ? _.get(result, query) : result; }); } -module.exports = new Command('functions:config:get [path]') - .description('fetch environment config stored at the given path') +module.exports = new Command("functions:config:get [path]") + .description("fetch environment config stored at the given path") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .before(functionsConfig.ensureApi) .action(function(path, options) { diff --git a/commands/functions-config-legacy.js b/commands/functions-config-legacy.js index f07eab1d..3bc6b4ed 100644 --- a/commands/functions-config-legacy.js +++ b/commands/functions-config-legacy.js @@ -1,40 +1,44 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var Command = require('../lib/command'); -var getProjectId = require('../lib/getProjectId'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var runtimeconfig = require('../lib/gcp/runtimeconfig'); -var functionsConfig = require('../lib/functionsConfig'); -var logger = require('../lib/logger'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var Command = require("../lib/command"); +var getProjectId = require("../lib/getProjectId"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var runtimeconfig = require("../lib/gcp/runtimeconfig"); +var functionsConfig = require("../lib/functionsConfig"); +var logger = require("../lib/logger"); -module.exports = new Command('functions:config:legacy') - .description('get legacy functions config variables') +module.exports = new Command("functions:config:legacy") + .description("get legacy functions config variables") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(function(options) { var projectId = getProjectId(options); - var metaPath = 'projects/' + projectId + '/configs/firebase/variables/meta'; - return runtimeconfig.variables.get(metaPath).then(function(result) { - var metaVal = JSON.parse(result.text); - if (!_.has(metaVal, 'version')) { - logger.info('You do not have any legacy config variables.'); - return null; - } - var latestVarPath = functionsConfig.idsToVarName(projectId, 'firebase', metaVal.version); - return runtimeconfig.variables.get(latestVarPath); - }).then(function(latest) { - if (latest !== null) { - var latestVal = JSON.parse(latest.text); - logger.info(JSON.stringify(latestVal, null, 2)); - return latestVal; - } - }).catch(function(err) { - if (_.get(err, 'context.response.statusCode') === 404) { - logger.info('You do not have any legacy config variables.'); - return null; - } - return RSVP.reject(err); - }); + var metaPath = "projects/" + projectId + "/configs/firebase/variables/meta"; + return runtimeconfig.variables + .get(metaPath) + .then(function(result) { + var metaVal = JSON.parse(result.text); + if (!_.has(metaVal, "version")) { + logger.info("You do not have any legacy config variables."); + return null; + } + var latestVarPath = functionsConfig.idsToVarName(projectId, "firebase", metaVal.version); + return runtimeconfig.variables.get(latestVarPath); + }) + .then(function(latest) { + if (latest !== null) { + var latestVal = JSON.parse(latest.text); + logger.info(JSON.stringify(latestVal, null, 2)); + return latestVal; + } + }) + .catch(function(err) { + if (_.get(err, "context.response.statusCode") === 404) { + logger.info("You do not have any legacy config variables."); + return null; + } + return RSVP.reject(err); + }); }); diff --git a/commands/functions-config-set.js b/commands/functions-config-set.js index c8c7ddd4..193415c5 100644 --- a/commands/functions-config-set.js +++ b/commands/functions-config-set.js @@ -1,34 +1,41 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var RSVP = require('rsvp'); -var Command = require('../lib/command'); -var getProjectId = require('../lib/getProjectId'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var logger = require('../lib/logger'); -var utils = require('../lib/utils'); -var functionsConfig = require('../lib/functionsConfig'); +var chalk = require("chalk"); +var RSVP = require("rsvp"); +var Command = require("../lib/command"); +var getProjectId = require("../lib/getProjectId"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var logger = require("../lib/logger"); +var utils = require("../lib/utils"); +var functionsConfig = require("../lib/functionsConfig"); -module.exports = new Command('functions:config:set [values...]') - .description('set environment config with key=value syntax') +module.exports = new Command("functions:config:set [values...]") + .description("set environment config with key=value syntax") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .before(functionsConfig.ensureApi) .action(function(args, options) { if (!args.length) { - return utils.reject('Must supply at least one key/value pair, e.g. ' + chalk.bold('app.name="My App"')); + return utils.reject( + "Must supply at least one key/value pair, e.g. " + chalk.bold('app.name="My App"') + ); } var projectId = getProjectId(options); var parsed = functionsConfig.parseSetArgs(args); var promises = []; parsed.forEach(function(item) { - promises.push(functionsConfig.setVariablesRecursive(projectId, item.configId, item.varId, item.val)); + promises.push( + functionsConfig.setVariablesRecursive(projectId, item.configId, item.varId, item.val) + ); }); return RSVP.all(promises).then(function() { - utils.logSuccess('Functions config updated.'); - logger.info('\nPlease deploy your functions for the change to take effect by running ' - + chalk.bold('firebase deploy --only functions') + '\n'); + utils.logSuccess("Functions config updated."); + logger.info( + "\nPlease deploy your functions for the change to take effect by running " + + chalk.bold("firebase deploy --only functions") + + "\n" + ); }); }); diff --git a/commands/functions-config-unset.js b/commands/functions-config-unset.js index 7dfbb0ce..24fae1a4 100644 --- a/commands/functions-config-unset.js +++ b/commands/functions-config-unset.js @@ -1,35 +1,40 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var chalk = require('chalk'); -var Command = require('../lib/command'); -var functionsConfig = require('../lib/functionsConfig'); -var getProjectId = require('../lib/getProjectId'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var utils = require('../lib/utils'); -var runtimeconfig = require('../lib/gcp/runtimeconfig'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var chalk = require("chalk"); +var Command = require("../lib/command"); +var functionsConfig = require("../lib/functionsConfig"); +var getProjectId = require("../lib/getProjectId"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var utils = require("../lib/utils"); +var runtimeconfig = require("../lib/gcp/runtimeconfig"); -module.exports = new Command('functions:config:unset [keys...]') - .description('unset environment config at the specified path(s)') +module.exports = new Command("functions:config:unset [keys...]") + .description("unset environment config at the specified path(s)") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .before(functionsConfig.ensureApi) .action(function(args, options) { if (!args.length) { - return utils.reject('Must supply at least one key'); + return utils.reject("Must supply at least one key"); } var projectId = getProjectId(options); var parsed = functionsConfig.parseUnsetArgs(args); - return RSVP.all(_.map(parsed, function(item) { - if (item.varId === '') { - return runtimeconfig.configs.delete(projectId, item.configId); - } - return runtimeconfig.variables.delete(projectId, item.configId, item.varId); - })).then(function() { - utils.logSuccess('Environment updated.'); - logger.info('\nPlease deploy your functions for the change to take effect by running ' - + chalk.bold('firebase deploy --only functions') + '\n'); + return RSVP.all( + _.map(parsed, function(item) { + if (item.varId === "") { + return runtimeconfig.configs.delete(projectId, item.configId); + } + return runtimeconfig.variables.delete(projectId, item.configId, item.varId); + }) + ).then(function() { + utils.logSuccess("Environment updated."); + logger.info( + "\nPlease deploy your functions for the change to take effect by running " + + chalk.bold("firebase deploy --only functions") + + "\n" + ); }); }); diff --git a/commands/functions-log.js b/commands/functions-log.js index fdb374c6..0f349684 100644 --- a/commands/functions-log.js +++ b/commands/functions-log.js @@ -1,58 +1,71 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var Command = require('../lib/command'); -var FirebaseError = require('../lib/error'); -var gcp = require('../lib/gcp'); -var getProjectId = require('../lib/getProjectId'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var open = require('open'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var Command = require("../lib/command"); +var FirebaseError = require("../lib/error"); +var gcp = require("../lib/gcp"); +var getProjectId = require("../lib/getProjectId"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var open = require("open"); -module.exports = new Command('functions:log') - .description('read logs from deployed functions') - .option('--only ', 'only show logs of specified, comma-seperated functions (e.g. "funcA,funcB")') - .option('-n, --lines ', 'specify number of log lines to fetch') - .option('--open', 'open logs page in web browser') +module.exports = new Command("functions:log") + .description("read logs from deployed functions") + .option( + "--only ", + 'only show logs of specified, comma-seperated functions (e.g. "funcA,funcB")' + ) + .option("-n, --lines ", "specify number of log lines to fetch") + .option("--open", "open logs page in web browser") .before(requireAccess, [scopes.OPENID, scopes.CLOUD_PLATFORM]) .action(function(options) { var projectId = getProjectId(options); var apiFilter = 'resource.type="cloud_function" '; var consoleFilter = 'metadata.serviceName:"cloudfunctions.googleapis.com"'; if (options.only) { - var funcNames = options.only.split(','); + var funcNames = options.only.split(","); var apiFuncFilters = _.map(funcNames, function(funcName) { return 'resource.labels.function_name="' + funcName + '" '; }); var consoleFuncFilters = _.map(funcNames, function(funcName) { return 'metadata.labels."cloudfunctions.googleapis.com/function_name":"' + funcName + '" '; }); - apiFilter += apiFuncFilters.join('OR '); - consoleFilter = [consoleFilter, consoleFuncFilters.join('%20OR%20')].join('%0A'); + apiFilter += apiFuncFilters.join("OR "); + consoleFilter = [consoleFilter, consoleFuncFilters.join("%20OR%20")].join("%0A"); } if (options.open) { - var url = 'https://console.developers.google.com/logs/viewer?advancedFilter=' + consoleFilter + '&project=' + projectId; + var url = + "https://console.developers.google.com/logs/viewer?advancedFilter=" + + consoleFilter + + "&project=" + + projectId; open(url); return RSVP.resolve(); } - return gcp.cloudlogging.listEntries(projectId, apiFilter, options.lines || 35, 'desc') - .then(function(entries) { - for (var i = _.size(entries); i-- > 0;) { - var entry = entries[i]; - logger.info( - entry.timestamp, - entry.severity.substring(0, 1), - entry.resource.labels.function_name + ':', - entry.textPayload); - } - if (_.isEmpty(entries)) { - logger.info('No log entries found.'); - } - return RSVP.resolve(entries); - }).catch(function(err) { - return RSVP.reject(new FirebaseError( - 'Failed to list log entries ' + err.message, {exit: 1})); - }); + return gcp.cloudlogging + .listEntries(projectId, apiFilter, options.lines || 35, "desc") + .then(function(entries) { + for (var i = _.size(entries); i-- > 0; ) { + var entry = entries[i]; + logger.info( + entry.timestamp, + entry.severity.substring(0, 1), + entry.resource.labels.function_name + ":", + entry.textPayload + ); + } + if (_.isEmpty(entries)) { + logger.info("No log entries found."); + } + return RSVP.resolve(entries); + }) + .catch(function(err) { + return RSVP.reject( + new FirebaseError("Failed to list log entries " + err.message, { + exit: 1, + }) + ); + }); }); diff --git a/commands/functions-shell.js b/commands/functions-shell.js index 42e0b9a9..f64abda0 100644 --- a/commands/functions-shell.js +++ b/commands/functions-shell.js @@ -1,14 +1,14 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var requireConfig = require('../lib/requireConfig'); -var scopes = require('../lib/scopes'); -var action = require('../lib/functionsShellCommandAction'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var requireConfig = require("../lib/requireConfig"); +var scopes = require("../lib/scopes"); +var action = require("../lib/functionsShellCommandAction"); -module.exports = new Command('functions:shell') - .description('launch full Node shell with emulated functions') - .option('-p, --port ', 'the port on which to emulate functions (default: 5000)', 5000) +module.exports = new Command("functions:shell") + .description("launch full Node shell with emulated functions") + .option("-p, --port ", "the port on which to emulate functions (default: 5000)", 5000) .before(requireConfig) .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(action); diff --git a/commands/help.js b/commands/help.js index 75ce239e..b5585178 100644 --- a/commands/help.js +++ b/commands/help.js @@ -1,25 +1,30 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var RSVP = require('rsvp'); -var chalk = require('chalk'); -var logger = require('../lib/logger'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var RSVP = require("rsvp"); +var chalk = require("chalk"); +var logger = require("../lib/logger"); +var utils = require("../lib/utils"); -module.exports = new Command('help [command]') - .description('display help information') +module.exports = new Command("help [command]") + .description("display help information") .action(function(commandName) { var cmd = this.client.getCommand(commandName); if (cmd) { cmd.outputHelp(); } else if (commandName) { logger.warn(); - utils.logWarning(chalk.bold(commandName) + ' is not a valid command. See below for valid commands'); + utils.logWarning( + chalk.bold(commandName) + " is not a valid command. See below for valid commands" + ); this.client.cli.outputHelp(); } else { this.client.cli.outputHelp(); logger.info(); - logger.info(' To get help with a specific command, type', chalk.bold('firebase help [command_name]')); + logger.info( + " To get help with a specific command, type", + chalk.bold("firebase help [command_name]") + ); logger.info(); } diff --git a/commands/hosting-disable.js b/commands/hosting-disable.js index 158606a8..f1e852fe 100644 --- a/commands/hosting-disable.js +++ b/commands/hosting-disable.js @@ -1,41 +1,53 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var requireAccess = require('../lib/requireAccess'); -var api = require('../lib/api'); -var utils = require('../lib/utils'); -var prompt = require('../lib/prompt'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var Command = require("../lib/command"); +var requireAccess = require("../lib/requireAccess"); +var api = require("../lib/api"); +var utils = require("../lib/utils"); +var prompt = require("../lib/prompt"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -module.exports = new Command('hosting:disable') - .description('stop serving web traffic to your Firebase Hosting site') - .option('-y, --confirm', 'skip confirmation') +module.exports = new Command("hosting:disable") + .description("stop serving web traffic to your Firebase Hosting site") + .option("-y, --confirm", "skip confirmation") .before(requireAccess) .action(function(options) { return prompt(options, [ { - type: 'confirm', - name: 'confirm', - message: 'Are you sure you want to disable Firebase Hosting?\n ' + chalk.bold.underline('This will immediately make your site inaccessible!') - } - ]).then(function() { - if (!options.confirm) { - return RSVP.resolve(); - } + type: "confirm", + name: "confirm", + message: + "Are you sure you want to disable Firebase Hosting?\n " + + chalk.bold.underline("This will immediately make your site inaccessible!"), + }, + ]) + .then(function() { + if (!options.confirm) { + return RSVP.resolve(); + } - return api.request('POST', '/v1/projects/' + encodeURIComponent(options.project) + '/releases', { - auth: true, - data: { - hosting: { - disabled: true + return api.request( + "POST", + "/v1/projects/" + encodeURIComponent(options.project) + "/releases", + { + auth: true, + data: { + hosting: { + disabled: true, + }, + }, + origin: api.deployOrigin, } - }, - origin: api.deployOrigin + ); + }) + .then(function() { + if (options.confirm) { + utils.logSuccess( + "Hosting has been disabled for " + + chalk.bold(options.project) + + ". Deploy a new version to re-enable." + ); + } }); - }).then(function() { - if (options.confirm) { - utils.logSuccess('Hosting has been disabled for ' + chalk.bold(options.project) + '. Deploy a new version to re-enable.'); - } - }); }); diff --git a/commands/index.js b/commands/index.js index 7c0a13c1..da2598ef 100644 --- a/commands/index.js +++ b/commands/index.js @@ -1,88 +1,88 @@ -'use strict'; +"use strict"; -var previews = require('../lib/previews'); +var previews = require("../lib/previews"); module.exports = function(client) { var loadCommand = function(name) { - var cmd = require('./' + name); + var cmd = require("./" + name); cmd.register(client); return cmd.runner(); }; client.auth = { - upload: loadCommand('auth-import'), - export: loadCommand('auth-export') + upload: loadCommand("auth-import"), + export: loadCommand("auth-export"), }; client.database = { - get: loadCommand('database-get'), - push: loadCommand('database-push'), - set: loadCommand('database-set'), - remove: loadCommand('database-remove'), - update: loadCommand('database-update'), - profile: loadCommand('database-profile') + get: loadCommand("database-get"), + push: loadCommand("database-push"), + set: loadCommand("database-set"), + remove: loadCommand("database-remove"), + update: loadCommand("database-update"), + profile: loadCommand("database-profile"), }; client.firestore = { - delete: loadCommand('firestore-delete'), - indexes: loadCommand('firestore-indexes-list') + delete: loadCommand("firestore-delete"), + indexes: loadCommand("firestore-indexes-list"), }; - client.deploy = loadCommand('deploy'); + client.deploy = loadCommand("deploy"); client.hosting = { - disable: loadCommand('hosting-disable') + disable: loadCommand("hosting-disable"), }; client.functions = { - log: loadCommand('functions-log'), - shell: loadCommand('functions-shell'), + log: loadCommand("functions-log"), + shell: loadCommand("functions-shell"), config: { - clone: loadCommand('functions-config-clone'), - get: loadCommand('functions-config-get'), - set: loadCommand('functions-config-set'), - unset: loadCommand('functions-config-unset') - } + clone: loadCommand("functions-config-clone"), + get: loadCommand("functions-config-get"), + set: loadCommand("functions-config-set"), + unset: loadCommand("functions-config-unset"), + }, }; client.experimental = { functions: { - shell: loadCommand('experimental-functions-shell') - } + shell: loadCommand("experimental-functions-shell"), + }, }; - client.help = loadCommand('help'); + client.help = loadCommand("help"); if (previews.kits) { client.kits = { - install: loadCommand('kits-install'), - uninstall: loadCommand('kits-uninstall') + install: loadCommand("kits-install"), + uninstall: loadCommand("kits-uninstall"), }; } - client.init = loadCommand('init'); - client.list = loadCommand('list'); + client.init = loadCommand("init"); + client.list = loadCommand("list"); - client.login = loadCommand('login'); - client.login.ci = loadCommand('login-ci'); + client.login = loadCommand("login"); + client.login.ci = loadCommand("login-ci"); - client.logout = loadCommand('logout'); - client.open = loadCommand('open'); - client.serve = loadCommand('serve'); + client.logout = loadCommand("logout"); + client.open = loadCommand("open"); + client.serve = loadCommand("serve"); client.setup = { - web: loadCommand('setup-web') + web: loadCommand("setup-web"), }; - client.target = loadCommand('target'); - client.target.apply = loadCommand('target-apply'); - client.target.clear = loadCommand('target-clear'); - client.target.remove = loadCommand('target-remove'); + client.target = loadCommand("target"); + client.target.apply = loadCommand("target-apply"); + client.target.clear = loadCommand("target-clear"); + client.target.remove = loadCommand("target-remove"); client.tools = { - migrate: loadCommand('tools-migrate') + migrate: loadCommand("tools-migrate"), }; - client.use = loadCommand('use'); + client.use = loadCommand("use"); return client; }; diff --git a/commands/init.js b/commands/init.js index 4989a084..2355761f 100644 --- a/commands/init.js +++ b/commands/init.js @@ -1,130 +1,175 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var fs = require('fs'); -var homeDir = require('user-home'); -var path = require('path'); -var RSVP = require('rsvp'); +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 init = require('../lib/init'); -var logger = require('../lib/logger'); -var prompt = require('../lib/prompt'); -var requireAuth = require('../lib/requireAuth'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var Config = require("../lib/config"); +var init = require("../lib/init"); +var logger = require("../lib/logger"); +var prompt = require("../lib/prompt"); +var requireAuth = require("../lib/requireAuth"); +var utils = require("../lib/utils"); -var BANNER_TEXT = fs.readFileSync(__dirname + '/../templates/banner.txt', 'utf8'); +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 [feature]') - .description('setup a Firebase project in the current directory') +module.exports = new Command("init [feature]") + .description("setup a Firebase project in the current directory") .before(requireAuth) .action(function(feature, options) { var cwd = options.cwd || process.cwd(); var warnings = []; - var warningText = ''; + var warningText = ""; if (_isOutside(homeDir, cwd)) { - warnings.push('You are currently outside your home directory'); + warnings.push("You are currently outside your home directory"); } if (cwd === homeDir) { - warnings.push('You are initializing your home directory as a Firebase project'); + warnings.push("You are initializing your home directory as a Firebase project"); } var config = Config.load(options, true); var existingConfig = !!config; if (!existingConfig) { - config = new Config({}, {projectDir: cwd, cwd: cwd}); + config = new Config({}, { projectDir: cwd, cwd: cwd }); } else { - warnings.push('You are initializing in an existing Firebase project directory'); + warnings.push("You are initializing in an existing Firebase project directory"); } if (warnings.length) { - warningText = '\nBefore we get started, keep in mind:\n\n ' + chalk.yellow.bold('* ') + warnings.join('\n ' + chalk.yellow.bold('* ')) + '\n'; + warningText = + "\nBefore we get started, keep in mind:\n\n " + + chalk.yellow.bold("* ") + + warnings.join("\n " + chalk.yellow.bold("* ")) + + "\n"; } - if (process.platform === 'darwin') { - BANNER_TEXT = BANNER_TEXT.replace(/#/g, '🔥'); + 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 + 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 ); var setup = { config: config._src, - rcfile: config.readProjectFile('.firebaserc', { + rcfile: config.readProjectFile(".firebaserc", { json: true, - fallback: {} - }) + fallback: {}, + }), }; var choices = [ - {name: 'database', label: 'Database: Deploy Firebase Realtime Database Rules', checked: false}, - {name: 'firestore', label: 'Firestore: Deploy rules and create indexes for Firestore', checked: false}, - {name: 'functions', label: 'Functions: Configure and deploy Cloud Functions', checked: false}, - {name: 'hosting', label: 'Hosting: Configure and deploy Firebase Hosting sites', checked: false}, - {name: 'storage', label: 'Storage: Deploy Cloud Storage security rules', checked: false} + { + name: "database", + label: "Database: Deploy Firebase Realtime Database Rules", + checked: false, + }, + { + name: "firestore", + label: "Firestore: Deploy rules and create indexes for Firestore", + checked: false, + }, + { + name: "functions", + label: "Functions: Configure and deploy Cloud Functions", + checked: false, + }, + { + name: "hosting", + label: "Hosting: Configure and deploy Firebase Hosting sites", + checked: false, + }, + { + name: "storage", + label: "Storage: Deploy Cloud Storage security rules", + checked: false, + }, ]; var next; // HACK: Windows Node has issues with selectables as the first prompt, so we // add an extra confirmation prompt that fixes the problem - if (process.platform === 'win32') { + if (process.platform === "win32") { next = prompt.once({ - type: 'confirm', - message: 'Are you ready to proceed?' + type: "confirm", + message: "Are you ready to proceed?", }); } else { next = RSVP.resolve(true); } - return next.then(function(proceed) { - if (!proceed) { - return utils.reject('Aborted by user.', {exit: 1}); - } + return next + .then(function(proceed) { + if (!proceed) { + return utils.reject("Aborted by user.", { exit: 1 }); + } - if (feature) { - setup.featureArg = true; - setup.features = [feature]; - return undefined; - } + if (feature) { + setup.featureArg = true; + setup.features = [feature]; + return undefined; + } - return prompt(setup, [{ - type: 'checkbox', - name: 'features', - message: 'Which Firebase CLI features do you want to setup for this folder? ' + - 'Press Space to select features, then Enter to confirm your choices.', - choices: prompt.convertLabeledListChoices(choices) - }]); - }).then(function() { - if (!setup.featureArg) { - setup.features = setup.features.map(function(feat) { - return prompt.listLabelToValue(feat, choices); - }); - } - if (setup.features.length === 0) { - return utils.reject('Must select at least one feature. Use ' + chalk.bold.underline('SPACEBAR') + ' to select features, or provide a feature with ' + chalk.bold('firebase init [feature_name]')); - } - setup.features.unshift('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!'); - - if (setup.createProject) { + return prompt(setup, [ + { + type: "checkbox", + name: "features", + message: + "Which Firebase CLI features do you want to setup for this folder? " + + "Press Space to select features, then Enter to confirm your choices.", + choices: prompt.convertLabeledListChoices(choices), + }, + ]); + }) + .then(function() { + if (!setup.featureArg) { + setup.features = setup.features.map(function(feat) { + return prompt.listLabelToValue(feat, choices); + }); + } + if (setup.features.length === 0) { + return utils.reject( + "Must select at least one feature. Use " + + chalk.bold.underline("SPACEBAR") + + " to select features, or provide a feature with " + + chalk.bold("firebase init [feature_name]") + ); + } + setup.features.unshift("project"); + return init(setup, config, options); + }) + .then(function() { logger.info(); - logger.info(chalk.bold.cyan('Project creation is only available from the Firebase Console')); - logger.info('Please visit', chalk.underline('https://console.firebase.google.com'), 'to create a new project, then run', chalk.bold('firebase use --add')); - } - }); + 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!"); + + if (setup.createProject) { + logger.info(); + logger.info( + chalk.bold.cyan("Project creation is only available from the Firebase Console") + ); + logger.info( + "Please visit", + chalk.underline("https://console.firebase.google.com"), + "to create a new project, then run", + chalk.bold("firebase use --add") + ); + } + }); }); diff --git a/commands/kits-install.js b/commands/kits-install.js index a53197f9..49bdb3d3 100644 --- a/commands/kits-install.js +++ b/commands/kits-install.js @@ -1,24 +1,24 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); +var chalk = require("chalk"); -var Command = require('../lib/command'); -var getProjectId = require('../lib/getProjectId'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var utils = require('../lib/utils'); -var kits = require('../lib/kits'); +var Command = require("../lib/command"); +var getProjectId = require("../lib/getProjectId"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var utils = require("../lib/utils"); +var kits = require("../lib/kits"); // TODO: add option for urlPath to be inserted or parse urlPath for name if needed -module.exports = new Command('kits:install ') - .option('-b, --branch ', 'repository branch to download from. Defaults to master') - .option('-p, --path ', 'custom path to kit configuration file. Defaults to kits.json') - .option('--id ', 'release version to be installed. Defaults to latest') +module.exports = new Command("kits:install ") + .option("-b, --branch ", "repository branch to download from. Defaults to master") + .option("-p, --path ", "custom path to kit configuration file. Defaults to kits.json") + .option("--id ", "release version to be installed. Defaults to latest") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(function(githubRepo, options) { var projectId = getProjectId(options); - var kit = githubRepo.split('/'); + var kit = githubRepo.split("/"); var kitOwner; var kitRepo; @@ -27,39 +27,49 @@ module.exports = new Command('kits:install ') kitOwner = kit[0]; kitRepo = kit[1]; } else { - kitOwner = 'function-kits'; + kitOwner = "function-kits"; kitRepo = kit[0]; } var githubConfig = { - id: options.releaseId || 'latest', + id: options.releaseId || "latest", owner: kitOwner, - manifestPath: options.path || 'kits.json', - ref: options.branch || 'master', - repo: kitRepo + manifestPath: options.path || "kits.json", + ref: options.branch || "master", + repo: kitRepo, }; - var gitRepo = kitOwner + '/' + kitRepo; + var gitRepo = kitOwner + "/" + kitRepo; var kitFunctions; var runtimeConfig; - return kits.prepareKitsUpload.retrieveFile(githubConfig).then(function(result) { - var kitConfig = JSON.parse(result); - kitFunctions = kitConfig.functions; + return kits.prepareKitsUpload + .retrieveFile(githubConfig) + .then(function(result) { + var kitConfig = JSON.parse(result); + kitFunctions = kitConfig.functions; - utils.logSuccess(chalk.green.bold('kits: ') + 'Fetched configuration file from ' + chalk.bold(gitRepo)); - utils.logBullet(chalk.bold('We will now ask a series of questions to help set up your kit.')); + utils.logSuccess( + chalk.green.bold("kits: ") + "Fetched configuration file from " + chalk.bold(gitRepo) + ); + utils.logBullet( + chalk.bold("We will now ask a series of questions to help set up your kit.") + ); - return kits.prepareKitsConfig.prompt(githubConfig.repo, kitConfig.config); - }).then(function(result) { - runtimeConfig = result; + return kits.prepareKitsConfig.prompt(githubConfig.repo, kitConfig.config); + }) + .then(function(result) { + runtimeConfig = result; - return kits.prepareKitsUpload.upload(projectId, githubConfig, runtimeConfig); - }).then(function(sourceUploadUrl) { - utils.logSuccess(chalk.green.bold('kits: ') + 'Completed configuration setup.'); - logger.debug(chalk.bold('kits: ') + 'Source uploaded to GCS bucket'); - utils.logBullet('Deploying kit ' + gitRepo + ' as ' + chalk.bold(runtimeConfig.kitname + '...')); + return kits.prepareKitsUpload.upload(projectId, githubConfig, runtimeConfig); + }) + .then(function(sourceUploadUrl) { + utils.logSuccess(chalk.green.bold("kits: ") + "Completed configuration setup."); + logger.debug(chalk.bold("kits: ") + "Source uploaded to GCS bucket"); + utils.logBullet( + "Deploying kit " + gitRepo + " as " + chalk.bold(runtimeConfig.kitname + "...") + ); - return kits.deploy(kitFunctions, options, runtimeConfig, sourceUploadUrl); - }); + return kits.deploy(kitFunctions, options, runtimeConfig, sourceUploadUrl); + }); }); diff --git a/commands/kits-uninstall.js b/commands/kits-uninstall.js index 0cfabf0b..359209b6 100644 --- a/commands/kits-uninstall.js +++ b/commands/kits-uninstall.js @@ -1,103 +1,135 @@ -'use strict'; -var chalk = require('chalk'); -var _ = require('lodash'); -var RSVP = require('rsvp'); +"use strict"; +var chalk = require("chalk"); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var Command = require('../lib/command'); -var gcp = require('../lib/gcp'); -var pollKits = require('../lib/kits/pollKits'); -var getProjectId = require('../lib/getProjectId'); -var prompt = require('../lib/prompt'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var gcp = require("../lib/gcp"); +var pollKits = require("../lib/kits/pollKits"); +var getProjectId = require("../lib/getProjectId"); +var prompt = require("../lib/prompt"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); +var utils = require("../lib/utils"); var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION; function _getFunctions(dict, kitName) { - return _.reduce(dict[kitName], function(funcs, func) { - return _.concat(funcs, func.functions); - }, []); + return _.reduce( + dict[kitName], + function(funcs, func) { + return _.concat(funcs, func.functions); + }, + [] + ); } function _listKits(projectId) { return gcp.cloudfunctions.list(projectId, DEFAULT_REGION).then(function(functions) { return _.chain(functions) - .filter(function(func) { return _.has(func, 'labels.goog-kit-name'); }) - .map(function(funcInfo) { - return { - 'kit': funcInfo.labels['goog-kit-name'], - 'source': funcInfo.labels['goog-kit-source'], - 'functions': funcInfo.functionName - }; - }) - .groupBy('kit') - .value(); + .filter(function(func) { + return _.has(func, "labels.goog-kit-name"); + }) + .map(function(funcInfo) { + return { + kit: funcInfo.labels["goog-kit-name"], + source: funcInfo.labels["goog-kit-source"], + functions: funcInfo.functionName, + }; + }) + .groupBy("kit") + .value(); }); } function _promptForKitsUninstall(choices, dict) { - return prompt({}, [{ - type: 'checkbox', - name: 'kitNames', - message: 'Which kits would you like to delete? ' + - 'The source of each kit is listed after the kit name.', - choices: prompt.convertLabeledListChoices(choices) - }]).then(function(list) { + return prompt({}, [ + { + type: "checkbox", + name: "kitNames", + message: + "Which kits would you like to delete? " + + "The source of each kit is listed after the kit name.", + choices: prompt.convertLabeledListChoices(choices), + }, + ]).then(function(list) { if (_.isEmpty(list.kitNames)) { - return utils.reject('Please select at least one kit to delete', {exit: 1}); + return utils.reject("Please select at least one kit to delete", { + exit: 1, + }); } return _.chain(list.kitNames) - .map(function(key) { return prompt.listLabelToValue(key, choices); }) - .map(function(kit) { return _getFunctions(dict, kit); }) - .value(); + .map(function(key) { + return prompt.listLabelToValue(key, choices); + }) + .map(function(kit) { + return _getFunctions(dict, kit); + }) + .value(); }); } function _deleteKitFunctions(projectId, functions) { - return RSVP.all(_.map(functions, function(funcName) { - return gcp.cloudfunctions.delete({ - projectId: projectId, - region: DEFAULT_REGION, - functionName: funcName - }); - })); + return RSVP.all( + _.map(functions, function(funcName) { + return gcp.cloudfunctions.delete({ + projectId: projectId, + region: DEFAULT_REGION, + functionName: funcName, + }); + }) + ); } -module.exports = new Command('kits:uninstall [kitName]') - .description('Command to uninstall function kit') +module.exports = new Command("kits:uninstall [kitName]") + .description("Command to uninstall function kit") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(function(kitName, options) { var projectId = getProjectId(options); - return _listKits(projectId).then(function(dict) { - if (_.isEmpty(dict)) { - return utils.reject('There are no kits asssociated with your project.', {exit: 1}); - } - - if (kitName) { - if (!dict[kitName]) { - return utils.reject('Could not find kit named ' + chalk.bold(kitName), {exit: 1}); + return _listKits(projectId) + .then(function(dict) { + if (_.isEmpty(dict)) { + return utils.reject("There are no kits asssociated with your project.", { exit: 1 }); } - return _getFunctions(dict, kitName); - } - var choices = _.map(dict, function(kit, key) { - return {name: key, label: key + ': ' + kit[0].source, checked: false}; + + if (kitName) { + if (!dict[kitName]) { + return utils.reject("Could not find kit named " + chalk.bold(kitName), { exit: 1 }); + } + return _getFunctions(dict, kitName); + } + var choices = _.map(dict, function(kit, key) { + return { + name: key, + label: key + ": " + kit[0].source, + checked: false, + }; + }); + return _promptForKitsUninstall(choices, dict); + }) + .then(function(funcsToDelete) { + utils.logBullet(chalk.cyan.bold("kits: ") + "Deleting kits now..."); + return _deleteKitFunctions(projectId, _.flatten(funcsToDelete)); + }) + .then(function(operations) { + utils.logBullet( + chalk.cyan.bold("kits: ") + "Checking to make sure kits have been deleted safely..." + ); + + var printSuccess = function(kits) { + return utils.logSuccess( + chalk.green.bold("kits: ") + + "Successfully deleted the following kit(s): " + + chalk.bold(_.uniq(kits)) + ); + }; + + var printFail = function(reason) { + return utils.logWarning( + chalk.yellow.bold("kits: ") + "Failed to delete the following kit: " + reason + ); + }; + + return pollKits(operations, printSuccess, printFail); }); - return _promptForKitsUninstall(choices, dict); - }).then(function(funcsToDelete) { - utils.logBullet(chalk.cyan.bold('kits: ') + 'Deleting kits now...'); - return _deleteKitFunctions(projectId, _.flatten(funcsToDelete)); - }).then(function(operations) { - utils.logBullet(chalk.cyan.bold('kits: ') + 'Checking to make sure kits have been deleted safely...'); - - var printSuccess = function(kits) { - return utils.logSuccess(chalk.green.bold('kits: ') + 'Successfully deleted the following kit(s): ' + chalk.bold(_.uniq(kits))); - }; - - var printFail = function(reason) { - return utils.logWarning(chalk.yellow.bold('kits: ') + 'Failed to delete the following kit: ' + reason); - }; - - return pollKits(operations, printSuccess, printFail); - }); }); diff --git a/commands/list.js b/commands/list.js index 8caa4df0..ff374201 100644 --- a/commands/list.js +++ b/commands/list.js @@ -1,22 +1,22 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var api = require('../lib/api'); -var requireAuth = require('../lib/requireAuth'); -var chalk = require('chalk'); -var Table = require('cli-table'); -var _ = require('lodash'); -var logger = require('../lib/logger'); +var Command = require("../lib/command"); +var api = require("../lib/api"); +var requireAuth = require("../lib/requireAuth"); +var chalk = require("chalk"); +var Table = require("cli-table"); +var _ = require("lodash"); +var logger = require("../lib/logger"); -module.exports = new Command('list') - .description('list the Firebase projects you have access to') +module.exports = new Command("list") + .description("list the Firebase projects you have access to") .before(requireAuth) .action(function(options) { return api.getProjects().then(function(projects) { - var tableHead = ['Name', 'Project ID / Instance', 'Permissions']; + var tableHead = ["Name", "Project ID / Instance", "Permissions"]; var table = new Table({ head: tableHead, - style: {head: ['yellow']} + style: { head: ["yellow"] }, }); var out = []; @@ -25,48 +25,46 @@ module.exports = new Command('list') name: data.name, id: projectId, permission: data.permission, - instance: data.instances.database[0] + instance: data.instances.database[0], }; var displayId = chalk.bold(projectId); if (data.instances.database[0] !== projectId) { - displayId += '\n' + data.instances.database[0] + ' (instance)'; + displayId += "\n" + data.instances.database[0] + " (instance)"; } var displayPermission; switch (data.permission) { - case 'own': - displayPermission = chalk.cyan.bold('Owner'); - break; - case 'edit': - displayPermission = chalk.bold('Editor'); - break; - case 'view': - default: - displayPermission = 'Viewer'; + case "own": + displayPermission = chalk.cyan.bold("Owner"); + break; + case "edit": + displayPermission = chalk.bold("Editor"); + break; + case "view": + default: + displayPermission = "Viewer"; } var displayName = data.name; if (options.project === projectId) { - displayName = chalk.cyan.bold(displayName + ' (current)'); + displayName = chalk.cyan.bold(displayName + " (current)"); } out.push(project); - var row = [ - displayName, - displayId, - displayPermission - ]; + var row = [displayName, displayId, displayPermission]; table.push(row); }); if (_.size(projects) === 0) { - logger.info(chalk.bold('No projects found.')); + logger.info(chalk.bold("No projects found.")); logger.info(); logger.info( - chalk.bold.cyan('Projects missing?') + ' This version of the Firebase CLI is only compatible with\n' + - 'projects that have been upgraded to the new Firebase Console. To access your\n' + - 'firebase.com apps, use a previous version: ' + chalk.bold('npm install -g firebase-tools@^2.1') + chalk.bold.cyan("Projects missing?") + + " This version of the Firebase CLI is only compatible with\n" + + "projects that have been upgraded to the new Firebase Console. To access your\n" + + "firebase.com apps, use a previous version: " + + chalk.bold("npm install -g firebase-tools@^2.1") ); } else { logger.info(table.toString()); diff --git a/commands/login-ci.js b/commands/login-ci.js index 717a5194..fe672504 100644 --- a/commands/login-ci.js +++ b/commands/login-ci.js @@ -1,23 +1,31 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var chalk = require('chalk'); -var utils = require('../lib/utils'); -var logger = require('../lib/logger'); -var auth = require('../lib/auth'); +var Command = require("../lib/command"); +var chalk = require("chalk"); +var utils = require("../lib/utils"); +var logger = require("../lib/logger"); +var auth = require("../lib/auth"); -module.exports = new Command('login:ci') - .description('generate an access token for use in non-interactive environments') - .option('--no-localhost', 'copy and paste a code instead of starting a local server for authentication') +module.exports = new Command("login:ci") + .description("generate an access token for use in non-interactive environments") + .option( + "--no-localhost", + "copy and paste a code instead of starting a local server for authentication" + ) .action(function(options) { if (options.nonInteractive) { - return utils.reject('Cannot run login:ci in non-interactive mode.', {exit: 1}); + return utils.reject("Cannot run login:ci in non-interactive mode.", { + exit: 1, + }); } return auth.login(options.localhost).then(function(result) { logger.info(); - utils.logSuccess('Success! Use this token to login on a CI server:\n\n' + - chalk.bold(result.tokens.refresh_token) + '\n\nExample: firebase deploy --token "$FIREBASE_TOKEN"\n'); + utils.logSuccess( + "Success! Use this token to login on a CI server:\n\n" + + chalk.bold(result.tokens.refresh_token) + + '\n\nExample: firebase deploy --token "$FIREBASE_TOKEN"\n' + ); return result; }); }); diff --git a/commands/login.js b/commands/login.js index cec602e5..fddd9aa9 100644 --- a/commands/login.js +++ b/commands/login.js @@ -1,50 +1,61 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var logger = require('../lib/logger'); -var configstore = require('../lib/configstore'); -var chalk = require('chalk'); -var utils = require('../lib/utils'); -var prompt = require('../lib/prompt'); -var RSVP = require('rsvp'); -var auth = require('../lib/auth'); +var Command = require("../lib/command"); +var logger = require("../lib/logger"); +var configstore = require("../lib/configstore"); +var chalk = require("chalk"); +var utils = require("../lib/utils"); +var prompt = require("../lib/prompt"); +var RSVP = require("rsvp"); +var auth = require("../lib/auth"); -module.exports = new Command('login') - .description('log the CLI into Firebase') - .option('--no-localhost', 'copy and paste a code instead of starting a local server for authentication') - .option('--reauth', 'force reauthentication even if already logged in') +module.exports = new Command("login") + .description("log the CLI into Firebase") + .option( + "--no-localhost", + "copy and paste a code instead of starting a local server for authentication" + ) + .option("--reauth", "force reauthentication even if already logged in") .action(function(options) { if (options.nonInteractive) { - return utils.reject('Cannot run login in non-interactive mode. See ' + - chalk.bold('login:ci') + ' to generate a token for use in non-interactive environments.', {exit: 1}); + return utils.reject( + "Cannot run login in non-interactive mode. See " + + chalk.bold("login:ci") + + " to generate a token for use in non-interactive environments.", + { exit: 1 } + ); } - var user = configstore.get('user'); - var tokens = configstore.get('tokens'); + var user = configstore.get("user"); + var tokens = configstore.get("tokens"); if (user && tokens && !options.reauth) { - logger.info('Already logged in as', chalk.bold(user.email)); + logger.info("Already logged in as", chalk.bold(user.email)); return RSVP.resolve(user); } - return prompt(options, [{ - type: 'confirm', - name: 'collectUsage', - message: 'Allow Firebase to collect anonymous CLI usage and error reporting information?' - }]).then(function() { - configstore.set('usage', options.collectUsage); - return auth.login(options.localhost); - }).then(function(result) { - configstore.set('user', result.user); - configstore.set('tokens', result.tokens); - // store login scopes in case mandatory scopes grow over time - configstore.set('loginScopes', result.scopes); - // remove old session token, if it exists - configstore.del('session'); + return prompt(options, [ + { + type: "confirm", + name: "collectUsage", + message: "Allow Firebase to collect anonymous CLI usage and error reporting information?", + }, + ]) + .then(function() { + configstore.set("usage", options.collectUsage); + return auth.login(options.localhost); + }) + .then(function(result) { + configstore.set("user", result.user); + configstore.set("tokens", result.tokens); + // store login scopes in case mandatory scopes grow over time + configstore.set("loginScopes", result.scopes); + // remove old session token, if it exists + configstore.del("session"); - logger.info(); - utils.logSuccess('Success! Logged in as ' + chalk.bold(result.user.email)); + logger.info(); + utils.logSuccess("Success! Logged in as " + chalk.bold(result.user.email)); - return auth; - }); + return auth; + }); }); diff --git a/commands/logout.js b/commands/logout.js index 502f17de..a50c0e30 100644 --- a/commands/logout.js +++ b/commands/logout.js @@ -1,22 +1,22 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var configstore = require('../lib/configstore'); -var logger = require('../lib/logger'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); -var utils = require('../lib/utils'); -var api = require('../lib/api'); -var auth = require('../lib/auth'); -var _ = require('lodash'); +var Command = require("../lib/command"); +var configstore = require("../lib/configstore"); +var logger = require("../lib/logger"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); +var utils = require("../lib/utils"); +var api = require("../lib/api"); +var auth = require("../lib/auth"); +var _ = require("lodash"); -module.exports = new Command('logout') - .description('log the CLI out of Firebase') +module.exports = new Command("logout") + .description("log the CLI out of Firebase") .action(function(options) { - var user = configstore.get('user'); - var tokens = configstore.get('tokens'); - var currentToken = _.get(tokens, 'refresh_token'); - var token = utils.getInheritedOption(options, 'token') || currentToken; + var user = configstore.get("user"); + var tokens = configstore.get("tokens"); + var currentToken = _.get(tokens, "refresh_token"); + var token = utils.getInheritedOption(options, "token") || currentToken; api.setRefreshToken(token); var next; if (token) { @@ -27,22 +27,22 @@ module.exports = new Command('logout') var cleanup = function() { if (token || user || tokens) { - var msg = 'Logged out'; + var msg = "Logged out"; if (token === currentToken) { if (user) { - msg += ' from ' + chalk.bold(user.email); + msg += " from " + chalk.bold(user.email); } } else { msg += ' token "' + chalk.bold(token) + '"'; } utils.logSuccess(msg); } else { - logger.info('No need to logout, not logged in'); + logger.info("No need to logout, not logged in"); } }; return next.then(cleanup, function() { - utils.logWarning('Invalid refresh token, did not need to deauthorize'); + utils.logWarning("Invalid refresh token, did not need to deauthorize"); cleanup(); }); }); diff --git a/commands/open.js b/commands/open.js index 2c4023b1..87cc3e46 100644 --- a/commands/open.js +++ b/commands/open.js @@ -1,58 +1,86 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var open = require('open'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var open = require("open"); +var RSVP = require("rsvp"); -var api = require('../lib/api'); -var Command = require('../lib/command'); -var logger = require('../lib/logger'); -var prompt = require('../lib/prompt'); -var requireAccess = require('../lib/requireAccess'); -var utils = require('../lib/utils'); +var api = require("../lib/api"); +var Command = require("../lib/command"); +var logger = require("../lib/logger"); +var prompt = require("../lib/prompt"); +var requireAccess = require("../lib/requireAccess"); +var utils = require("../lib/utils"); var LINKS = [ - {name: 'Project Dashboard', arg: 'dashboard', consoleUrl: '/overview'}, - {name: 'Analytics', arg: 'analytics', consoleUrl: '/analytics'}, - {name: 'Database: Data', arg: 'database', consoleUrl: '/database/data'}, - {name: 'Database: Rules', arg: 'database:rules', consoleUrl: '/database/rules'}, - {name: 'Authentication: Providers', arg: 'auth', consoleUrl: '/authentication/providers'}, - {name: 'Authentication: Users', arg: 'auth:users', consoleUrl: '/authentication/users'}, - {name: 'Storage: Files', arg: 'storage', consoleUrl: '/storage/files'}, - {name: 'Storage: Rules', arg: 'storage:rules', consoleUrl: '/storage/rules'}, - {name: 'Hosting', arg: 'hosting', consoleUrl: '/hosting/main'}, - {name: 'Hosting: Deployed Site', arg: 'hosting:site'}, - {name: 'Remote Config', arg: 'config', consoleUrl: '/config'}, - {name: 'Remote Config: Conditions', arg: 'config:conditions', consoleUrl: '/config/conditions'}, - {name: 'Test Lab', arg: 'testlab', consoleUrl: '/testlab/histories/'}, - {name: 'Crash Reporting', arg: 'crash', consoleUrl: '/monitoring'}, - {name: 'Notifications', arg: 'notifications', consoleUrl: '/notification'}, - {name: 'Dynamic Links', arg: 'links', consoleUrl: '/durablelinks'}, - {name: 'Project Settings', arg: 'settings', consoleUrl: '/settings/general'}, - {name: 'Docs', arg: 'docs', url: 'https://firebase.google.com/docs'} + { name: "Project Dashboard", arg: "dashboard", consoleUrl: "/overview" }, + { name: "Analytics", arg: "analytics", consoleUrl: "/analytics" }, + { name: "Database: Data", arg: "database", consoleUrl: "/database/data" }, + { + name: "Database: Rules", + arg: "database:rules", + consoleUrl: "/database/rules", + }, + { + name: "Authentication: Providers", + arg: "auth", + consoleUrl: "/authentication/providers", + }, + { + name: "Authentication: Users", + arg: "auth:users", + consoleUrl: "/authentication/users", + }, + { name: "Storage: Files", arg: "storage", consoleUrl: "/storage/files" }, + { + name: "Storage: Rules", + arg: "storage:rules", + consoleUrl: "/storage/rules", + }, + { name: "Hosting", arg: "hosting", consoleUrl: "/hosting/main" }, + { name: "Hosting: Deployed Site", arg: "hosting:site" }, + { name: "Remote Config", arg: "config", consoleUrl: "/config" }, + { + name: "Remote Config: Conditions", + arg: "config:conditions", + consoleUrl: "/config/conditions", + }, + { name: "Test Lab", arg: "testlab", consoleUrl: "/testlab/histories/" }, + { name: "Crash Reporting", arg: "crash", consoleUrl: "/monitoring" }, + { name: "Notifications", arg: "notifications", consoleUrl: "/notification" }, + { name: "Dynamic Links", arg: "links", consoleUrl: "/durablelinks" }, + { + name: "Project Settings", + arg: "settings", + consoleUrl: "/settings/general", + }, + { name: "Docs", arg: "docs", url: "https://firebase.google.com/docs" }, ]; -var CHOICES = _.map(LINKS, 'name'); +var CHOICES = _.map(LINKS, "name"); -module.exports = new Command('open [link]') - .description('quickly open a browser to relevant project resources') +module.exports = new Command("open [link]") + .description("quickly open a browser to relevant project resources") .before(requireAccess) .action(function(linkName, options) { - var link = _.find(LINKS, {arg: linkName}); + var link = _.find(LINKS, { arg: linkName }); if (linkName && !link) { - return utils.reject('Unrecognized link name. Valid links are:\n\n' + _.map(LINKS, 'arg').join('\n')); + return utils.reject( + "Unrecognized link name. Valid links are:\n\n" + _.map(LINKS, "arg").join("\n") + ); } var next = RSVP.resolve(link); if (!link) { - next = prompt.once({ - type: 'list', - message: 'What link would you like to open?', - choices: CHOICES - }).then(function(result) { - return _.find(LINKS, {name: result}); - }); + next = prompt + .once({ + type: "list", + message: "What link would you like to open?", + choices: CHOICES, + }) + .then(function(result) { + return _.find(LINKS, { name: result }); + }); } return next.then(function(finalLink) { @@ -61,19 +89,25 @@ module.exports = new Command('open [link]') url = utils.consoleUrl(options.project, finalLink.consoleUrl); } else if (finalLink.url) { url = finalLink.url; - } else if (finalLink.arg === 'hosting:site') { + } else if (finalLink.arg === "hosting:site") { url = utils.addSubdomain(api.hostingOrigin, options.instance); - } else if (finalLink.arg === 'functions') { - url = 'https://console.firebase.google.com/project/' + options.project + '/functions/list'; - } else if (finalLink.arg === 'functions:log') { - url = 'https://console.developers.google.com/logs/viewer?resource=cloudfunctions.googleapis.com&project=' + options.project; + } else if (finalLink.arg === "functions") { + url = "https://console.firebase.google.com/project/" + options.project + "/functions/list"; + } else if (finalLink.arg === "functions:log") { + url = + "https://console.developers.google.com/logs/viewer?resource=cloudfunctions.googleapis.com&project=" + + options.project; } if (finalLink.arg !== linkName) { - logger.info(chalk.bold.cyan('Tip: ') + 'You can also run ' + chalk.bold.underline('firebase open ' + finalLink.arg)); + logger.info( + chalk.bold.cyan("Tip: ") + + "You can also run " + + chalk.bold.underline("firebase open " + finalLink.arg) + ); logger.info(); } - logger.info('Opening ' + chalk.bold(finalLink.name) + ' link in your default browser:'); + logger.info("Opening " + chalk.bold(finalLink.name) + " link in your default browser:"); logger.info(chalk.bold.underline(url)); open(url); diff --git a/commands/serve.js b/commands/serve.js index bb46be39..db9049de 100644 --- a/commands/serve.js +++ b/commands/serve.js @@ -1,26 +1,32 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); +var chalk = require("chalk"); -var Command = require('../lib/command'); -var logger = require('../lib/logger'); -var utils = require('../lib/utils'); -var requireAccess = require('../lib/requireAccess'); -var requireConfig = require('../lib/requireConfig'); -var checkDupHostingKeys = require('../lib/checkDupHostingKeys'); -var serve = require('../lib/serve/index'); -var scopes = require('../lib/scopes'); -var filterTargets = require('../lib/filterTargets'); -var getProjectNumber = require('../lib/getProjectNumber'); +var Command = require("../lib/command"); +var logger = require("../lib/logger"); +var utils = require("../lib/utils"); +var requireAccess = require("../lib/requireAccess"); +var requireConfig = require("../lib/requireConfig"); +var checkDupHostingKeys = require("../lib/checkDupHostingKeys"); +var serve = require("../lib/serve/index"); +var scopes = require("../lib/scopes"); +var filterTargets = require("../lib/filterTargets"); +var getProjectNumber = require("../lib/getProjectNumber"); -var VALID_TARGETS = ['functions', 'hosting']; +var VALID_TARGETS = ["functions", "hosting"]; -module.exports = new Command('serve') - .description('start a local server for your static assets') - .option('-p, --port ', 'the port on which to listen (default: 5000)', 5000) - .option('-o, --host ', 'the host on which to listen (default: localhost)', 'localhost') - .option('--only ', 'only serve specified targets (valid targets are: functions, hosting)') - .option('--except ', 'serve all except specified targets (valid targets are: functions, hosting)') +module.exports = new Command("serve") + .description("start a local server for your static assets") + .option("-p, --port ", "the port on which to listen (default: 5000)", 5000) + .option("-o, --host ", "the host on which to listen (default: localhost)", "localhost") + .option( + "--only ", + "only serve specified targets (valid targets are: functions, hosting)" + ) + .option( + "--except ", + "serve all except specified targets (valid targets are: functions, hosting)" + ) .before(requireConfig) .before(requireAccess, [scopes.CLOUD_PLATFORM]) .before(checkDupHostingKeys) @@ -28,10 +34,15 @@ module.exports = new Command('serve') .action(function(options) { if (options.config) { logger.info(); - logger.info(chalk.bold(chalk.gray('===') + ' Serving from \'' + options.config.projectDir + '\'...')); + logger.info( + chalk.bold(chalk.gray("===") + " Serving from '" + options.config.projectDir + "'...") + ); logger.info(); } else { - utils.logWarning('No Firebase project directory detected. Serving static content from ' + chalk.bold(options.cwd || process.cwd())); + utils.logWarning( + "No Firebase project directory detected. Serving static content from " + + chalk.bold(options.cwd || process.cwd()) + ); } options.targets = filterTargets(options, VALID_TARGETS); return serve(options); diff --git a/commands/setup-web.js b/commands/setup-web.js index 72c5ea92..7f705d12 100644 --- a/commands/setup-web.js +++ b/commands/setup-web.js @@ -1,22 +1,22 @@ -'use strict'; +"use strict"; -var fs = require('fs'); -var RSVP = require('rsvp'); +var fs = require("fs"); +var RSVP = require("rsvp"); -var Command = require('../lib/command'); -var fetchWebSetup = require('../lib/fetchWebSetup'); -var logger = require('../lib/logger'); -var requireAccess = require('../lib/requireAccess'); -var scopes = require('../lib/scopes'); +var Command = require("../lib/command"); +var fetchWebSetup = require("../lib/fetchWebSetup"); +var logger = require("../lib/logger"); +var requireAccess = require("../lib/requireAccess"); +var scopes = require("../lib/scopes"); -var JS_TEMPLATE = fs.readFileSync(__dirname + '/../templates/setup/web.js', 'utf8'); +var JS_TEMPLATE = fs.readFileSync(__dirname + "/../templates/setup/web.js", "utf8"); -module.exports = new Command('setup:web') - .description('display this project\'s setup information for the Firebase JS SDK') +module.exports = new Command("setup:web") + .description("display this project's setup information for the Firebase JS SDK") .before(requireAccess, [scopes.CLOUD_PLATFORM]) .action(function(options) { return fetchWebSetup(options).then(function(config) { - logger.info(JS_TEMPLATE.replace('{/*--CONFIG--*/}', JSON.stringify(config, null, 2))); + logger.info(JS_TEMPLATE.replace("{/*--CONFIG--*/}", JSON.stringify(config, null, 2))); return RSVP.resolve(config); }); }); diff --git a/commands/target-apply.js b/commands/target-apply.js index f65082fb..7bdc7474 100644 --- a/commands/target-apply.js +++ b/commands/target-apply.js @@ -1,23 +1,32 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); +var _ = require("lodash"); +var chalk = require("chalk"); -var Command = require('../lib/command'); -var logger = require('../lib/logger'); -var requireConfig = require('../lib/requireConfig'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var logger = require("../lib/logger"); +var requireConfig = require("../lib/requireConfig"); +var utils = require("../lib/utils"); -module.exports = new Command('target:apply ') - .description('apply a deploy target to a resource') +module.exports = new Command("target:apply ") + .description("apply a deploy target to a resource") .before(requireConfig) .action(function(type, name, resources, options) { var changes = options.rc.applyTarget(options.project, type, name, resources); - utils.logSuccess('Applied ' + type + ' target ' + chalk.bold(name) + ' to ' + chalk.bold(resources.join(', '))); + utils.logSuccess( + "Applied " + type + " target " + chalk.bold(name) + " to " + chalk.bold(resources.join(", ")) + ); _.forEach(changes, function(change) { - utils.logWarning('Previous target ' + chalk.bold(change.target) + ' removed from ' + chalk.bold(change.resource)); + utils.logWarning( + "Previous target " + + chalk.bold(change.target) + + " removed from " + + chalk.bold(change.resource) + ); }); logger.info(); - logger.info('Updated: ' + name + ' (' + options.rc.target(options.project, type, name).join(',') + ')'); + logger.info( + "Updated: " + name + " (" + options.rc.target(options.project, type, name).join(",") + ")" + ); }); diff --git a/commands/target-clear.js b/commands/target-clear.js index c37279d3..27f597fb 100644 --- a/commands/target-clear.js +++ b/commands/target-clear.js @@ -1,21 +1,21 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var Command = require('../lib/command'); -var requireConfig = require('../lib/requireConfig'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var requireConfig = require("../lib/requireConfig"); +var utils = require("../lib/utils"); -module.exports = new Command('target:clear ') - .description('clear all resources from a named resource target') +module.exports = new Command("target:clear ") + .description("clear all resources from a named resource target") .before(requireConfig) .action(function(type, name, options) { var existed = options.rc.clearTarget(options.project, type, name); if (existed) { - utils.logSuccess('Cleared ' + type + ' target ' + chalk.bold(name)); + utils.logSuccess("Cleared " + type + " target " + chalk.bold(name)); } else { - utils.logWarning('No action taken. No ' + type + ' target found named ' + chalk.bold(name)); + utils.logWarning("No action taken. No " + type + " target found named " + chalk.bold(name)); } return RSVP.resolve(existed); }); diff --git a/commands/target-remove.js b/commands/target-remove.js index 2b29d2ae..349e7892 100644 --- a/commands/target-remove.js +++ b/commands/target-remove.js @@ -1,21 +1,25 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var Command = require('../lib/command'); -var requireConfig = require('../lib/requireConfig'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var requireConfig = require("../lib/requireConfig"); +var utils = require("../lib/utils"); -module.exports = new Command('target:remove ') - .description('remove a resource target') +module.exports = new Command("target:remove ") + .description("remove a resource target") .before(requireConfig) .action(function(type, resource, options) { var name = options.rc.removeTarget(options.project, type, resource); if (name) { - utils.logSuccess('Removed ' + type + ' target ' + chalk.bold(name) + ' from ' + chalk.bold(resource)); + utils.logSuccess( + "Removed " + type + " target " + chalk.bold(name) + " from " + chalk.bold(resource) + ); } else { - utils.logWarning('No action taken. No target found for ' + type + ' resource ' + chalk.bold(resource)); + utils.logWarning( + "No action taken. No target found for " + type + " resource " + chalk.bold(resource) + ); } return RSVP.resolve(name); }); diff --git a/commands/target.js b/commands/target.js index ff3b7d62..12614af5 100644 --- a/commands/target.js +++ b/commands/target.js @@ -1,30 +1,30 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var Command = require('../lib/command'); -var logger = require('../lib/logger'); -var requireConfig = require('../lib/requireConfig'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var logger = require("../lib/logger"); +var requireConfig = require("../lib/requireConfig"); +var utils = require("../lib/utils"); function _logTargets(type, targets) { - logger.info(chalk.cyan('[ ' + type + ' ]')); + logger.info(chalk.cyan("[ " + type + " ]")); _.forEach(targets, function(resources, name) { - logger.info(name, '(' + (resources || []).join(',') + ')'); + logger.info(name, "(" + (resources || []).join(",") + ")"); }); } -module.exports = new Command('target [type]') - .description('display configured deploy targets for the current project') +module.exports = new Command("target [type]") + .description("display configured deploy targets for the current project") .before(requireConfig) .action(function(type, options) { if (!options.project) { - return utils.error('No active project, cannot list deploy targets.'); + return utils.error("No active project, cannot list deploy targets."); } - logger.info('Resource targets for', chalk.bold(options.project) + ':'); + logger.info("Resource targets for", chalk.bold(options.project) + ":"); logger.info(); if (type) { var targets = options.rc.targets(options.project, type); @@ -32,7 +32,7 @@ module.exports = new Command('target [type]') return RSVP.resolve(targets); } - var allTargets = options.rc.get(['targets', options.project], {}); + var allTargets = options.rc.get(["targets", options.project], {}); _.forEach(allTargets, function(ts, tp) { _logTargets(tp, ts); }); diff --git a/commands/tools-migrate.js b/commands/tools-migrate.js index ee0a69fc..b8cbf96b 100644 --- a/commands/tools-migrate.js +++ b/commands/tools-migrate.js @@ -1,34 +1,36 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var Command = require('../lib/command'); -var Config = require('../lib/config'); -var identifierToProjectId = require('../lib/identifierToProjectId'); -var logger = require('../lib/logger'); -var prompt = require('../lib/prompt'); -var requireAuth = require('../lib/requireAuth'); -var utils = require('../lib/utils'); +var Command = require("../lib/command"); +var Config = require("../lib/config"); +var identifierToProjectId = require("../lib/identifierToProjectId"); +var logger = require("../lib/logger"); +var prompt = require("../lib/prompt"); +var requireAuth = require("../lib/requireAuth"); +var utils = require("../lib/utils"); var MOVE_KEYS = { - rules: 'database.rules' + rules: "database.rules", }; Config.LEGACY_HOSTING_KEYS.forEach(function(key) { - MOVE_KEYS[key] = 'hosting.' + key; + MOVE_KEYS[key] = "hosting." + key; }); -module.exports = new Command('tools:migrate') - .description('ensure your firebase.json format is up to date') - .option('-y, --confirm', 'pass this option to bypass confirmation prompt') +module.exports = new Command("tools:migrate") + .description("ensure your firebase.json format is up to date") + .option("-y, --confirm", "pass this option to bypass confirmation prompt") .before(requireAuth) .action(function(options) { if (!options.config) { - return utils.reject('Must run ' + chalk.bold('tools:migrate') + ' from a directory with a firebase.json'); + return utils.reject( + "Must run " + chalk.bold("tools:migrate") + " from a directory with a firebase.json" + ); } - utils.logBullet('Checking feature configuration...'); + utils.logBullet("Checking feature configuration..."); var out = _.cloneDeep(options.config._src); var changed = false; @@ -49,13 +51,17 @@ module.exports = new Command('tools:migrate') next = identifierToProjectId(instance).then(function(result) { projectId = result; if (projectId) { - rcfile = {projects: {default: projectId}}; - _.unset(out, 'firebase'); + rcfile = { projects: { default: projectId } }; + _.unset(out, "firebase"); } else { - return utils.reject('Could not find Firebase project corresponding to ' + chalk.bold(instance) + '.\nPlease ensure it has been migrated to the new console before proceeding.'); + return utils.reject( + "Could not find Firebase project corresponding to " + + chalk.bold(instance) + + ".\nPlease ensure it has been migrated to the new console before proceeding." + ); } }); - rcfile = {projects: {default: instance}}; + rcfile = { projects: { default: instance } }; changed = true; } else { @@ -65,12 +71,12 @@ module.exports = new Command('tools:migrate') return next.then(function() { if (!changed) { logger.info(); - utils.logSuccess('No action required, your firebase.json is all up to date!'); + utils.logSuccess("No action required, your firebase.json is all up to date!"); return true; } logger.info(); - logger.info(chalk.gray.bold('# preview: updated contents of firebase.json')); + logger.info(chalk.gray.bold("# preview: updated contents of firebase.json")); logger.info(); logger.info(JSON.stringify(out, null, 2)); logger.info(); @@ -79,23 +85,23 @@ module.exports = new Command('tools:migrate') next = RSVP.resolve(true); } else { next = prompt.once({ - type: 'confirm', - message: 'Write new config to ' + chalk.underline('firebase.json') + '?', - default: true + type: "confirm", + message: "Write new config to " + chalk.underline("firebase.json") + "?", + default: true, }); } return next.then(function(confirmed) { if (confirmed) { - options.config.writeProjectFile('firebase.json', out); - utils.logSuccess('Migrated ' + chalk.bold('firebase.json') + ' successfully'); + options.config.writeProjectFile("firebase.json", out); + utils.logSuccess("Migrated " + chalk.bold("firebase.json") + " successfully"); if (projectId) { - options.config.writeProjectFile('.firebaserc', rcfile); + options.config.writeProjectFile(".firebaserc", rcfile); utils.makeActiveProject(options.projectRoot, projectId); - utils.logSuccess('Set default project to ' + chalk.bold(projectId)); + utils.logSuccess("Set default project to " + chalk.bold(projectId)); } } else { - return utils.reject('Migration aborted by user.', {exit: 1}); + return utils.reject("Migration aborted by user.", { exit: 1 }); } }); }); diff --git a/commands/use.js b/commands/use.js index c3c34388..48c7d19c 100644 --- a/commands/use.js +++ b/commands/use.js @@ -1,142 +1,171 @@ -'use strict'; +"use strict"; -var Command = require('../lib/command'); -var logger = require('../lib/logger'); -var requireAuth = require('../lib/requireAuth'); -var api = require('../lib/api'); -var chalk = require('chalk'); -var utils = require('../lib/utils'); -var _ = require('lodash'); -var prompt = require('../lib/prompt'); +var Command = require("../lib/command"); +var logger = require("../lib/logger"); +var requireAuth = require("../lib/requireAuth"); +var api = require("../lib/api"); +var chalk = require("chalk"); +var utils = require("../lib/utils"); +var _ = require("lodash"); +var prompt = require("../lib/prompt"); var listAliases = function(options) { if (options.rc.hasProjects) { - logger.info('Project aliases for', chalk.bold(options.projectRoot) + ':'); + logger.info("Project aliases for", chalk.bold(options.projectRoot) + ":"); logger.info(); _.forEach(options.rc.projects, function(projectId, alias) { - var listing = alias + ' (' + projectId + ')'; + var listing = alias + " (" + projectId + ")"; if (options.project === projectId || options.projectAlias === alias) { - logger.info(chalk.cyan.bold('* ' + listing)); + logger.info(chalk.cyan.bold("* " + listing)); } else { - logger.info(' ' + listing); + logger.info(" " + listing); } }); logger.info(); } - logger.info('Run', chalk.bold('firebase use --add'), 'to define a new project alias.'); + logger.info("Run", chalk.bold("firebase use --add"), "to define a new project alias."); }; var verifyMessage = function(name) { - return 'please verify project ' + chalk.bold(name) + ' exists and you have access.'; + return "please verify project " + chalk.bold(name) + " exists and you have access."; }; -module.exports = new Command('use [alias_or_project_id]') - .description('set an active Firebase project for your working directory') - .option('--add', 'create a new project alias interactively') - .option('--alias ', 'create a new alias for the provided project id') - .option('--unalias ', 'remove an already created project alias') - .option('--clear', 'clear the active project selection') +module.exports = new Command("use [alias_or_project_id]") + .description("set an active Firebase project for your working directory") + .option("--add", "create a new project alias interactively") + .option("--alias ", "create a new alias for the provided project id") + .option("--unalias ", "remove an already created project alias") + .option("--clear", "clear the active project selection") .before(requireAuth) .action(function(newActive, options) { // HACK: Commander.js silently swallows an option called alias >_< var aliasOpt; - var i = process.argv.indexOf('--alias'); + var i = process.argv.indexOf("--alias"); if (i >= 0 && process.argv.length > i + 1) { aliasOpt = process.argv[i + 1]; } - if (!options.projectRoot) { // not in project directory - return utils.reject(chalk.bold('firebase use') + ' must be run from a Firebase project directory.\n\nRun ' + chalk.bold('firebase init') + ' to start a project directory in the current folder.'); + if (!options.projectRoot) { + // not in project directory + return utils.reject( + chalk.bold("firebase use") + + " must be run from a Firebase project directory.\n\nRun " + + chalk.bold("firebase init") + + " to start a project directory in the current folder." + ); } - if (newActive) { // firebase use [alias_or_project] - var aliasedProject = options.rc.get(['projects', newActive]); + if (newActive) { + // firebase use [alias_or_project] + var aliasedProject = options.rc.get(["projects", newActive]); return api.getProjects().then(function(projects) { - if (aliasOpt) { // firebase use [project] --alias [alias] + if (aliasOpt) { + // firebase use [project] --alias [alias] if (!projects[newActive]) { - return utils.reject('Cannot create alias ' + chalk.bold(aliasOpt) + ', ' + verifyMessage(newActive)); + return utils.reject( + "Cannot create alias " + chalk.bold(aliasOpt) + ", " + verifyMessage(newActive) + ); } options.rc.addProjectAlias(aliasOpt, newActive); aliasedProject = newActive; - logger.info('Created alias', chalk.bold(aliasOpt), 'for', aliasedProject + '.'); + logger.info("Created alias", chalk.bold(aliasOpt), "for", aliasedProject + "."); } - if (aliasedProject) { // found alias - if (!projects[aliasedProject]) { // found alias, but not in project list - return utils.reject('Unable to use alias ' + chalk.bold(newActive) + ', ' + verifyMessage(aliasedProject)); + if (aliasedProject) { + // found alias + if (!projects[aliasedProject]) { + // found alias, but not in project list + return utils.reject( + "Unable to use alias " + chalk.bold(newActive) + ", " + verifyMessage(aliasedProject) + ); } utils.makeActiveProject(options.projectRoot, newActive); - logger.info('Now using alias', chalk.bold(newActive), '(' + aliasedProject + ')'); - } else if (projects[newActive]) { // exact project id specified + logger.info("Now using alias", chalk.bold(newActive), "(" + aliasedProject + ")"); + } else if (projects[newActive]) { + // exact project id specified 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)); + logger.info("Now using project", chalk.bold(newActive)); + } else { + // no alias or project recognized + return utils.reject("Invalid project selection, " + verifyMessage(newActive)); } }); - } else if (options.unalias) { // firebase use --unalias [alias] - if (_.has(options.rc, ['projects', options.unalias])) { + } else if (options.unalias) { + // firebase use --unalias [alias] + if (_.has(options.rc, ["projects", options.unalias])) { options.rc.removeProjectAlias(options.unalias); - logger.info('Removed alias', chalk.bold(options.unalias)); + logger.info("Removed alias", chalk.bold(options.unalias)); logger.info(); listAliases(options); } - } else if (options.add) { // firebase use --add + } else if (options.add) { + // firebase use --add if (options.nonInteractive) { - return utils.reject('Cannot run ' + chalk.bold('firebase use --add') + ' in non-interactive mode. Use ' + chalk.bold('firebase use --alias ') + ' instead.'); + return utils.reject( + "Cannot run " + + chalk.bold("firebase use --add") + + " in non-interactive mode. Use " + + chalk.bold("firebase use --alias ") + + " instead." + ); } return api.getProjects().then(function(projects) { var results = {}; return prompt(results, [ { - type: 'list', - name: 'project', - message: 'Which project do you want to add?', - choices: Object.keys(projects).sort() + type: "list", + name: "project", + message: "Which project do you want to add?", + choices: Object.keys(projects).sort(), }, { - type: 'input', - name: 'alias', - message: 'What alias do you want to use for this project? (e.g. staging)', + type: "input", + name: "alias", + message: "What alias do you want to use for this project? (e.g. staging)", validate: function(input) { return input && input.length > 0; - } - } + }, + }, ]).then(function() { options.rc.addProjectAlias(results.alias, results.project); 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 + ')'); + 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 + } else if (options.clear) { + // firebase use --clear utils.makeActiveProject(options.projectRoot, null); options.projectAlias = null; options.project = null; - logger.info('Cleared active project.'); + logger.info("Cleared active project."); logger.info(); listAliases(options); - } else { // firebase use + } else { + // firebase use if (options.nonInteractive || !process.stdout.isTTY) { if (options.project) { logger.info(options.project); return options.project; } - return utils.reject('No active project', {exit: 1}); + return utils.reject("No active project", { exit: 1 }); } if (options.projectAlias) { - logger.info('Active Project:', chalk.bold.cyan(options.projectAlias + ' (' + options.project + ')')); + logger.info( + "Active Project:", + chalk.bold.cyan(options.projectAlias + " (" + options.project + ")") + ); } else if (options.project) { - logger.info('Active Project:', chalk.bold.cyan(options.project)); + logger.info("Active Project:", chalk.bold.cyan(options.project)); } else { - var msg = 'No project is currently active'; + var msg = "No project is currently active"; if (options.rc.hasProjects) { - msg += ', and no aliases have been created.'; + msg += ", and no aliases have been created."; } - logger.info(msg + '.'); + logger.info(msg + "."); } logger.info(); listAliases(options); diff --git a/gulpfile.js b/gulpfile.js index 4d497745..7e3cba5b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,70 +1,66 @@ /**************/ /* REQUIRES */ /**************/ -var gulp = require('gulp'); +var gulp = require("gulp"); // File I/O -var exit = require('gulp-exit'); -var eslint = require('gulp-eslint'); +var exit = require("gulp-exit"); +var eslint = require("gulp-eslint"); // Testing -var mocha = require('gulp-mocha'); -var istanbul = require('gulp-istanbul'); +var mocha = require("gulp-mocha"); +var istanbul = require("gulp-istanbul"); -var _ = require('lodash'); +var _ = require("lodash"); /****************/ /* FILE PATHS */ /****************/ var paths = { - js: [ - 'index.js', - 'lib/**/*.js', - 'commands/**/*.js' - ], + js: ["index.js", "lib/**/*.js", "commands/**/*.js"], - tests: [ - 'test/**/*.spec.js' - ], + tests: ["test/**/*.spec.js"], - scripts: [ - 'scripts/*.js' - ] + scripts: ["scripts/*.js"], }; - /***********/ /* TASKS */ /***********/ // Lints the JavaScript files -gulp.task('lint', function() { +gulp.task("lint", function() { var filesToLint = _.union(paths.js, paths.tests, paths.scripts); - return gulp.src(filesToLint) + return gulp + .src(filesToLint) .pipe(eslint()) .pipe(eslint.format()) .pipe(eslint.failAfterError()); }); // Runs the Mocha test suite -gulp.task('test', function() { - return gulp.src(paths.js) +gulp.task("test", function() { + return gulp + .src(paths.js) .pipe(istanbul()) .pipe(istanbul.hookRequire()) - .on('finish', function () { - gulp.src(paths.tests) - .pipe(mocha({ - reporter: 'spec', - timeout: 5000 - })) + .on("finish", function() { + gulp + .src(paths.tests) + .pipe( + mocha({ + reporter: "spec", + timeout: 5000, + }) + ) .pipe(istanbul.writeReports()) .pipe(exit()); }); }); // Reruns the linter every time a JavaScript file changes -gulp.task('watch', function() { - gulp.watch(paths.js, ['lint']); +gulp.task("watch", function() { + gulp.watch(paths.js, ["lint"]); }); // Default task -gulp.task('default', ['lint', 'test']); +gulp.task("default", ["lint", "test"]); diff --git a/index.js b/index.js index 2a26dc43..37ecc56c 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,28 @@ -'use strict'; +"use strict"; -var program = require('commander'); -var pkg = require('./package.json'); -var chalk = require('chalk'); -var logger = require('./lib/logger'); -var didYouMean = require('didyoumean'); +var program = require("commander"); +var pkg = require("./package.json"); +var chalk = require("chalk"); +var logger = require("./lib/logger"); +var didYouMean = require("didyoumean"); program.version(pkg.version); -program.option('-P, --project ', 'the Firebase project to use for this command'); -program.option('-j, --json', 'output JSON instead of text, also triggers non-interactive mode'); -program.option('--token ', 'supply an auth token for this command'); -program.option('--non-interactive', 'error out of the command instead of waiting for prompts'); -program.option('--interactive', 'force interactive shell treatment even when not detected'); -program.option('--debug', 'print verbose debug output and keep a debug log file'); +program.option( + "-P, --project ", + "the Firebase project to use for this command" +); +program.option("-j, --json", "output JSON instead of text, also triggers non-interactive mode"); +program.option("--token ", "supply an auth token for this command"); +program.option("--non-interactive", "error out of the command instead of waiting for prompts"); +program.option("--interactive", "force interactive shell treatment even when not detected"); +program.option("--debug", "print verbose debug output and keep a debug log file"); // program.option('-d, --debug', 'display debug information and keep firebase-debug.log'); var client = {}; client.cli = program; -client.logger = require('./lib/logger'); +client.logger = require("./lib/logger"); client.errorOut = function(error, status) { - require('./lib/errorOut')(client, error, status); + require("./lib/errorOut")(client, error, status); }; client.getCommand = function(name) { for (var i = 0; i < client.cli.commands.length; i++) { @@ -30,40 +33,41 @@ client.getCommand = function(name) { return null; }; -require('./commands')(client); +require("./commands")(client); var commandNames = program.commands.map(function(cmd) { return cmd._name; }); var RENAMED_COMMANDS = { - 'delete-site': 'hosting:disable', - 'disable:hosting': 'hosting:disable', - 'data:get': 'database:get', - 'data:push': 'database:push', - 'data:remove': 'database:remove', - 'data:set': 'database:set', - 'data:update': 'database:update', - 'deploy:hosting': 'deploy --only hosting', - 'deploy:database': 'deploy --only database', - 'prefs:token': 'login:ci' + "delete-site": "hosting:disable", + "disable:hosting": "hosting:disable", + "data:get": "database:get", + "data:push": "database:push", + "data:remove": "database:remove", + "data:set": "database:set", + "data:update": "database:update", + "deploy:hosting": "deploy --only hosting", + "deploy:database": "deploy --only database", + "prefs:token": "login:ci", }; program.action(function(cmd, cmd2) { - logger.error( - chalk.bold.red('Error:'), - chalk.bold(cmd), 'is not a Firebase command' - ); + logger.error(chalk.bold.red("Error:"), chalk.bold(cmd), "is not a Firebase command"); if (RENAMED_COMMANDS[cmd]) { logger.error(); - logger.error(chalk.bold(cmd) + ' has been renamed, please run', chalk.bold('firebase ' + RENAMED_COMMANDS[cmd]), 'instead'); + logger.error( + chalk.bold(cmd) + " has been renamed, please run", + chalk.bold("firebase " + RENAMED_COMMANDS[cmd]), + "instead" + ); } else { var suggestion = didYouMean(cmd, commandNames); - suggestion = suggestion || didYouMean([cmd, cmd2].join(':'), commandNames); + suggestion = suggestion || didYouMean([cmd, cmd2].join(":"), commandNames); if (suggestion) { logger.error(); - logger.error('Did you mean', chalk.bold(suggestion) + '?'); + logger.error("Did you mean", chalk.bold(suggestion) + "?"); } } diff --git a/lib/RulesDeploy.js b/lib/RulesDeploy.js index ec1f89db..98b63004 100644 --- a/lib/RulesDeploy.js +++ b/lib/RulesDeploy.js @@ -1,14 +1,14 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var fs = require('fs'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var fs = require("fs"); +var RSVP = require("rsvp"); -var gcp = require('./gcp'); -var logger = require('./logger'); -var FirebaseError = require('./error'); -var utils = require('./utils'); +var gcp = require("./gcp"); +var logger = require("./logger"); +var FirebaseError = require("./error"); +var utils = require("./utils"); function RulesDeploy(options, type) { this.type = type; @@ -27,13 +27,13 @@ RulesDeploy.prototype = { var fullPath = this.options.config.path(path); var src; try { - src = fs.readFileSync(fullPath, 'utf8'); + src = fs.readFileSync(fullPath, "utf8"); } catch (e) { - logger.debug('[rules read error]', e.stack); - throw new FirebaseError('Error reading rules file ' + chalk.bold(path)); + logger.debug("[rules read error]", e.stack); + throw new FirebaseError("Error reading rules file " + chalk.bold(path)); } - this.rulesFiles[path] = [{name: path, content: src}]; + this.rulesFiles[path] = [{ name: path, content: src }]; }, /** @@ -57,43 +57,69 @@ RulesDeploy.prototype = { var self = this; var promises = []; _.forEach(this.rulesFiles, function(files, filename) { - utils.logBullet(chalk.cyan(self.type + ':') + ' uploading rules ' + chalk.bold(filename) + '...'); - promises.push(gcp.rules.createRuleset(self.options.project, files).then(function(rulesetName) { - self.rulesetNames[filename] = rulesetName; - })); + utils.logBullet( + chalk.cyan(self.type + ":") + " uploading rules " + chalk.bold(filename) + "..." + ); + promises.push( + gcp.rules.createRuleset(self.options.project, files).then(function(rulesetName) { + self.rulesetNames[filename] = rulesetName; + }) + ); }); return RSVP.all(promises); }, release: function(filename, resourceName) { var self = this; - return gcp.rules.updateOrCreateRelease( - this.options.project, - this.rulesetNames[filename], - resourceName - ).then(function() { - utils.logSuccess(chalk.bold.green(self.type + ': ') + 'released rules ' + chalk.bold(filename) + ' to ' + chalk.bold(resourceName)); - }); + return gcp.rules + .updateOrCreateRelease(this.options.project, this.rulesetNames[filename], resourceName) + .then(function() { + utils.logSuccess( + chalk.bold.green(self.type + ": ") + + "released rules " + + chalk.bold(filename) + + " to " + + chalk.bold(resourceName) + ); + }); }, _compileRuleset: function(filename, files) { - utils.logBullet(chalk.bold.cyan(this.type + ':') + ' checking ' + chalk.bold(filename) + ' for compilation errors...'); + utils.logBullet( + chalk.bold.cyan(this.type + ":") + + " checking " + + chalk.bold(filename) + + " for compilation errors..." + ); var self = this; return gcp.rules.testRuleset(self.options.project, files).then(function(response) { if (response.body && response.body.issues && response.body.issues.length > 0) { - var add = response.body.issues.length === 1 ? '' : 's'; - var message = 'Compilation error' + add + ' in ' + chalk.bold(filename) + ':\n'; + var add = response.body.issues.length === 1 ? "" : "s"; + var message = "Compilation error" + add + " in " + chalk.bold(filename) + ":\n"; response.body.issues.forEach(function(issue) { - message += '\n[' + issue.severity.substring(0, 1) + '] ' + issue.sourcePosition.line + ':' + issue.sourcePosition.column + ' - ' + issue.description; + message += + "\n[" + + issue.severity.substring(0, 1) + + "] " + + issue.sourcePosition.line + + ":" + + issue.sourcePosition.column + + " - " + + issue.description; }); - return utils.reject(message, {exit: 1}); + return utils.reject(message, { exit: 1 }); } - utils.logSuccess(chalk.bold.green(self.type + ':') + ' rules file ' + chalk.bold(filename) + ' compiled successfully'); + utils.logSuccess( + chalk.bold.green(self.type + ":") + + " rules file " + + chalk.bold(filename) + + " compiled successfully" + ); return RSVP.resolve(); }); - } + }, }; module.exports = RulesDeploy; diff --git a/lib/accountExporter.js b/lib/accountExporter.js index 768b1b1b..01defe35 100644 --- a/lib/accountExporter.js +++ b/lib/accountExporter.js @@ -1,46 +1,55 @@ -'use strict'; +"use strict"; -var os = require('os'); -var path = require('path'); -var _ = require('lodash'); +var os = require("os"); +var path = require("path"); +var _ = require("lodash"); -var api = require('../lib/api'); -var utils = require('../lib/utils'); +var api = require("../lib/api"); +var utils = require("../lib/utils"); var EXPORTED_JSON_KEYS = [ - 'localId', 'email', 'emailVerified', 'passwordHash', 'salt', 'displayName', 'photoUrl', 'lastLoginAt', 'createdAt', - 'phoneNumber']; + "localId", + "email", + "emailVerified", + "passwordHash", + "salt", + "displayName", + "photoUrl", + "lastLoginAt", + "createdAt", + "phoneNumber", +]; var EXPORTED_JSON_KEYS_RENAMING = { - lastLoginAt: 'lastSignedInAt' + lastLoginAt: "lastSignedInAt", }; -var EXPORTED_PROVIDER_USER_INFO_KEYS = ['providerId', 'rawId', 'email', 'displayName', 'photoUrl']; +var EXPORTED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"]; var PROVIDER_ID_INDEX_MAP = { - 'google.com': 7, - 'facebook.com': 11, - 'twitter.com': 15, - 'github.com': 19 + "google.com": 7, + "facebook.com": 11, + "twitter.com": 15, + "github.com": 19, }; var _convertToNormalBase64 = function(data) { - return data.replace(/_/g, '\/').replace(/-/g, '\+'); + return data.replace(/_/g, "/").replace(/-/g, "+"); }; var _addProviderUserInfo = function(providerInfo, arr, startPos) { arr[startPos] = providerInfo.rawId; - arr[startPos + 1] = providerInfo.email || ''; - arr[startPos + 2] = providerInfo.displayName || ''; - arr[startPos + 3] = providerInfo.photoUrl || ''; + arr[startPos + 1] = providerInfo.email || ""; + arr[startPos + 2] = providerInfo.displayName || ""; + arr[startPos + 3] = providerInfo.photoUrl || ""; }; var _transUserToArray = function(user) { - var arr = Array(26).fill(''); + var arr = Array(26).fill(""); arr[0] = user.localId; - arr[1] = user.email || ''; + arr[1] = user.email || ""; arr[2] = user.emailVerified || false; - arr[3] = _convertToNormalBase64(user.passwordHash || ''); - arr[4] = _convertToNormalBase64(user.salt || ''); - arr[5] = user.displayName || ''; - arr[6] = user.photoUrl || ''; + arr[3] = _convertToNormalBase64(user.passwordHash || ""); + arr[4] = _convertToNormalBase64(user.salt || ""); + arr[5] = user.displayName || ""; + arr[6] = user.photoUrl || ""; for (var i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) { var providerInfo = user.providerUserInfo[i]; if (providerInfo && PROVIDER_ID_INDEX_MAP[providerInfo.providerId]) { @@ -81,28 +90,30 @@ var _transUserJson = function(user) { var validateOptions = function(options, fileName) { var exportOptions = {}; if (fileName === undefined) { - return utils.reject('Must specify data file', {exit: 1}); + return utils.reject("Must specify data file", { exit: 1 }); } var extName = path.extname(fileName.toLowerCase()); - if (extName === '.csv') { - exportOptions.format = 'csv'; - } else if (extName === '.json') { - exportOptions.format = 'json'; + if (extName === ".csv") { + exportOptions.format = "csv"; + } else if (extName === ".json") { + exportOptions.format = "json"; } else if (options.format) { var format = options.format.toLowerCase(); - if (format === 'csv' || format === 'json') { + if (format === "csv" || format === "json") { exportOptions.format = format; } else { - return utils.reject('Unsupported data file format, should be csv or json', {exit: 1}); + return utils.reject("Unsupported data file format, should be csv or json", { exit: 1 }); } } else { - return utils.reject('Please specify data file format in file name, or use `format` parameter', {exit: 1}); + return utils.reject("Please specify data file format in file name, or use `format` parameter", { + exit: 1, + }); } return exportOptions; }; var _writeUsersToFile = (function() { - var jsonSep = ''; + var jsonSep = ""; return function(userList, format, writeStream) { userList.map(function(user) { if (user.passwordHash && user.version !== 0) { @@ -110,11 +121,11 @@ var _writeUsersToFile = (function() { delete user.passwordHash; delete user.salt; } - if (format === 'csv') { - writeStream.write(_transUserToArray(user).join(',') + ',' + os.EOL, 'utf8'); + if (format === "csv") { + writeStream.write(_transUserToArray(user).join(",") + "," + os.EOL, "utf8"); } else { - writeStream.write(jsonSep + JSON.stringify(_transUserJson(user), null, 2), 'utf8'); - jsonSep = ',' + os.EOL; + writeStream.write(jsonSep + JSON.stringify(_transUserJson(user), null, 2), "utf8"); + jsonSep = "," + os.EOL; } }); }; @@ -123,30 +134,32 @@ var _writeUsersToFile = (function() { var serialExportUsers = function(projectId, options) { var postBody = { targetProjectId: projectId, - maxResults: options.batchSize + maxResults: options.batchSize, }; if (options.nextPageToken) { postBody.nextPageToken = options.nextPageToken; } - return api.request('POST', '/identitytoolkit/v3/relyingparty/downloadAccount', { - auth: true, - json: true, - data: postBody, - origin: api.googleOrigin - }).then(function(ret) { - var userList = ret.body.users; - if (userList && userList.length > 0) { - _writeUsersToFile(userList, options.format, options.writeStream); - utils.logSuccess('Exported ' + userList.length + ' account(s) successfully.'); - options.nextPageToken = ret.body.nextPageToken; - return serialExportUsers(projectId, options); - } - }); + return api + .request("POST", "/identitytoolkit/v3/relyingparty/downloadAccount", { + auth: true, + json: true, + data: postBody, + origin: api.googleOrigin, + }) + .then(function(ret) { + var userList = ret.body.users; + if (userList && userList.length > 0) { + _writeUsersToFile(userList, options.format, options.writeStream); + utils.logSuccess("Exported " + userList.length + " account(s) successfully."); + options.nextPageToken = ret.body.nextPageToken; + return serialExportUsers(projectId, options); + } + }); }; var accountExporter = { validateOptions: validateOptions, - serialExportUsers: serialExportUsers + serialExportUsers: serialExportUsers, }; module.exports = accountExporter; diff --git a/lib/accountImporter.js b/lib/accountImporter.js index fd2783ae..f3aa0557 100644 --- a/lib/accountImporter.js +++ b/lib/accountImporter.js @@ -1,23 +1,36 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var _ = require('lodash'); +var chalk = require("chalk"); +var _ = require("lodash"); -var api = require('../lib/api'); -var logger = require('../lib/logger'); -var utils = require('../lib/utils'); +var api = require("../lib/api"); +var logger = require("../lib/logger"); +var utils = require("../lib/utils"); var ALLOWED_JSON_KEYS = [ - 'localId', 'email', 'emailVerified', 'passwordHash', 'salt', 'displayName', 'photoUrl', 'createdAt', 'lastSignedInAt', - 'providerUserInfo', 'phoneNumber']; + "localId", + "email", + "emailVerified", + "passwordHash", + "salt", + "displayName", + "photoUrl", + "createdAt", + "lastSignedInAt", + "providerUserInfo", + "phoneNumber", +]; var ALLOWED_JSON_KEYS_RENAMING = { - lastSignedInAt: 'lastLoginAt' + lastSignedInAt: "lastLoginAt", }; -var ALLOWED_PROVIDER_USER_INFO_KEYS = ['providerId', 'rawId', 'email', 'displayName', 'photoUrl']; -var ALLOWED_PROVIDER_IDS = ['google.com', 'facebook.com', 'twitter.com', 'github.com']; +var ALLOWED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"]; +var ALLOWED_PROVIDER_IDS = ["google.com", "facebook.com", "twitter.com", "github.com"]; var _toWebSafeBase64 = function(data) { - return data.toString('base64').replace(/\//g, '_').replace(/\+/g, '-'); + return data + .toString("base64") + .replace(/\//g, "_") + .replace(/\+/g, "-"); }; var _addProviderUserInfo = function(user, providerId, arr) { @@ -27,29 +40,28 @@ var _addProviderUserInfo = function(user, providerId, arr) { rawId: arr[0], email: arr[1], displayName: arr[2], - photoUrl: arr[3] + photoUrl: arr[3], }); } }; var _genUploadAccountPostBody = function(projectId, accounts, hashOptions) { var postBody = { - users: accounts.map( - function(account) { - if (account.passwordHash) { - account.passwordHash = _toWebSafeBase64(account.passwordHash); - } - if (account.salt) { - account.salt = _toWebSafeBase64(account.salt); - } - _.each(ALLOWED_JSON_KEYS_RENAMING, function(value, key) { - if (account[key]) { - account[value] = account[key]; - delete account[key]; - } - }); - return account; - }) + users: accounts.map(function(account) { + if (account.passwordHash) { + account.passwordHash = _toWebSafeBase64(account.passwordHash); + } + if (account.salt) { + account.salt = _toWebSafeBase64(account.salt); + } + _.each(ALLOWED_JSON_KEYS_RENAMING, function(value, key) { + if (account[key]) { + account[value] = account[key]; + delete account[key]; + } + }); + return account; + }), }; if (hashOptions.hashAlgo) { postBody.hashAlgorithm = hashOptions.hashAlgo; @@ -86,7 +98,7 @@ var transArrayToUser = function(arr) { var user = { localId: arr[0], email: arr[1], - emailVerified: arr[2] === 'true', + emailVerified: arr[2] === "true", passwordHash: arr[3], salt: arr[4], displayName: arr[5], @@ -94,92 +106,112 @@ var transArrayToUser = function(arr) { createdAt: arr[23], lastLoginAt: arr[24], phoneNumber: arr[26], - providerUserInfo: [] + providerUserInfo: [], }; - _addProviderUserInfo(user, 'google.com', arr.slice(7, 11)); - _addProviderUserInfo(user, 'facebook.com', arr.slice(11, 15)); - _addProviderUserInfo(user, 'twitter.com', arr.slice(15, 19)); - _addProviderUserInfo(user, 'github.com', arr.slice(19, 23)); + _addProviderUserInfo(user, "google.com", arr.slice(7, 11)); + _addProviderUserInfo(user, "facebook.com", arr.slice(11, 15)); + _addProviderUserInfo(user, "twitter.com", arr.slice(15, 19)); + _addProviderUserInfo(user, "github.com", arr.slice(19, 23)); return user; }; var validateOptions = function(options) { if (!options.hashAlgo) { - utils.logWarning('No hash algorithm specified. Password users cannot be imported.'); - return {valid: true}; + utils.logWarning("No hash algorithm specified. Password users cannot be imported."); + return { valid: true }; } var hashAlgo = options.hashAlgo.toUpperCase(); switch (hashAlgo) { - case 'HMAC_SHA512': - case 'HMAC_SHA256': - case 'HMAC_SHA1': - case 'HMAC_MD5': - if (!options.hashKey || options.hashKey === '') { - return utils.reject('Must provide hash key(base64 encoded) for hash algorithm ' + options.hashAlgo, {exit: 1}); - } - return {hashAlgo: hashAlgo, hashKey: options.hashKey, valid: true}; - case 'MD5': - case 'SHA1': - case 'SHA256': - case 'SHA512': - case 'PBKDF_SHA1': - case 'PBKDF2_SHA256': - var roundsNum = parseInt(options.rounds, 10); - if (isNaN(roundsNum) || roundsNum < 0 || roundsNum > 120000) { - return utils.reject('Must provide valid rounds(0..120000) for hash algorithm ' + options.hashAlgo, {exit: 1}); - } - return {hashAlgo: hashAlgo, rounds: options.rounds, valid: true}; - case 'SCRYPT': - if (!options.hashKey || options.hashKey === '') { - return utils.reject('Must provide hash key(base64 encoded) for hash algorithm ' + options.hashAlgo, {exit: 1}); - } - roundsNum = parseInt(options.rounds, 10); - if (isNaN(roundsNum) || roundsNum <= 0 || roundsNum > 8) { - return utils.reject('Must provide valid rounds(1..8) for hash algorithm ' + options.hashAlgo, {exit: 1}); - } - var memCost = parseInt(options.memCost, 10); - if (isNaN(memCost) || memCost <= 0 || memCost > 14) { - return utils.reject('Must provide valid memory cost(1..14) for hash algorithm ' + options.hashAlgo, {exit: 1}); - } - var saltSeparator = ''; - if (options.saltSeparator) { - saltSeparator = options.saltSeparator; - } - return { - hashAlgo: hashAlgo, - hashKey: options.hashKey, - saltSeparator: saltSeparator, - rounds: options.rounds, - memCost: options.memCost, - valid: true - }; - case 'BCRYPT': - return {hashAlgo: hashAlgo, valid: true}; - case 'STANDARD_SCRYPT': - var cpuMemCost = parseInt(options.memCost, 10); - var parallelization = parseInt(options.parallelization, 10); - var blockSize = parseInt(options.blockSize, 10); - var dkLen = parseInt(options.dkLen, 10); - return { - hashAlgo: hashAlgo, - valid: true, - cpuMemCost: cpuMemCost, - parallelization: parallelization, - blockSize: blockSize, - dkLen: dkLen - }; - default: - return utils.reject('Unsupported hash algorithm ' + chalk.bold(options.hashAlgo)); + case "HMAC_SHA512": + case "HMAC_SHA256": + case "HMAC_SHA1": + case "HMAC_MD5": + if (!options.hashKey || options.hashKey === "") { + return utils.reject( + "Must provide hash key(base64 encoded) for hash algorithm " + options.hashAlgo, + { exit: 1 } + ); + } + return { hashAlgo: hashAlgo, hashKey: options.hashKey, valid: true }; + case "MD5": + case "SHA1": + case "SHA256": + case "SHA512": + case "PBKDF_SHA1": + case "PBKDF2_SHA256": + var roundsNum = parseInt(options.rounds, 10); + if (isNaN(roundsNum) || roundsNum < 0 || roundsNum > 120000) { + return utils.reject( + "Must provide valid rounds(0..120000) for hash algorithm " + options.hashAlgo, + { exit: 1 } + ); + } + return { hashAlgo: hashAlgo, rounds: options.rounds, valid: true }; + case "SCRYPT": + if (!options.hashKey || options.hashKey === "") { + return utils.reject( + "Must provide hash key(base64 encoded) for hash algorithm " + options.hashAlgo, + { exit: 1 } + ); + } + roundsNum = parseInt(options.rounds, 10); + if (isNaN(roundsNum) || roundsNum <= 0 || roundsNum > 8) { + return utils.reject( + "Must provide valid rounds(1..8) for hash algorithm " + options.hashAlgo, + { exit: 1 } + ); + } + var memCost = parseInt(options.memCost, 10); + if (isNaN(memCost) || memCost <= 0 || memCost > 14) { + return utils.reject( + "Must provide valid memory cost(1..14) for hash algorithm " + options.hashAlgo, + { exit: 1 } + ); + } + var saltSeparator = ""; + if (options.saltSeparator) { + saltSeparator = options.saltSeparator; + } + return { + hashAlgo: hashAlgo, + hashKey: options.hashKey, + saltSeparator: saltSeparator, + rounds: options.rounds, + memCost: options.memCost, + valid: true, + }; + case "BCRYPT": + return { hashAlgo: hashAlgo, valid: true }; + case "STANDARD_SCRYPT": + var cpuMemCost = parseInt(options.memCost, 10); + var parallelization = parseInt(options.parallelization, 10); + var blockSize = parseInt(options.blockSize, 10); + var dkLen = parseInt(options.dkLen, 10); + return { + hashAlgo: hashAlgo, + valid: true, + cpuMemCost: cpuMemCost, + parallelization: parallelization, + blockSize: blockSize, + dkLen: dkLen, + }; + default: + return utils.reject("Unsupported hash algorithm " + chalk.bold(options.hashAlgo)); } }; var _validateProviderUserInfo = function(providerUserInfo) { if (!_.includes(ALLOWED_PROVIDER_IDS, providerUserInfo.providerId)) { - return {error: JSON.stringify(providerUserInfo, null, 2) + ' has unsupported providerId'}; + return { + error: JSON.stringify(providerUserInfo, null, 2) + " has unsupported providerId", + }; } var keydiff = _.difference(_.keys(providerUserInfo), ALLOWED_PROVIDER_USER_INFO_KEYS); if (keydiff.length) { - return {error: JSON.stringify(providerUserInfo, null, 2) + ' has unsupported keys: ' + keydiff.join(',')}; + return { + error: + JSON.stringify(providerUserInfo, null, 2) + " has unsupported keys: " + keydiff.join(","), + }; } return {}; }; @@ -187,7 +219,9 @@ var _validateProviderUserInfo = function(providerUserInfo) { var validateUserJson = function(userJson) { var keydiff = _.difference(_.keys(userJson), ALLOWED_JSON_KEYS); if (keydiff.length) { - return {error: JSON.stringify(userJson, null, 2) + ' has unsupported keys: ' + keydiff.join(',')}; + return { + error: JSON.stringify(userJson, null, 2) + " has unsupported keys: " + keydiff.join(","), + }; } if (userJson.providerUserInfo) { for (var i = 0; i < userJson.providerUserInfo.length; i++) { @@ -201,43 +235,45 @@ var validateUserJson = function(userJson) { }; var _sendRequest = function(projectId, userList, hashOptions) { - logger.info('Starting importing ' + userList.length + ' account(s).'); - return api.request('POST', '/identitytoolkit/v3/relyingparty/uploadAccount', { - auth: true, - json: true, - data: _genUploadAccountPostBody(projectId, userList, hashOptions), - origin: api.googleOrigin - }).then(function(ret) { - if (ret.body.error) { - logger.info('Encountered problems while importing accounts. Details:'); - logger.info(ret.body.error.map( - function(rawInfo) { + logger.info("Starting importing " + userList.length + " account(s)."); + return api + .request("POST", "/identitytoolkit/v3/relyingparty/uploadAccount", { + auth: true, + json: true, + data: _genUploadAccountPostBody(projectId, userList, hashOptions), + origin: api.googleOrigin, + }) + .then(function(ret) { + if (ret.body.error) { + logger.info("Encountered problems while importing accounts. Details:"); + logger.info( + ret.body.error.map(function(rawInfo) { return { account: JSON.stringify(userList[parseInt(rawInfo.index, 10)], null, 2), - reason: rawInfo.message + reason: rawInfo.message, }; - })); - } else { - utils.logSuccess('Imported successfully.'); - } - logger.info(); - }); + }) + ); + } else { + utils.logSuccess("Imported successfully."); + } + logger.info(); + }); }; var serialImportUsers = function(projectId, hashOptions, userListArr, index) { - return _sendRequest(projectId, userListArr[index], hashOptions) - .then(function() { - if (index < userListArr.length - 1) { - return serialImportUsers(projectId, hashOptions, userListArr, index + 1); - } - }); + return _sendRequest(projectId, userListArr[index], hashOptions).then(function() { + if (index < userListArr.length - 1) { + return serialImportUsers(projectId, hashOptions, userListArr, index + 1); + } + }); }; var accountImporter = { validateOptions: validateOptions, validateUserJson: validateUserJson, transArrayToUser: transArrayToUser, - serialImportUsers: serialImportUsers + serialImportUsers: serialImportUsers, }; module.exports = accountImporter; diff --git a/lib/acquireRefs.js b/lib/acquireRefs.js index 4893237d..3782cd78 100644 --- a/lib/acquireRefs.js +++ b/lib/acquireRefs.js @@ -1,26 +1,28 @@ -'use strict'; +"use strict"; -var FirebaseError = require('./error'); -var api = require('./api'); -var Firebase = require('firebase'); -var RSVP = require('rsvp'); -var utils = require('./utils'); -var requireAccess = require('./requireAccess'); +var FirebaseError = require("./error"); +var api = require("./api"); +var Firebase = require("firebase"); +var RSVP = require("rsvp"); +var utils = require("./utils"); +var requireAccess = require("./requireAccess"); module.exports = function(options, authScopes) { return requireAccess(options, authScopes).then(function() { return new RSVP.Promise(function(resolve, reject) { - if (process.env.FIREBASE_BYPASS_ADMIN_CALLS_FOR_TESTING === 'true') { + if (process.env.FIREBASE_BYPASS_ADMIN_CALLS_FOR_TESTING === "true") { // requireAccess() hasn't set the metadataToken, so can't auth. resolve(); } - var firebaseRef = new Firebase(utils.addSubdomain(api.realtimeOrigin, 'firebase')); + var firebaseRef = new Firebase(utils.addSubdomain(api.realtimeOrigin, "firebase")); firebaseRef.authWithCustomToken(options.metadataToken, function(err) { if (err) { - return reject(new FirebaseError('Failed to authenticate to Firebase', { - original: err - })); + return reject( + new FirebaseError("Failed to authenticate to Firebase", { + original: err, + }) + ); } options.firebaseRef = firebaseRef; resolve(); diff --git a/lib/api.js b/lib/api.js index 844838dd..1bd93ed7 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,46 +1,49 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var querystring = require('querystring'); -var request = require('request'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var querystring = require("querystring"); +var request = require("request"); +var RSVP = require("rsvp"); -var FirebaseError = require('./error'); -var logger = require('./logger'); -var responseToError = require('./responseToError'); -var scopes = require('./scopes'); -var utils = require('./utils'); +var FirebaseError = require("./error"); +var logger = require("./logger"); +var responseToError = require("./responseToError"); +var scopes = require("./scopes"); +var utils = require("./utils"); -var CLI_VERSION = require('../package.json').version; +var CLI_VERSION = require("../package.json").version; var accessToken; var refreshToken; var commandScopes; var _request = function(options) { - logger.debug('>>> HTTP REQUEST', + logger.debug( + ">>> HTTP REQUEST", options.method, options.url, - options.qs ? '\nquery params: ' + JSON.stringify(options.qs) : '', - '\n', - options.body || options.form || '', - '\n', + options.qs ? "\nquery params: " + JSON.stringify(options.qs) : "", + "\n", + options.body || options.form || "", + "\n", new Date().toString() ); return new RSVP.Promise(function(resolve, reject) { var req = request(options, function(err, response, body) { if (err) { - return reject(new FirebaseError('Server Error. ' + err.message, { - original: err, - exit: 2 - })); + return reject( + new FirebaseError("Server Error. " + err.message, { + original: err, + exit: 2, + }) + ); } - logger.debug('<<< HTTP RESPONSE', response.statusCode, response.headers); + logger.debug("<<< HTTP RESPONSE", response.statusCode, response.headers); if (response.statusCode >= 400) { - logger.debug('<<< HTTP RESPONSE BODY', response.body); + logger.debug("<<< HTTP RESPONSE BODY", response.body); if (!options.resolveOnHTTPError) { return reject(responseToError(response, body, options)); } @@ -49,7 +52,7 @@ var _request = function(options) { return resolve({ status: response.statusCode, response: response, - body: body + body: body, }); }); @@ -59,7 +62,7 @@ var _request = function(options) { form.append(param, details.stream, { knownLength: details.knownLength, filename: details.filename, - contentType: details.contentType + contentType: details.contentType, }); }); } @@ -68,7 +71,7 @@ var _request = function(options) { var _appendQueryData = function(path, data) { if (data && _.size(data) > 0) { - path += _.includes(path, '?') ? '&' : '?'; + path += _.includes(path, "?") ? "&" : "?"; path += querystring.stringify(data); } return path; @@ -77,24 +80,45 @@ var _appendQueryData = function(path, data) { var api = { // "In this context, the client secret is obviously not treated as a secret" // https://developers.google.com/identity/protocols/OAuth2InstalledApp - clientId: utils.envOverride('FIREBASE_CLIENT_ID', '563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com'), - clientSecret: utils.envOverride('FIREBASE_CLIENT_SECRET', 'j9iVZfS8kkCEFUPaAeJV0sAi'), - cloudloggingOrigin: utils.envOverride('FIREBASE_CLOUDLOGGING_URL', 'https://logging.googleapis.com'), - adminOrigin: utils.envOverride('FIREBASE_ADMIN_URL', 'https://admin.firebase.com'), - appengineOrigin: utils.envOverride('FIREBASE_APPENGINE_URL', 'https://appengine.googleapis.com'), - authOrigin: utils.envOverride('FIREBASE_AUTH_URL', 'https://accounts.google.com'), - consoleOrigin: utils.envOverride('FIREBASE_CONSOLE_URL', 'https://console.firebase.google.com'), - deployOrigin: utils.envOverride('FIREBASE_DEPLOY_URL', utils.envOverride('FIREBASE_UPLOAD_URL', 'https://deploy.firebase.com')), - firedataOrigin: utils.envOverride('FIREBASE_FIREDATA_URL', 'https://mobilesdk-pa.googleapis.com'), - firestoreOrigin: utils.envOverride('FIRESTORE_URL', 'https://firestore.googleapis.com'), - functionsOrigin: utils.envOverride('FIREBASE_FUNCTIONS_URL', 'https://cloudfunctions.googleapis.com'), - googleOrigin: utils.envOverride('FIREBASE_TOKEN_URL', utils.envOverride('FIREBASE_GOOGLE_URL', 'https://www.googleapis.com')), - hostingOrigin: utils.envOverride('FIREBASE_HOSTING_URL', 'https://firebaseapp.com'), - realtimeOrigin: utils.envOverride('FIREBASE_REALTIME_URL', 'https://firebaseio.com'), - resourceManagerOrigin: utils.envOverride('FIREBASE_RESOURCEMANAGER_URL', 'https://cloudresourcemanager.googleapis.com'), - rulesOrigin: utils.envOverride('FIREBASE_RULES_URL', 'https://firebaserules.googleapis.com'), - runtimeconfigOrigin: utils.envOverride('FIREBASE_RUNTIMECONFIG_URL', 'https://runtimeconfig.googleapis.com'), - storageOrigin: utils.envOverride('FIREBASE_STORAGE_URL', 'https://storage.googleapis.com'), + clientId: utils.envOverride( + "FIREBASE_CLIENT_ID", + "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com" + ), + clientSecret: utils.envOverride("FIREBASE_CLIENT_SECRET", "j9iVZfS8kkCEFUPaAeJV0sAi"), + cloudloggingOrigin: utils.envOverride( + "FIREBASE_CLOUDLOGGING_URL", + "https://logging.googleapis.com" + ), + adminOrigin: utils.envOverride("FIREBASE_ADMIN_URL", "https://admin.firebase.com"), + appengineOrigin: utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com"), + authOrigin: utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com"), + consoleOrigin: utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com"), + deployOrigin: utils.envOverride( + "FIREBASE_DEPLOY_URL", + utils.envOverride("FIREBASE_UPLOAD_URL", "https://deploy.firebase.com") + ), + firedataOrigin: utils.envOverride("FIREBASE_FIREDATA_URL", "https://mobilesdk-pa.googleapis.com"), + firestoreOrigin: utils.envOverride("FIRESTORE_URL", "https://firestore.googleapis.com"), + functionsOrigin: utils.envOverride( + "FIREBASE_FUNCTIONS_URL", + "https://cloudfunctions.googleapis.com" + ), + googleOrigin: utils.envOverride( + "FIREBASE_TOKEN_URL", + utils.envOverride("FIREBASE_GOOGLE_URL", "https://www.googleapis.com") + ), + hostingOrigin: utils.envOverride("FIREBASE_HOSTING_URL", "https://firebaseapp.com"), + realtimeOrigin: utils.envOverride("FIREBASE_REALTIME_URL", "https://firebaseio.com"), + resourceManagerOrigin: utils.envOverride( + "FIREBASE_RESOURCEMANAGER_URL", + "https://cloudresourcemanager.googleapis.com" + ), + rulesOrigin: utils.envOverride("FIREBASE_RULES_URL", "https://firebaserules.googleapis.com"), + runtimeconfigOrigin: utils.envOverride( + "FIREBASE_RUNTIMECONFIG_URL", + "https://runtimeconfig.googleapis.com" + ), + storageOrigin: utils.envOverride("FIREBASE_STORAGE_URL", "https://storage.googleapis.com"), setRefreshToken: function(token) { refreshToken = token; @@ -103,49 +127,58 @@ var api = { accessToken = token; }, setScopes: function(s) { - commandScopes = _.uniq(_.flatten([ - scopes.EMAIL, - scopes.OPENID, - scopes.CLOUD_PROJECTS_READONLY, - scopes.FIREBASE_PLATFORM - ].concat(s || []))); - logger.debug('> command requires scopes:', JSON.stringify(commandScopes)); + commandScopes = _.uniq( + _.flatten( + [ + scopes.EMAIL, + scopes.OPENID, + scopes.CLOUD_PROJECTS_READONLY, + scopes.FIREBASE_PLATFORM, + ].concat(s || []) + ) + ); + logger.debug("> command requires scopes:", JSON.stringify(commandScopes)); }, getAccessToken: function() { - return accessToken ? RSVP.resolve({access_token: accessToken}) : require('./auth').getAccessToken(refreshToken, commandScopes); + return accessToken + ? RSVP.resolve({ access_token: accessToken }) + : require("./auth").getAccessToken(refreshToken, commandScopes); }, addRequestHeaders: function(reqOptions) { - // Runtime fetch of Auth singleton to prevent circular module dependencies - _.set(reqOptions, ['headers', 'User-Agent'], 'FirebaseCLI/' + CLI_VERSION); - _.set(reqOptions, ['headers', 'X-Client-Version'], 'FirebaseCLI/' + CLI_VERSION); + // Runtime fetch of Auth singleton to prevent circular module dependencies + _.set(reqOptions, ["headers", "User-Agent"], "FirebaseCLI/" + CLI_VERSION); + _.set(reqOptions, ["headers", "X-Client-Version"], "FirebaseCLI/" + CLI_VERSION); return api.getAccessToken().then(function(result) { - _.set(reqOptions, 'headers.authorization', 'Bearer ' + result.access_token); + _.set(reqOptions, "headers.authorization", "Bearer " + result.access_token); return reqOptions; }); }, request: function(method, resource, options) { - options = _.extend({ - data: {}, - origin: api.adminOrigin, // default to hitting the admin backend - resolveOnHTTPError: false, // by default, status codes >= 400 leads to reject - json: true - }, options); + options = _.extend( + { + data: {}, + origin: api.adminOrigin, // default to hitting the admin backend + resolveOnHTTPError: false, // by default, status codes >= 400 leads to reject + json: true, + }, + options + ); - var validMethods = ['GET', 'PUT', 'POST', 'DELETE', 'PATCH']; + var validMethods = ["GET", "PUT", "POST", "DELETE", "PATCH"]; if (validMethods.indexOf(method) < 0) { - method = 'GET'; + method = "GET"; } var reqOptions = { - method: method + method: method, }; if (options.query) { resource = _appendQueryData(resource, options.query); } - if (method === 'GET') { + if (method === "GET") { resource = _appendQueryData(resource, options.data); } else { if (_.size(options.data) > 0) { @@ -171,33 +204,41 @@ var api = { return _request(reqOptions); }, getProject: function(projectId) { - return api.request('GET', '/v1/projects/' + encodeURIComponent(projectId), { - auth: true - }).then(function(res) { - if (res.body && !res.body.error) { - return res.body; - } + return api + .request("GET", "/v1/projects/" + encodeURIComponent(projectId), { + auth: true, + }) + .then(function(res) { + if (res.body && !res.body.error) { + return res.body; + } - return RSVP.reject(new FirebaseError('Server Error: Unexpected Response. Please try again', { - context: res, - exit: 2 - })); - }); + return RSVP.reject( + new FirebaseError("Server Error: Unexpected Response. Please try again", { + context: res, + exit: 2, + }) + ); + }); }, getProjects: function() { - return api.request('GET', '/v1/projects', { - auth: true - }).then(function(res) { - if (res.body && res.body.projects) { - return res.body.projects; - } + return api + .request("GET", "/v1/projects", { + auth: true, + }) + .then(function(res) { + if (res.body && res.body.projects) { + return res.body.projects; + } - return RSVP.reject(new FirebaseError('Server Error: Unexpected Response. Please try again', { - context: res, - exit: 2 - })); - }); - } + return RSVP.reject( + new FirebaseError("Server Error: Unexpected Response. Please try again", { + context: res, + exit: 2, + }) + ); + }); + }, }; module.exports = api; diff --git a/lib/auth.js b/lib/auth.js index 27f40b40..e2cf739f 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,29 +1,32 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var fs = require('fs'); -var jwt = require('jsonwebtoken'); -var http = require('http'); -var open = require('open'); -var path = require('path'); -var portfinder = require('portfinder'); -var RSVP = require('rsvp'); -var url = require('url'); +var _ = require("lodash"); +var chalk = require("chalk"); +var fs = require("fs"); +var jwt = require("jsonwebtoken"); +var http = require("http"); +var open = require("open"); +var path = require("path"); +var portfinder = require("portfinder"); +var RSVP = require("rsvp"); +var url = require("url"); -var api = require('./api'); -var configstore = require('./configstore'); -var FirebaseError = require('./error'); -var logger = require('./logger'); -var prompt = require('./prompt'); -var scopes = require('./scopes'); +var api = require("./api"); +var configstore = require("./configstore"); +var FirebaseError = require("./error"); +var logger = require("./logger"); +var prompt = require("./prompt"); +var scopes = require("./scopes"); portfinder.basePort = 9005; var INVALID_CREDENTIAL_ERROR = new FirebaseError( - 'Authentication Error: Your credentials are no longer valid. Please run ' + chalk.bold('firebase login --reauth') + '\n\n' + - 'For CI servers and headless environments, generate a new token with ' + chalk.bold('firebase login:ci'), - {exit: 1} + "Authentication Error: Your credentials are no longer valid. Please run " + + chalk.bold("firebase login --reauth") + + "\n\n" + + "For CI servers and headless environments, generate a new token with " + + chalk.bold("firebase login:ci"), + { exit: 1 } ); var FIFTEEN_MINUTES_IN_MS = 15 * 60 * 1000; @@ -32,7 +35,7 @@ var SCOPES = [ scopes.OPENID, scopes.CLOUD_PROJECTS_READONLY, scopes.FIREBASE_PLATFORM, - scopes.CLOUD_PLATFORM + scopes.CLOUD_PLATFORM, ]; var _nonce = _.random(1, 2 << 29).toString(); @@ -43,46 +46,61 @@ var lastAccessToken = {}; var _getCallbackUrl = function(port) { if (_.isUndefined(port)) { - return 'urn:ietf:wg:oauth:2.0:oob'; + return "urn:ietf:wg:oauth:2.0:oob"; } - return 'http://localhost:' + port; + return "http://localhost:" + port; }; var _getLoginUrl = function(callbackUrl) { - return api.authOrigin + '/o/oauth2/auth?' + _.map({ - client_id: api.clientId, - scope: SCOPES.join(' '), - response_type: 'code', - state: _nonce, - redirect_uri: callbackUrl - }, function(v, k) { - return k + '=' + encodeURIComponent(v); - }).join('&'); + return ( + api.authOrigin + + "/o/oauth2/auth?" + + _.map( + { + client_id: api.clientId, + scope: SCOPES.join(" "), + response_type: "code", + state: _nonce, + redirect_uri: callbackUrl, + }, + function(v, k) { + return k + "=" + encodeURIComponent(v); + } + ).join("&") + ); }; var _getTokensFromAuthorizationCode = function(code, callbackUrl) { - return api.request('POST', '/o/oauth2/token', { - origin: api.authOrigin, - form: { - code: code, - client_id: api.clientId, - client_secret: api.clientSecret, - redirect_uri: callbackUrl, - grant_type: 'authorization_code' - } - }).then(function(res) { - if (!_.has(res, 'body.access_token') && !_.has(res, 'body.refresh_token')) { - logger.debug('Token Fetch Error:', res.statusCode, res.body); - throw INVALID_CREDENTIAL_ERROR; - } - lastAccessToken = _.assign({ - expires_at: Date.now() + res.body.expires_in * 1000 - }, res.body); - return lastAccessToken; - }, function(err) { - logger.debug('Token Fetch Error:', err.stack); - throw INVALID_CREDENTIAL_ERROR; - }); + return api + .request("POST", "/o/oauth2/token", { + origin: api.authOrigin, + form: { + code: code, + client_id: api.clientId, + client_secret: api.clientSecret, + redirect_uri: callbackUrl, + grant_type: "authorization_code", + }, + }) + .then( + function(res) { + if (!_.has(res, "body.access_token") && !_.has(res, "body.refresh_token")) { + logger.debug("Token Fetch Error:", res.statusCode, res.body); + throw INVALID_CREDENTIAL_ERROR; + } + lastAccessToken = _.assign( + { + expires_at: Date.now() + res.body.expires_in * 1000, + }, + res.body + ); + return lastAccessToken; + }, + function(err) { + logger.debug("Token Fetch Error:", err.stack); + throw INVALID_CREDENTIAL_ERROR; + } + ); }; var _respondWithFile = function(req, res, statusCode, filename) { @@ -92,8 +110,8 @@ var _respondWithFile = function(req, res, statusCode, filename) { return reject(err); } res.writeHead(statusCode, { - 'Content-Length': response.length, - 'Content-Type': 'text/html' + "Content-Length": response.length, + "Content-Type": "text/html", }); res.end(response); req.socket.destroy(); @@ -107,25 +125,29 @@ var _loginWithoutLocalhost = function() { var authUrl = _getLoginUrl(callbackUrl); logger.info(); - logger.info('Visit this URL on any device to log in:'); + logger.info("Visit this URL on any device to log in:"); logger.info(chalk.bold.underline(authUrl)); logger.info(); open(authUrl); - return prompt({}, [{ - type: 'input', - name: 'code', - message: 'Paste authorization code here:' - }]).then(function(answers) { - return _getTokensFromAuthorizationCode(answers.code, callbackUrl); - }).then(function(tokens) { - return { - user: jwt.decode(tokens.id_token), - tokens: tokens, - scopes: SCOPES - }; - }); + return prompt({}, [ + { + type: "input", + name: "code", + message: "Paste authorization code here:", + }, + ]) + .then(function(answers) { + return _getTokensFromAuthorizationCode(answers.code, callbackUrl); + }) + .then(function(tokens) { + return { + user: jwt.decode(tokens.id_token), + tokens: tokens, + scopes: SCOPES, + }; + }); }; var _loginWithLocalhost = function(port) { @@ -135,37 +157,39 @@ var _loginWithLocalhost = function(port) { var server = http.createServer(function(req, res) { var tokens; - var query = _.get(url.parse(req.url, true), 'query', {}); + var query = _.get(url.parse(req.url, true), "query", {}); if (query.state === _nonce && _.isString(query.code)) { return _getTokensFromAuthorizationCode(query.code, callbackUrl) .then(function(result) { tokens = result; - return _respondWithFile(req, res, 200, '../templates/loginSuccess.html'); - }).then(function() { + return _respondWithFile(req, res, 200, "../templates/loginSuccess.html"); + }) + .then(function() { server.close(); return resolve({ user: jwt.decode(tokens.id_token), - tokens: tokens + tokens: tokens, }); - }).catch(function() { - return _respondWithFile(req, res, 400, '../templates/loginFailure.html'); + }) + .catch(function() { + return _respondWithFile(req, res, 400, "../templates/loginFailure.html"); }); } - _respondWithFile(req, res, 400, '../templates/loginFailure.html'); + _respondWithFile(req, res, 400, "../templates/loginFailure.html"); }); server.listen(port, function() { logger.info(); - logger.info('Visit this URL on any device to log in:'); + logger.info("Visit this URL on any device to log in:"); logger.info(chalk.bold.underline(authUrl)); logger.info(); - logger.info('Waiting for authentication...'); + logger.info("Waiting for authentication..."); open(authUrl); }); - server.on('error', function() { + server.on("error", function() { _loginWithoutLocalhost().then(resolve, reject); }); }); @@ -180,69 +204,82 @@ var login = function(localhost) { var _haveValidAccessToken = function(refreshToken, authScopes) { if (_.isEmpty(lastAccessToken)) { - var tokens = configstore.get('tokens'); - if (refreshToken === _.get(tokens, 'refresh_token')) { + var tokens = configstore.get("tokens"); + if (refreshToken === _.get(tokens, "refresh_token")) { lastAccessToken = tokens; } } - return _.has(lastAccessToken, 'access_token') && + return ( + _.has(lastAccessToken, "access_token") && lastAccessToken.refresh_token === refreshToken && // verify that the exact same scopes are being used for this request _.isEqual(authScopes.sort(), (lastAccessToken.scopes || []).sort()) && - _.has(lastAccessToken, 'expires_at') && - lastAccessToken.expires_at > Date.now() + FIFTEEN_MINUTES_IN_MS; + _.has(lastAccessToken, "expires_at") && + lastAccessToken.expires_at > Date.now() + FIFTEEN_MINUTES_IN_MS + ); }; var _logoutCurrentSession = function(refreshToken) { - var tokens = configstore.get('tokens'); - var currentToken = _.get(tokens, 'refresh_token'); + var tokens = configstore.get("tokens"); + var currentToken = _.get(tokens, "refresh_token"); if (refreshToken === currentToken) { - configstore.del('user'); - configstore.del('tokens'); - configstore.del('usage'); - configstore.del('analytics-uuid'); + configstore.del("user"); + configstore.del("tokens"); + configstore.del("usage"); + configstore.del("analytics-uuid"); } }; var _refreshAccessToken = function(refreshToken, authScopes) { - logger.debug('> refreshing access token with scopes:', JSON.stringify(authScopes)); - return api.request('POST', '/oauth2/v3/token', { - origin: api.googleOrigin, - form: { - refresh_token: refreshToken, - client_id: api.clientId, - client_secret: api.clientSecret, - grant_type: 'refresh_token', - scope: (authScopes || []).join(' ') - } - }).then(function(res) { - if (!_.isString(res.body.access_token)) { - throw INVALID_CREDENTIAL_ERROR; - } - lastAccessToken = _.assign({ - expires_at: Date.now() + res.body.expires_in * 1000, - refresh_token: refreshToken, - scopes: authScopes - }, res.body); + logger.debug("> refreshing access token with scopes:", JSON.stringify(authScopes)); + return api + .request("POST", "/oauth2/v3/token", { + origin: api.googleOrigin, + form: { + refresh_token: refreshToken, + client_id: api.clientId, + client_secret: api.clientSecret, + grant_type: "refresh_token", + scope: (authScopes || []).join(" "), + }, + }) + .then( + function(res) { + if (!_.isString(res.body.access_token)) { + throw INVALID_CREDENTIAL_ERROR; + } + lastAccessToken = _.assign( + { + expires_at: Date.now() + res.body.expires_in * 1000, + refresh_token: refreshToken, + scopes: authScopes, + }, + res.body + ); - var currentRefreshToken = _.get(configstore.get('tokens'), 'refresh_token'); - if (refreshToken === currentRefreshToken) { - configstore.set('tokens', lastAccessToken); - } + var currentRefreshToken = _.get(configstore.get("tokens"), "refresh_token"); + if (refreshToken === currentRefreshToken) { + configstore.set("tokens", lastAccessToken); + } - return lastAccessToken; - }, function(err) { - if (_.get(err, 'context.body.error') === 'invalid_scope') { - throw new FirebaseError( - 'This command requires new authorization scopes not granted to your current session. Please run ' + chalk.bold('firebase login --reauth') + '\n\n' + - 'For CI servers and headless environments, generate a new token with ' + chalk.bold('firebase login:ci'), - {exit: 1} - ); - } + return lastAccessToken; + }, + function(err) { + if (_.get(err, "context.body.error") === "invalid_scope") { + throw new FirebaseError( + "This command requires new authorization scopes not granted to your current session. Please run " + + chalk.bold("firebase login --reauth") + + "\n\n" + + "For CI servers and headless environments, generate a new token with " + + chalk.bold("firebase login:ci"), + { exit: 1 } + ); + } - throw INVALID_CREDENTIAL_ERROR; - }); + throw INVALID_CREDENTIAL_ERROR; + } + ); }; var getAccessToken = function(refreshToken, authScopes) { @@ -257,22 +294,27 @@ var logout = function(refreshToken) { lastAccessToken = {}; } _logoutCurrentSession(refreshToken); - return api.request('GET', '/o/oauth2/revoke', { - origin: api.authOrigin, - data: { - token: refreshToken + return api.request( + "GET", + "/o/oauth2/revoke", + { + origin: api.authOrigin, + data: { + token: refreshToken, + }, + }, + function() { + throw new FirebaseError("Authentication Error.", { + exit: 1, + }); } - }, function() { - throw new FirebaseError('Authentication Error.', { - exit: 1 - }); - }); + ); }; var auth = { login: login, getAccessToken: getAccessToken, - logout: logout + logout: logout, }; module.exports = auth; diff --git a/lib/checkDupHostingKeys.js b/lib/checkDupHostingKeys.js index 93c1b6a0..aa6c5208 100644 --- a/lib/checkDupHostingKeys.js +++ b/lib/checkDupHostingKeys.js @@ -1,31 +1,49 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var utils = require('./utils'); -var chalk = require('chalk'); -var Config = require('./config'); -var FirebaseError = require('./error'); -var logger = require('./logger'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var utils = require("./utils"); +var chalk = require("chalk"); +var Config = require("./config"); +var FirebaseError = require("./error"); +var logger = require("./logger"); module.exports = function(options) { return new RSVP.Promise(function(resolve, reject) { var src = options.config._src; var legacyKeys = Config.LEGACY_HOSTING_KEYS; - var hasLegacyKeys = _.reduce(legacyKeys, function(result, key) { - return result || _.has(src, key); - }, false); + var hasLegacyKeys = _.reduce( + legacyKeys, + function(result, key) { + return result || _.has(src, key); + }, + false + ); - - if (hasLegacyKeys && _.has(src, ['hosting'])) { - utils.logWarning(chalk.bold.yellow('hosting: ') + 'We found a ' + chalk.bold('hosting') + ' key inside ' + chalk.bold('firebase.json') + ' as well as hosting configuration keys that are not nested inside the ' + chalk.bold('hosting') + ' key.'); - logger.info('\n\nPlease run ' + chalk.bold('firebase tools:migrate') + ' to fix this issue.'); - logger.info('Please note that this will overwrite any configuration keys nested inside the ' + chalk.bold('hosting') + ' key with configuration keys at the root level of ' + chalk.bold('firebase.json.')); - reject(new FirebaseError('Hosting key and legacy hosting keys are both present in firebase.json.')); + if (hasLegacyKeys && _.has(src, ["hosting"])) { + utils.logWarning( + chalk.bold.yellow("hosting: ") + + "We found a " + + chalk.bold("hosting") + + " key inside " + + chalk.bold("firebase.json") + + " as well as hosting configuration keys that are not nested inside the " + + chalk.bold("hosting") + + " key." + ); + logger.info("\n\nPlease run " + chalk.bold("firebase tools:migrate") + " to fix this issue."); + logger.info( + "Please note that this will overwrite any configuration keys nested inside the " + + chalk.bold("hosting") + + " key with configuration keys at the root level of " + + chalk.bold("firebase.json.") + ); + reject( + new FirebaseError("Hosting key and legacy hosting keys are both present in firebase.json.") + ); } else { resolve(); } }); }; - diff --git a/lib/checkFirebaseSDKVersion.js b/lib/checkFirebaseSDKVersion.js index 140a3464..0ff53513 100644 --- a/lib/checkFirebaseSDKVersion.js +++ b/lib/checkFirebaseSDKVersion.js @@ -1,36 +1,36 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var path = require('path'); -var RSVP = require('rsvp'); -var semver = require('semver'); -var spawn = require('cross-spawn'); +var chalk = require("chalk"); +var path = require("path"); +var RSVP = require("rsvp"); +var semver = require("semver"); +var spawn = require("cross-spawn"); -var utils = require('./utils'); -var logger = require('./logger'); +var utils = require("./utils"); +var logger = require("./logger"); module.exports = function(options) { return new RSVP.Promise(function(resolve) { - if (!options.config.has('functions')) { + if (!options.config.has("functions")) { return resolve(); } try { var output; - var child = spawn('npm', ['outdated', 'firebase-functions', '--json=true'], { - cwd: path.join(options.config.projectDir, options.config.get('functions.source')), - stdio: [0, 'pipe', 2] + var child = spawn("npm", ["outdated", "firebase-functions", "--json=true"], { + cwd: path.join(options.config.projectDir, options.config.get("functions.source")), + stdio: [0, "pipe", 2], }); - child.on('error', function(err) { + child.on("error", function(err) { logger.debug(err.stack); return resolve(); }); - child.stdout.on('data', function(data) { - output = JSON.parse(data.toString('utf8')); + child.stdout.on("data", function(data) { + output = JSON.parse(data.toString("utf8")); }); - child.on('exit', function() { + child.on("exit", function() { return resolve(output); }); } catch (e) { @@ -40,17 +40,24 @@ module.exports = function(options) { if (!output) { return; } - var wanted = output['firebase-functions'].wanted; - var latest = output['firebase-functions'].latest; + var wanted = output["firebase-functions"].wanted; + var latest = output["firebase-functions"].latest; if (semver.lt(wanted, latest)) { - utils.logWarning(chalk.bold.yellow('functions: ') + 'package.json indicates an outdated version of firebase-functions.\n Please upgrade using ' - + chalk.bold('npm install --save firebase-functions@latest') + ' in your functions directory.'); - if (semver.satisfies(wanted, '0.x') && semver.satisfies(latest, '1.x')) { - utils.logWarning(chalk.bold.yellow('functions: ') + 'Please note that there will be breaking changes when you upgrade.\n Go to ' - + chalk.bold('https://firebase.google.com/docs/functions/beta-v1-diff') + ' to learn more.'); + utils.logWarning( + chalk.bold.yellow("functions: ") + + "package.json indicates an outdated version of firebase-functions.\n Please upgrade using " + + chalk.bold("npm install --save firebase-functions@latest") + + " in your functions directory." + ); + if (semver.satisfies(wanted, "0.x") && semver.satisfies(latest, "1.x")) { + utils.logWarning( + chalk.bold.yellow("functions: ") + + "Please note that there will be breaking changes when you upgrade.\n Go to " + + chalk.bold("https://firebase.google.com/docs/functions/beta-v1-diff") + + " to learn more." + ); } } }); }; - diff --git a/lib/checkValidTargetFilters.js b/lib/checkValidTargetFilters.js index dea87129..3abc2b1b 100644 --- a/lib/checkValidTargetFilters.js +++ b/lib/checkValidTargetFilters.js @@ -1,13 +1,13 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var FirebaseError = require('./error'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var FirebaseError = require("./error"); module.exports = function(options) { function numFilters(targetTypes) { return _.filter(options.only, function(opt) { - var optChunks = opt.split(':'); + var optChunks = opt.split(":"); return _.includes(targetTypes, optChunks[0]) && optChunks.length > 1; }).length; } @@ -23,13 +23,27 @@ module.exports = function(options) { return resolve(); } if (options.except) { - return reject(new FirebaseError('Cannot specify both --only and --except', {exit: 1})); + return reject( + new FirebaseError("Cannot specify both --only and --except", { + exit: 1, + }) + ); } - if (targetContainsFilter(['database', 'storage', 'hosting'])) { - return reject(new FirebaseError('Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions', {exit: 1})); + if (targetContainsFilter(["database", "storage", "hosting"])) { + return reject( + new FirebaseError( + "Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions", + { exit: 1 } + ) + ); } - if (targetContainsFilter(['functions']) && targetDoesNotContainFilter(['functions'])) { - return reject(new FirebaseError('Cannot specify "--only functions" and "--only functions:" at the same time', {exit: 1})); + if (targetContainsFilter(["functions"]) && targetDoesNotContainFilter(["functions"])) { + return reject( + new FirebaseError( + 'Cannot specify "--only functions" and "--only functions:" at the same time', + { exit: 1 } + ) + ); } return resolve(); }); diff --git a/lib/command.js b/lib/command.js index 44cf7e10..4b6dd61a 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,21 +1,21 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var _ = require('lodash'); -var track = require('./track'); -var logger = require('./logger'); -var utils = require('./utils'); -var FirebaseError = require('./error'); -var chalk = require('chalk'); -var getProjectId = require('./getProjectId'); -var RC = require('./rc'); -var Config = require('./config'); -var detectProjectRoot = require('./detectProjectRoot'); -var configstore = require('../lib/configstore'); +var RSVP = require("rsvp"); +var _ = require("lodash"); +var track = require("./track"); +var logger = require("./logger"); +var utils = require("./utils"); +var FirebaseError = require("./error"); +var chalk = require("chalk"); +var getProjectId = require("./getProjectId"); +var RC = require("./rc"); +var Config = require("./config"); +var detectProjectRoot = require("./detectProjectRoot"); +var configstore = require("../lib/configstore"); var Command = function(cmd) { this._cmd = cmd; - this._name = _.first(cmd.split(' ')); + this._name = _.first(cmd.split(" ")); this._description = null; this._options = []; this._action = null; @@ -35,7 +35,7 @@ Command.prototype.option = function() { Command.prototype.before = function(fn, args) { this._befores.push({ fn: fn, - args: args + args: args, }); return this; }; @@ -51,8 +51,12 @@ Command.prototype.register = function(client) { this.client = client; var program = client.cli; var cmd = program.command(this._cmd); - if (this._description) { cmd.description(this._description); } - this._options.forEach(function(args) { cmd.option.apply(cmd, args); }); + if (this._description) { + cmd.description(this._description); + } + this._options.forEach(function(args) { + cmd.option.apply(cmd, args); + }); var self = this; cmd.action(function() { @@ -62,38 +66,58 @@ Command.prototype.register = function(client) { var argCount = cmd._args.length; if (arguments.length - 1 > argCount) { return client.errorOut( - new FirebaseError('Too many arguments. Run ' + chalk.bold('firebase help ' + cmd._name) + ' for usage instructions', {exit: 1}) + new FirebaseError( + "Too many arguments. Run " + + chalk.bold("firebase help " + cmd._name) + + " for usage instructions", + { exit: 1 } + ) ); } - runner.apply(self, arguments).then(function(result) { - if (utils.getInheritedOption(options, 'json')) { - console.log(JSON.stringify({ - status: 'success', - result: result - }, null, 2)); - } - var duration = new Date().getTime() - start; - track(self._name, 'success', duration).then(process.exit); - }).catch(function(err) { - if (utils.getInheritedOption(options, 'json')) { - console.log(JSON.stringify({ - status: 'error', - error: err.message - }, null, 2)); - } - var duration = Date.now() - start; - var errorEvent = err.exit === 1 ? 'Error (User)' : 'Error (Unexpected)'; - var projectId = getProjectId(options, true); - var preppedMessage = chalk.stripColor(err.message || '').replace(projectId, ''); + runner + .apply(self, arguments) + .then(function(result) { + if (utils.getInheritedOption(options, "json")) { + console.log( + JSON.stringify( + { + status: "success", + result: result, + }, + null, + 2 + ) + ); + } + var duration = new Date().getTime() - start; + track(self._name, "success", duration).then(process.exit); + }) + .catch(function(err) { + if (utils.getInheritedOption(options, "json")) { + console.log( + JSON.stringify( + { + status: "error", + error: err.message, + }, + null, + 2 + ) + ); + } + var duration = Date.now() - start; + var errorEvent = err.exit === 1 ? "Error (User)" : "Error (Unexpected)"; + var projectId = getProjectId(options, true); + var preppedMessage = chalk.stripColor(err.message || "").replace(projectId, ""); - RSVP.all([ - track(self._name, 'error', duration), - track(errorEvent, preppedMessage, duration) - ]).then(function() { - client.errorOut(err); + RSVP.all([ + track(self._name, "error", duration), + track(errorEvent, preppedMessage, duration), + ]).then(function() { + client.errorOut(err); + }); }); - }); return cmd; }); @@ -101,22 +125,22 @@ Command.prototype.register = function(client) { Command.prototype._prepare = function(options) { options = options || {}; - options.project = utils.getInheritedOption(options, 'project'); + options.project = utils.getInheritedOption(options, "project"); - if (!process.stdin.isTTY || utils.getInheritedOption(options, 'nonInteractive')) { + if (!process.stdin.isTTY || utils.getInheritedOption(options, "nonInteractive")) { options.nonInteractive = true; } // allow override of detected non-interactive with --interactive flag - if (utils.getInheritedOption(options, 'interactive')) { + if (utils.getInheritedOption(options, "interactive")) { options.nonInteractive = false; } - if (utils.getInheritedOption(options, 'debug')) { - logger.transports.console.level = 'debug'; + if (utils.getInheritedOption(options, "debug")) { + logger.transports.console.level = "debug"; } - if (utils.getInheritedOption(options, 'json')) { + if (utils.getInheritedOption(options, "json")) { options.nonInteractive = true; - logger.transports.console.level = 'none'; + logger.transports.console.level = "none"; } try { @@ -137,7 +161,8 @@ Command.prototype.applyRC = function(options) { var rc = RC.load(options.cwd); options.rc = rc; - options.project = options.project || (configstore.get('activeProjects') || {})[options.projectRoot]; + options.project = + options.project || (configstore.get("activeProjects") || {})[options.projectRoot]; // support deprecated "firebase" key in firebase.json if (options.config && !options.project) { options.project = options.config.defaults.project; diff --git a/lib/config.js b/lib/config.js index b113779f..5cc3901f 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,20 +1,20 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var cjson = require('cjson'); -var fs = require('fs-extra'); -var path = require('path'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +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 utils = require('./utils'); +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 utils = require("./utils"); var Config = function(src, options) { this.options = options || {}; @@ -27,11 +27,16 @@ var Config = function(src, options) { if (this._src.firebase) { this.defaults.project = this._src.firebase; - utils.logWarning(chalk.bold('"firebase"') + ' key in firebase.json is deprecated. Run ' + chalk.bold('firebase use --add') + ' instead'); + utils.logWarning( + chalk.bold('"firebase"') + + " key in firebase.json is deprecated. Run " + + chalk.bold("firebase use --add") + + " instead" + ); } - if (_.has(this._src, 'rules')) { - _.set(this._src, 'database.rules', this._src.rules); + if (_.has(this._src, "rules")) { + _.set(this._src, "database.rules", this._src.rules); } Config.MATERIALIZE_TARGETS.forEach(function(target) { @@ -41,8 +46,12 @@ var Config = function(src, options) { }, this); // auto-detect functions from package.json in directory - if (this.projectDir && !this.get('functions.source') && fsutils.fileExistsSync(this.path('functions/package.json'))) { - this.set('functions.source', 'functions'); + if ( + this.projectDir && + !this.get("functions.source") && + fsutils.fileExistsSync(this.path("functions/package.json")) + ) { + this.set("functions.source", "functions"); } // use 'public' as signal for legacy hosting since it's a required key @@ -51,20 +60,30 @@ var Config = function(src, options) { } }; -Config.FILENAME = 'firebase.json'; -Config.MATERIALIZE_TARGETS = ['database', 'functions', 'hosting', 'storage', 'firestore']; -Config.LEGACY_HOSTING_KEYS = ['public', 'rewrites', 'redirects', 'headers', 'ignore', 'cleanUrls', 'trailingSlash']; +Config.FILENAME = "firebase.json"; +Config.MATERIALIZE_TARGETS = ["database", "functions", "hosting", "storage", "firestore"]; +Config.LEGACY_HOSTING_KEYS = [ + "public", + "rewrites", + "redirects", + "headers", + "ignore", + "cleanUrls", + "trailingSlash", +]; Config.prototype.importLegacyHostingKeys = function() { var found = false; Config.LEGACY_HOSTING_KEYS.forEach(function(key) { if (_.has(this._src, key)) { found = true; - this.set('hosting.' + key, this._src[key]); + this.set("hosting." + key, this._src[key]); } }, this); if (found) { - utils.logWarning('Deprecation Warning: Firebase Hosting configuration should be moved under "hosting" key.'); + utils.logWarning( + 'Deprecation Warning: Firebase Hosting configuration should be moved under "hosting" key.' + ); } }; @@ -86,7 +105,7 @@ Config.prototype._materialize = function(target) { if (_.isString(val)) { var out = this._parseFile(target, val); // if e.g. rules.json has {"rules": {}} use that - var lastSegment = _.last(target.split('.')); + var lastSegment = _.last(target.split(".")); if (_.size(out) === 1 && _.has(out, lastSegment)) { out = out[lastSegment]; } @@ -95,33 +114,40 @@ Config.prototype._materialize = function(target) { return val; } - throw new FirebaseError('Parse Error: "' + target + '" must be object or import path', {exit: 1}); + throw new FirebaseError('Parse Error: "' + target + '" must be object or import path', { + exit: 1, + }); }; Config.prototype._parseFile = function(target, filePath) { var fullPath = resolveProjectPath(this.options.cwd, filePath); var ext = path.extname(filePath); if (!fsutils.fileExistsSync(fullPath)) { - throw new FirebaseError('Parse Error: Imported file ' + filePath + ' does not exist', {exit: 1}); + throw new FirebaseError("Parse Error: Imported file " + filePath + " does not exist", { + exit: 1, + }); } switch (ext) { - case '.json': - if (target === 'database') { - this.notes.databaseRules = 'json'; - } else if (target === 'database.rules') { - this.notes.databaseRulesFile = filePath; - return fs.readFileSync(fullPath, 'utf8'); - } - return loadCJSON(fullPath); - /* istanbul ignore-next */ - case '.bolt': - if (target === 'database') { - this.notes.databaseRules = 'bolt'; - } - return parseBoltRules(fullPath); - default: - throw new FirebaseError('Parse Error: ' + filePath + ' is not of a supported config file type', {exit: 1}); + case ".json": + if (target === "database") { + this.notes.databaseRules = "json"; + } else if (target === "database.rules") { + this.notes.databaseRulesFile = filePath; + return fs.readFileSync(fullPath, "utf8"); + } + return loadCJSON(fullPath); + /* istanbul ignore-next */ + case ".bolt": + if (target === "database") { + this.notes.databaseRules = "bolt"; + } + return parseBoltRules(fullPath); + default: + throw new FirebaseError( + "Parse Error: " + filePath + " is not of a supported config file type", + { exit: 1 } + ); } }; @@ -139,8 +165,8 @@ Config.prototype.has = function(key) { Config.prototype.path = function(pathName) { var outPath = path.normalize(path.join(this.projectDir, pathName)); - if (_.includes(path.relative(this.projectDir, outPath), '..')) { - throw new FirebaseError(chalk.bold(pathName) + ' is outside of project directory', {exit: 1}); + if (_.includes(path.relative(this.projectDir, outPath), "..")) { + throw new FirebaseError(chalk.bold(pathName) + " is outside of project directory", { exit: 1 }); } return outPath; }; @@ -148,7 +174,7 @@ Config.prototype.path = function(pathName) { Config.prototype.readProjectFile = function(p, options) { options = options || {}; try { - var content = fs.readFileSync(this.path(p), 'utf8'); + var content = fs.readFileSync(this.path(p), "utf8"); if (options.json) { return JSON.parse(content); } @@ -162,12 +188,12 @@ Config.prototype.readProjectFile = function(p, options) { }; Config.prototype.writeProjectFile = function(p, content) { - if (typeof content !== 'string') { - content = JSON.stringify(content, null, 2) + '\n'; + if (typeof content !== "string") { + content = JSON.stringify(content, null, 2) + "\n"; } fs.ensureFileSync(this.path(p)); - fs.writeFileSync(this.path(p), content, 'utf8'); + fs.writeFileSync(this.path(p), content, "utf8"); }; Config.prototype.askWriteProjectFile = function(p, content) { @@ -175,9 +201,9 @@ Config.prototype.askWriteProjectFile = function(p, content) { var next; if (fsutils.fileExistsSync(writeTo)) { next = prompt.once({ - type: 'confirm', - message: 'File ' + chalk.underline(p) + ' already exists. Overwrite?', - default: false + type: "confirm", + message: "File " + chalk.underline(p) + " already exists. Overwrite?", + default: false, }); } else { next = RSVP.resolve(true); @@ -187,9 +213,9 @@ Config.prototype.askWriteProjectFile = function(p, content) { return next.then(function(result) { if (result) { self.writeProjectFile(p, content); - utils.logSuccess('Wrote ' + chalk.bold(p)); + utils.logSuccess("Wrote " + chalk.bold(p)); } else { - utils.logBullet('Skipping write of ' + chalk.bold(p)); + utils.logBullet("Skipping write of " + chalk.bold(p)); } }); }; @@ -201,8 +227,8 @@ Config.load = function(options, allowMissing) { var data = cjson.load(path.join(pd, Config.FILENAME)); return new Config(data, options); } catch (e) { - throw new FirebaseError('There was an error loading firebase.json:\n\n' + e.message, { - exit: 1 + throw new FirebaseError("There was an error loading firebase.json:\n\n" + e.message, { + exit: 1, }); } } @@ -211,7 +237,9 @@ Config.load = function(options, allowMissing) { return null; } - throw new FirebaseError('Not in a Firebase app directory (could not locate firebase.json)', {exit: 1}); + throw new FirebaseError("Not in a Firebase app directory (could not locate firebase.json)", { + exit: 1, + }); }; module.exports = Config; diff --git a/lib/configstore.js b/lib/configstore.js index f3f5113b..899e36ec 100644 --- a/lib/configstore.js +++ b/lib/configstore.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -var Configstore = require('configstore'); -var pkg = require('../package.json'); +var Configstore = require("configstore"); +var pkg = require("../package.json"); // Init a Configstore instance with an unique ID eg. package name // and optionally some default values diff --git a/lib/deploy/database/index.js b/lib/deploy/database/index.js index 357454fa..523f4ccf 100644 --- a/lib/deploy/database/index.js +++ b/lib/deploy/database/index.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; module.exports = { - prepare: require('./prepare'), - release: require('./release') + prepare: require("./prepare"), + release: require("./release"), }; diff --git a/lib/deploy/database/prepare.js b/lib/deploy/database/prepare.js index eb3830f4..6c419bf4 100644 --- a/lib/deploy/database/prepare.js +++ b/lib/deploy/database/prepare.js @@ -1,27 +1,25 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var path = require('path'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var path = require("path"); +var RSVP = require("rsvp"); -var FirebaseError = require('../../error'); -var parseBoltRules = require('../../parseBoltRules'); -var rtdb = require('../../rtdb'); -var utils = require('../../utils'); +var FirebaseError = require("../../error"); +var parseBoltRules = require("../../parseBoltRules"); +var rtdb = require("../../rtdb"); +var utils = require("../../utils"); module.exports = function(context, options) { - var rulesConfig = options.config.get('database'); + var rulesConfig = options.config.get("database"); var next = RSVP.resolve(); if (!rulesConfig) { return next; } - if (_.isString(_.get(rulesConfig, 'rules'))) { - rulesConfig = [ - _.assign(rulesConfig, {instance: options.instance}) - ]; + if (_.isString(_.get(rulesConfig, "rules"))) { + rulesConfig = [_.assign(rulesConfig, { instance: options.instance })]; } var ruleFiles = {}; @@ -35,11 +33,13 @@ module.exports = function(context, options) { ruleFiles[ruleConfig.rules] = null; if (ruleConfig.target) { - options.rc.requireTarget(context.projectId, 'database', ruleConfig.target); - var instances = options.rc.target(context.projectId, 'database', ruleConfig.target); - deploys = deploys.concat(instances.map(function(inst) { - return {instance: inst, rules: ruleConfig.rules}; - })); + options.rc.requireTarget(context.projectId, "database", ruleConfig.target); + var instances = options.rc.target(context.projectId, "database", ruleConfig.target); + deploys = deploys.concat( + instances.map(function(inst) { + return { instance: inst, rules: ruleConfig.rules }; + }) + ); } else if (!ruleConfig.instance) { throw new FirebaseError('Must supply either "target" or "instance" in database config'); } else { @@ -49,25 +49,34 @@ module.exports = function(context, options) { _.forEach(ruleFiles, function(v, file) { switch (path.extname(file)) { - case '.json': - ruleFiles[file] = options.config.readProjectFile(file); - break; - case '.bolt': - ruleFiles[file] = parseBoltRules(file); - break; - default: - throw new FirebaseError('Unexpected rules format ' + path.extname(file)); + case ".json": + ruleFiles[file] = options.config.readProjectFile(file); + break; + case ".bolt": + ruleFiles[file] = parseBoltRules(file); + break; + default: + throw new FirebaseError("Unexpected rules format " + path.extname(file)); } }); context.database = { deploys: deploys, - ruleFiles: ruleFiles + ruleFiles: ruleFiles, }; - utils.logBullet(chalk.bold.cyan('database: ') + 'checking rules syntax...'); - return Promise.all(deploys.map(function(deploy) { - return rtdb.updateRules(deploy.instance, ruleFiles[deploy.rules], {dryRun: true}).then(function() { - utils.logSuccess(chalk.bold.green('database: ') + 'rules syntax for database ' + chalk.bold(deploy.instance) + ' is valid'); - }); - })); + utils.logBullet(chalk.bold.cyan("database: ") + "checking rules syntax..."); + return Promise.all( + deploys.map(function(deploy) { + return rtdb + .updateRules(deploy.instance, ruleFiles[deploy.rules], { dryRun: true }) + .then(function() { + utils.logSuccess( + chalk.bold.green("database: ") + + "rules syntax for database " + + chalk.bold(deploy.instance) + + " is valid" + ); + }); + }) + ); }; diff --git a/lib/deploy/database/release.js b/lib/deploy/database/release.js index acd4439b..cc82e362 100644 --- a/lib/deploy/database/release.js +++ b/lib/deploy/database/release.js @@ -1,10 +1,10 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var rtdb = require('../../rtdb'); -var utils = require('../../utils'); +var rtdb = require("../../rtdb"); +var utils = require("../../utils"); module.exports = function(context) { if (!context.database || !context.database.deploys || !context.database.ruleFiles) { @@ -14,10 +14,21 @@ module.exports = function(context) { var deploys = context.database.deploys; var ruleFiles = context.database.ruleFiles; - utils.logBullet(chalk.bold.cyan('database: ') + 'releasing rules...'); - return RSVP.all(deploys.map(function(deploy) { - return rtdb.updateRules(deploy.instance, ruleFiles[deploy.rules], {dryRun: false}).then(function() { - utils.logSuccess(chalk.bold.green('database: ') + 'rules for database ' + chalk.bold(deploy.instance) + ' released successfully'); - }); - })); + utils.logBullet(chalk.bold.cyan("database: ") + "releasing rules..."); + return RSVP.all( + deploys.map(function(deploy) { + return rtdb + .updateRules(deploy.instance, ruleFiles[deploy.rules], { + dryRun: false, + }) + .then(function() { + utils.logSuccess( + chalk.bold.green("database: ") + + "rules for database " + + chalk.bold(deploy.instance) + + " released successfully" + ); + }); + }) + ); }; diff --git a/lib/deploy/firestore/deploy.js b/lib/deploy/firestore/deploy.js index aa2f2f8a..d26e6084 100644 --- a/lib/deploy/firestore/deploy.js +++ b/lib/deploy/firestore/deploy.js @@ -1,11 +1,11 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var firestoreIndexes = require('../../firestore/indexes'); -var logger = require('../../logger'); -var RSVP = require('rsvp'); -var utils = require('../../utils'); +var _ = require("lodash"); +var chalk = require("chalk"); +var firestoreIndexes = require("../../firestore/indexes"); +var logger = require("../../logger"); +var RSVP = require("rsvp"); +var utils = require("../../utils"); /** * Create an index if it does not already exist on the specified project. @@ -21,11 +21,11 @@ var _createIfMissing = function(projectId, index, existingIndexes) { }); if (exists) { - logger.debug('Skipping existing index: ' + JSON.stringify(index)); + logger.debug("Skipping existing index: " + JSON.stringify(index)); return RSVP.resolve(); } - logger.debug('Creating new index: ' + JSON.stringify(index)); + logger.debug("Creating new index: " + JSON.stringify(index)); return firestoreIndexes.create(projectId, index); }; @@ -44,10 +44,9 @@ var _createAllChained = function(projectId, indexes, existingIndexes) { } var index = indexes.shift(); - return _createIfMissing(projectId, index, existingIndexes) - .then(function() { - return _createAllChained(projectId, indexes, existingIndexes); - }); + return _createIfMissing(projectId, index, existingIndexes).then(function() { + return _createAllChained(projectId, indexes, existingIndexes); + }); }; /** @@ -70,8 +69,10 @@ var _logAllMissing = function(indexes, existingIndexes) { if (missingIndexes.length > 0) { logger.info(); - logger.info(chalk.bold('NOTE: ') - + 'The following indexes are already deployed but not present in the specified indexes file:'); + logger.info( + chalk.bold("NOTE: ") + + "The following indexes are already deployed but not present in the specified indexes file:" + ); missingIndexes.forEach(function(index) { logger.info(firestoreIndexes.toPrettyString(index)); @@ -81,17 +82,21 @@ var _logAllMissing = function(indexes, existingIndexes) { }; function _deployRules(context) { - var rulesDeploy = _.get(context, 'firestore.rulesDeploy'); - if (!context.firestoreRules || !rulesDeploy) { return RSVP.resolve(); } + var rulesDeploy = _.get(context, "firestore.rulesDeploy"); + if (!context.firestoreRules || !rulesDeploy) { + return RSVP.resolve(); + } return rulesDeploy.createRulesets(); } function _deployIndexes(context, options) { - if (!context.firestoreIndexes) { return RSVP.resolve(); } + if (!context.firestoreIndexes) { + return RSVP.resolve(); + } - var indexesSrc = _.get(context, 'firestore.indexes.content'); + var indexesSrc = _.get(context, "firestore.indexes.content"); if (!indexesSrc) { - logger.debug('No Firestore indexes present.'); + logger.debug("No Firestore indexes present."); return RSVP.resolve(); } @@ -110,8 +115,5 @@ function _deployIndexes(context, options) { * Deploy indexes. */ module.exports = function(context, options) { - return RSVP.all([ - _deployRules(context), - _deployIndexes(context, options) - ]); + return RSVP.all([_deployRules(context), _deployIndexes(context, options)]); }; diff --git a/lib/deploy/firestore/index.js b/lib/deploy/firestore/index.js index a832046c..8368af69 100644 --- a/lib/deploy/firestore/index.js +++ b/lib/deploy/firestore/index.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; module.exports = { - prepare: require('./prepare'), - deploy: require('./deploy'), - release: require('./release') + prepare: require("./prepare"), + deploy: require("./deploy"), + release: require("./release"), }; diff --git a/lib/deploy/firestore/prepare.js b/lib/deploy/firestore/prepare.js index 895920f0..bd9e285c 100644 --- a/lib/deploy/firestore/prepare.js +++ b/lib/deploy/firestore/prepare.js @@ -1,18 +1,18 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var firestoreIndexes = require('../../firestore/indexes'); -var RulesDeploy = require('../../RulesDeploy'); +var firestoreIndexes = require("../../firestore/indexes"); +var RulesDeploy = require("../../RulesDeploy"); function _prepareRules(context, options) { var prepare = RSVP.resolve(); - var rulesFile = options.config.get('firestore.rules'); + var rulesFile = options.config.get("firestore.rules"); if (context.firestoreRules && rulesFile) { - var rulesDeploy = new RulesDeploy(options, 'firestore'); - _.set(context, 'firestore.rulesDeploy', rulesDeploy); + var rulesDeploy = new RulesDeploy(options, "firestore"); + _.set(context, "firestore.rulesDeploy", rulesDeploy); rulesDeploy.addFile(rulesFile); prepare = rulesDeploy.compile(); } @@ -21,7 +21,7 @@ function _prepareRules(context, options) { } function _prepareIndexes(context, options) { - if (!context.firestoreIndexes || !options.config.get('firestore.indexes')) { + if (!context.firestoreIndexes || !options.config.get("firestore.indexes")) { return RSVP.resolve(); } @@ -30,10 +30,10 @@ function _prepareIndexes(context, options) { module.exports = function(context, options) { if (options.only) { - var targets = options.only.split(','); - var onlyIndexes = targets.indexOf('firestore:indexes') >= 0; - var onlyRules = targets.indexOf('firestore:rules') >= 0; - var onlyFirestore = targets.indexOf('firestore') >= 0; + var targets = options.only.split(","); + var onlyIndexes = targets.indexOf("firestore:indexes") >= 0; + var onlyRules = targets.indexOf("firestore:rules") >= 0; + var onlyFirestore = targets.indexOf("firestore") >= 0; context.firestoreIndexes = onlyIndexes || onlyFirestore; context.firestoreRules = onlyRules || onlyFirestore; @@ -42,8 +42,5 @@ module.exports = function(context, options) { context.firestoreRules = true; } - return RSVP.all([ - _prepareRules(context, options), - _prepareIndexes(context, options) - ]); + return RSVP.all([_prepareRules(context, options), _prepareIndexes(context, options)]); }; diff --git a/lib/deploy/firestore/release.js b/lib/deploy/firestore/release.js index f8d291ec..e24e6bc5 100644 --- a/lib/deploy/firestore/release.js +++ b/lib/deploy/firestore/release.js @@ -1,14 +1,16 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var FIRESTORE_RELEASE_NAME = 'cloud.firestore'; +var FIRESTORE_RELEASE_NAME = "cloud.firestore"; function _releaseRules(context, options) { - var rulesDeploy = _.get(context, 'firestore.rulesDeploy'); - if (!context.firestoreRules || !rulesDeploy) { return RSVP.resolve(); } - return rulesDeploy.release(options.config.get('firestore.rules'), FIRESTORE_RELEASE_NAME); + var rulesDeploy = _.get(context, "firestore.rulesDeploy"); + if (!context.firestoreRules || !rulesDeploy) { + return RSVP.resolve(); + } + return rulesDeploy.release(options.config.get("firestore.rules"), FIRESTORE_RELEASE_NAME); } module.exports = function(context, options) { diff --git a/lib/deploy/functions/deploy.js b/lib/deploy/functions/deploy.js index bf9e6fb3..0a7eff50 100644 --- a/lib/deploy/functions/deploy.js +++ b/lib/deploy/functions/deploy.js @@ -1,12 +1,12 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); -var tmp = require('tmp'); -var utils = require('../../utils'); -var gcp = require('../../gcp'); -var prepareFunctionsUpload = require('../../prepareFunctionsUpload'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); +var tmp = require("tmp"); +var utils = require("../../utils"); +var gcp = require("../../gcp"); +var prepareFunctionsUpload = require("../../prepareFunctionsUpload"); var GCP_REGION = gcp.cloudfunctions.DEFAULT_REGION; @@ -14,29 +14,43 @@ tmp.setGracefulCleanup(); module.exports = function(context, options, payload) { var _uploadSource = function(source) { - return gcp.cloudfunctions.generateUploadUrl(context.projectId, GCP_REGION).then(function(uploadUrl) { - _.set(context, 'uploadUrl', uploadUrl); - uploadUrl = _.replace(uploadUrl, 'https://storage.googleapis.com', ''); - return gcp.storage.upload(source, uploadUrl); - }); + return gcp.cloudfunctions + .generateUploadUrl(context.projectId, GCP_REGION) + .then(function(uploadUrl) { + _.set(context, "uploadUrl", uploadUrl); + uploadUrl = _.replace(uploadUrl, "https://storage.googleapis.com", ""); + return gcp.storage.upload(source, uploadUrl); + }); }; - if (options.config.get('functions')) { - utils.logBullet(chalk.cyan.bold('functions:') + ' preparing ' + chalk.bold(options.config.get('functions.source')) + ' directory for uploading...'); + if (options.config.get("functions")) { + utils.logBullet( + chalk.cyan.bold("functions:") + + " preparing " + + chalk.bold(options.config.get("functions.source")) + + " directory for uploading..." + ); return prepareFunctionsUpload(context, options).then(function(result) { payload.functions = { - triggers: options.config.get('functions.triggers') + triggers: options.config.get("functions.triggers"), }; if (!result) { return undefined; } - return _uploadSource(result).then(function() { - utils.logSuccess(chalk.green.bold('functions:') + ' ' + chalk.bold(options.config.get('functions.source')) + ' folder uploaded successfully'); - }).catch(function(err) { - utils.logWarning(chalk.yellow('functions:') + ' Upload Error: ' + err.message); - return RSVP.reject(err); - }); + return _uploadSource(result) + .then(function() { + utils.logSuccess( + chalk.green.bold("functions:") + + " " + + chalk.bold(options.config.get("functions.source")) + + " folder uploaded successfully" + ); + }) + .catch(function(err) { + utils.logWarning(chalk.yellow("functions:") + " Upload Error: " + err.message); + return RSVP.reject(err); + }); }); } return RSVP.resolve(); diff --git a/lib/deploy/functions/index.js b/lib/deploy/functions/index.js index a832046c..8368af69 100644 --- a/lib/deploy/functions/index.js +++ b/lib/deploy/functions/index.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; module.exports = { - prepare: require('./prepare'), - deploy: require('./deploy'), - release: require('./release') + prepare: require("./prepare"), + deploy: require("./deploy"), + release: require("./release"), }; diff --git a/lib/deploy/functions/prepare.js b/lib/deploy/functions/prepare.js index 5b7803fe..2d106826 100644 --- a/lib/deploy/functions/prepare.js +++ b/lib/deploy/functions/prepare.js @@ -1,68 +1,87 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var cjson = require('cjson'); -var chalk = require('chalk'); -var path = require('path'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var cjson = require("cjson"); +var chalk = require("chalk"); +var path = require("path"); +var RSVP = require("rsvp"); -var ensureApiEnabled = require('../../ensureApiEnabled'); -var functionsConfig = require('../../functionsConfig'); -var fsutils = require('../../fsutils'); -var getProjectId = require('../../getProjectId'); -var logger = require('../../logger'); -var resolveProjectPath = require('../../resolveProjectPath'); -var utils = require('../../utils'); +var ensureApiEnabled = require("../../ensureApiEnabled"); +var functionsConfig = require("../../functionsConfig"); +var fsutils = require("../../fsutils"); +var getProjectId = require("../../getProjectId"); +var logger = require("../../logger"); +var resolveProjectPath = require("../../resolveProjectPath"); +var utils = require("../../utils"); var VALID_FUNCTION_NAME_REGEX = /^[a-z][a-zA-Z0-9_-]{1,62}$/i; module.exports = function(context, options, payload) { - if (!options.config.has('functions')) { + if (!options.config.has("functions")) { return RSVP.resolve(); } - var sourceDirName = options.config.get('functions.source'); + var sourceDirName = options.config.get("functions.source"); if (!fsutils.dirExistsSync(resolveProjectPath(options.cwd, sourceDirName))) { - var msg = 'could not deploy functions because the ' + chalk.bold('"' + sourceDirName + '"') + - ' directory was not found. Please create it or specify a different source directory in firebase.json'; + var msg = + "could not deploy functions because the " + + chalk.bold('"' + sourceDirName + '"') + + " directory was not found. Please create it or specify a different source directory in firebase.json"; - return utils.reject(msg, {exit: 1}); + return utils.reject(msg, { exit: 1 }); } // Function name validation var invalidNames = _.reject(_.keys(payload.functions), function(name) { - return _.startsWith(name, '.') || VALID_FUNCTION_NAME_REGEX.test(name); + return _.startsWith(name, ".") || VALID_FUNCTION_NAME_REGEX.test(name); }); if (!_.isEmpty(invalidNames)) { - return utils.reject(invalidNames.join(', ') + ' function name(s) must be a valid subdomain (lowercase letters, numbers and dashes)', {exit: 1}); + return utils.reject( + invalidNames.join(", ") + + " function name(s) must be a valid subdomain (lowercase letters, numbers and dashes)", + { exit: 1 } + ); } // Check main file specified in package.json is present var sourceDir = options.config.path(sourceDirName); - var packageJsonFile = path.join(sourceDir, 'package.json'); + var packageJsonFile = path.join(sourceDir, "package.json"); if (fsutils.fileExistsSync(packageJsonFile)) { try { var data = cjson.load(packageJsonFile); - logger.debug('> [functions] package.json contents:', JSON.stringify(data, null, 2)); - var indexJsFile = path.join(sourceDir, data.main || 'index.js'); + logger.debug("> [functions] package.json contents:", JSON.stringify(data, null, 2)); + var indexJsFile = path.join(sourceDir, data.main || "index.js"); if (!fsutils.fileExistsSync(indexJsFile)) { - return utils.reject(path.relative(options.config.projectDir, indexJsFile) + ' does not exist, can\'t deploy Firebase Functions', {exit: 1}); + return utils.reject( + path.relative(options.config.projectDir, indexJsFile) + + " does not exist, can't deploy Firebase Functions", + { exit: 1 } + ); } } catch (e) { - return utils.reject('There was an error reading ' + sourceDirName + path.sep + 'package.json:\n\n' + e.message, {exit: 1}); + return utils.reject( + "There was an error reading " + sourceDirName + path.sep + "package.json:\n\n" + e.message, + { exit: 1 } + ); } - } else if (!fsutils.fileExistsSync(path.join(sourceDir, 'function.js'))) { - return utils.reject('No npm package found in functions source directory. Please run \'npm init\' inside ' + sourceDirName, {exit: 1}); + } else if (!fsutils.fileExistsSync(path.join(sourceDir, "function.js"))) { + return utils.reject( + "No npm package found in functions source directory. Please run 'npm init' inside " + + sourceDirName, + { exit: 1 } + ); } var projectId = getProjectId(options); return RSVP.all([ - ensureApiEnabled.ensure(options.project, 'cloudfunctions.googleapis.com', 'functions'), - ensureApiEnabled.check(projectId, 'runtimeconfig.googleapis.com', 'runtimeconfig', true) - ]).then(function(results) { - _.set(context, 'runtimeConfigEnabled', results[1]); - return functionsConfig.getFirebaseConfig(options); - }).then(function(result) { - _.set(context, 'firebaseConfig', result); - }); + ensureApiEnabled.ensure(options.project, "cloudfunctions.googleapis.com", "functions"), + ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true), + ]) + .then(function(results) { + _.set(context, "runtimeConfigEnabled", results[1]); + return functionsConfig.getFirebaseConfig(options); + }) + .then(function(result) { + _.set(context, "firebaseConfig", result); + }); }; diff --git a/lib/deploy/functions/release.js b/lib/deploy/functions/release.js index 27c78cb7..80bb3b47 100644 --- a/lib/deploy/functions/release.js +++ b/lib/deploy/functions/release.js @@ -1,18 +1,18 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var _ = require('lodash'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var FirebaseError = require('../../error'); -var gcp = require('../../gcp'); -var logger = require('../../logger'); -var track = require('../../track'); -var utils = require('../../utils'); -var pollOperation = require('../../pollOperations'); +var FirebaseError = require("../../error"); +var gcp = require("../../gcp"); +var logger = require("../../logger"); +var track = require("../../track"); +var utils = require("../../utils"); +var pollOperation = require("../../pollOperations"); module.exports = function(context, options, payload) { - if (!options.config.has('functions')) { + if (!options.config.has("functions")) { return RSVP.resolve(); } @@ -20,51 +20,62 @@ module.exports = function(context, options, payload) { var projectId = context.projectId; var sourceUrl = context.uploadUrl; // Used in CLI releases v3.4.0 to v3.17.6 - var legacySourceUrlTwo = 'gs://' + 'staging.' + context.firebaseConfig.storageBucket + '/firebase-functions-source'; + var legacySourceUrlTwo = + "gs://" + "staging." + context.firebaseConfig.storageBucket + "/firebase-functions-source"; // Used in CLI releases v3.3.0 and prior - var legacySourceUrlOne = 'gs://' + projectId + '-gcf/' + projectId; - var CLI_DEPLOYMENT_TOOL = 'cli-firebase'; + var legacySourceUrlOne = "gs://" + projectId + "-gcf/" + projectId; + var CLI_DEPLOYMENT_TOOL = "cli-firebase"; var CLI_DEPLOYMENT_LABELS = { - 'deployment-tool': CLI_DEPLOYMENT_TOOL + "deployment-tool": CLI_DEPLOYMENT_TOOL, }; var functionsInfo = payload.functions.triggers; - var uploadedNames = _.map(functionsInfo, 'name'); + var uploadedNames = _.map(functionsInfo, "name"); var timings = {}; var failedDeployments = 0; var deployments = []; function _startTimer(name, type) { - timings[name] = {type: type, t0: process.hrtime()}; + timings[name] = { type: type, t0: process.hrtime() }; } function _endTimer(name) { if (!timings[name]) { - logger.debug('[functions] no timer initialized for', name); + logger.debug("[functions] no timer initialized for", name); return; } // hrtime returns a duration as an array of [seconds, nanos] var duration = process.hrtime(timings[name].t0); - track('Functions Deploy (Duration)', timings[name].type, duration[0] * 1000 + Math.round(duration[1] * 1e-6)); + track( + "Functions Deploy (Duration)", + timings[name].type, + duration[0] * 1000 + Math.round(duration[1] * 1e-6) + ); } function _fetchTriggerUrls(ops) { - if (!_.find(ops, ['trigger.httpsTrigger', {}])) { + if (!_.find(ops, ["trigger.httpsTrigger", {}])) { // No HTTPS functions being deployed return RSVP.resolve(); } return gcp.cloudfunctions.list(projectId, GCP_REGION).then(function(functions) { - var httpFunctions = _.chain(functions).filter({ sourceUploadUrl: sourceUrl}).filter('httpsTrigger').value(); + var httpFunctions = _.chain(functions) + .filter({ sourceUploadUrl: sourceUrl }) + .filter("httpsTrigger") + .value(); _.forEach(httpFunctions, function(httpFunc) { - _.chain(ops).find({ func: httpFunc.name }).assign({ triggerUrl: httpFunc.httpsTrigger.url}).value(); + _.chain(ops) + .find({ func: httpFunc.name }) + .assign({ triggerUrl: httpFunc.httpsTrigger.url }) + .value(); }); return RSVP.resolve(); }); } function _functionMatchesGroup(functionName, groupChunks) { - return _.isEqual(groupChunks, functionName.split('-').slice(0, groupChunks.length)); + return _.isEqual(groupChunks, functionName.split("-").slice(0, groupChunks.length)); } function _getFilterGroups() { @@ -73,13 +84,15 @@ module.exports = function(context, options, payload) { } var opts; - return _.chain(options.only.split(',')) + return _.chain(options.only.split(",")) .filter(function(filter) { - opts = filter.split(':'); - return opts[0] === 'functions' && opts[1]; - }).map(function(filter) { - return filter.split(':')[1].split('.'); - }).value(); + opts = filter.split(":"); + return opts[0] === "functions" && opts[1]; + }) + .map(function(filter) { + return filter.split(":")[1].split("."); + }) + .value(); } function _getReleaseNames(uploadNames, existingNames, functionFilterGroups) { @@ -89,9 +102,11 @@ module.exports = function(context, options, payload) { var allFunctions = _.union(uploadNames, existingNames); return _.filter(allFunctions, function(functionName) { - return _.some(_.map(functionFilterGroups, function(groupChunks) { - return _functionMatchesGroup(functionName, groupChunks); - })); + return _.some( + _.map(functionFilterGroups, function(groupChunks) { + return _functionMatchesGroup(functionName, groupChunks); + }) + ); }); } @@ -100,27 +115,41 @@ module.exports = function(context, options, payload) { return; } - logger.debug('> [functions] filtering triggers to: ' + JSON.stringify(releaseNames, null, 2)); - track('Functions Deploy with Filter', '', releaseNames.length); + logger.debug("> [functions] filtering triggers to: " + JSON.stringify(releaseNames, null, 2)); + track("Functions Deploy with Filter", "", releaseNames.length); if (existingNames.length > 0) { - utils.logBullet(chalk.bold.cyan('functions: ') + 'current functions in project: ' + existingNames.join(', ')); + utils.logBullet( + chalk.bold.cyan("functions: ") + "current functions in project: " + existingNames.join(", ") + ); } if (releaseNames.length > 0) { - utils.logBullet(chalk.bold.cyan('functions: ') + 'uploading functions in project: ' + releaseNames.join(', ')); + utils.logBullet( + chalk.bold.cyan("functions: ") + + "uploading functions in project: " + + releaseNames.join(", ") + ); } var allFunctions = _.union(releaseNames, existingNames); var unmatchedFilters = _.chain(functionFilterGroups) .filter(function(filterGroup) { - return !_.some(_.map(allFunctions, function(functionName) { - return _functionMatchesGroup(functionName, filterGroup); - })); - }).map(function(group) { - return group.join('-'); - }).value(); + return !_.some( + _.map(allFunctions, function(functionName) { + return _functionMatchesGroup(functionName, filterGroup); + }) + ); + }) + .map(function(group) { + return group.join("-"); + }) + .value(); if (unmatchedFilters.length > 0) { - utils.logWarning(chalk.bold.yellow('functions: ') + 'the following filters were specified but do not match any functions in the project: ' + unmatchedFilters.join(', ')); + utils.logWarning( + chalk.bold.yellow("functions: ") + + "the following filters were specified but do not match any functions in the project: " + + unmatchedFilters.join(", ") + ); } } @@ -129,9 +158,17 @@ module.exports = function(context, options, payload) { // Poll less frequently when there are many operations to avoid hitting read quota. // See "Read requests" quota at https://cloud.google.com/console/apis/api/cloudfunctions/quotas if (_.size(operations) > 90) { - utils.logWarning(chalk.bold.yellow('functions:') + ' too many functions are being deployed, cannot poll status.'); - logger.info('In a few minutes, you can check status at ' + utils.consoleUrl(options.project, '/functions/logs')); - logger.info('You can use the --only flag to deploy only a portion of your functions in the future.'); + utils.logWarning( + chalk.bold.yellow("functions:") + + " too many functions are being deployed, cannot poll status." + ); + logger.info( + "In a few minutes, you can check status at " + + utils.consoleUrl(options.project, "/functions/logs") + ); + logger.info( + "You can use the --only flag to deploy only a portion of your functions in the future." + ); deployments = []; // prevents analytics tracking of deployments return RSVP.resolve(); } else if (_.size(operations) > 40) { @@ -144,19 +181,28 @@ module.exports = function(context, options, payload) { var pollFunction = gcp.cloudfunctions.check; var printSuccess = function(op) { _endTimer(op.functionName); - utils.logSuccess(chalk.bold.green('functions[' + op.functionName + ']: ') + 'Successful ' + op.type + ' operation. '); - if (op.triggerUrl && op.type !== 'delete') { - logger.info(chalk.bold('Function URL'), '(' + op.functionName + '):', op.triggerUrl); + utils.logSuccess( + chalk.bold.green("functions[" + op.functionName + "]: ") + + "Successful " + + op.type + + " operation. " + ); + if (op.triggerUrl && op.type !== "delete") { + logger.info(chalk.bold("Function URL"), "(" + op.functionName + "):", op.triggerUrl); } }; var printFail = function(op) { _endTimer(op.functionName); failedDeployments += 1; - utils.logWarning(chalk.bold.yellow('functions[' + op.functionName + ']: ') + 'Deployment error.'); + utils.logWarning( + chalk.bold.yellow("functions[" + op.functionName + "]: ") + "Deployment error." + ); if (op.error.code === 8) { logger.debug(op.error.message); - logger.info('You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, ' + - 'and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more.'); + logger.info( + "You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, " + + "and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more." + ); } else { logger.info(op.error.message); } @@ -165,10 +211,10 @@ module.exports = function(context, options, payload) { // The error codes from a Google.LongRunning operation follow google.rpc.Code format. var retryableCodes = [ - 1, // cancelled by client - 4, // deadline exceeded + 1, // cancelled by client + 4, // deadline exceeded 10, // aborted (typically due to concurrency issue) - 14 // unavailable + 14, // unavailable ]; if (_.includes(retryableCodes, result.error.code)) { @@ -176,163 +222,217 @@ module.exports = function(context, options, payload) { } return false; }; - return pollOperation.pollAndRetry(operations, pollFunction, interval, printSuccess, printFail, retryCondition); + return pollOperation.pollAndRetry( + operations, + pollFunction, + interval, + printSuccess, + printFail, + retryCondition + ); } function _getFunctionTrigger(functionInfo) { if (functionInfo.httpsTrigger) { - return _.pick(functionInfo, 'httpsTrigger'); + return _.pick(functionInfo, "httpsTrigger"); } else if (functionInfo.eventTrigger) { var trigger = functionInfo.eventTrigger; - return {eventTrigger: trigger}; + return { eventTrigger: trigger }; } - logger.debug('Unknown trigger type found in:', functionInfo); - return new FirebaseError('Could not parse function trigger, unknown trigger type.'); + logger.debug("Unknown trigger type found in:", functionInfo); + return new FirebaseError("Could not parse function trigger, unknown trigger type."); } delete payload.functions; - return gcp.cloudfunctions.list(projectId, GCP_REGION).then(function(existingFunctions) { - var pluckName = function(functionObject) { - var fullName = _.get(functionObject, 'name'); // e.g.'projects/proj1/locations/us-central1/functions/func' - return _.last(fullName.split('/')); - }; + return gcp.cloudfunctions + .list(projectId, GCP_REGION) + .then(function(existingFunctions) { + var pluckName = function(functionObject) { + var fullName = _.get(functionObject, "name"); // e.g.'projects/proj1/locations/us-central1/functions/func' + return _.last(fullName.split("/")); + }; - var existingNames = _.map(existingFunctions, pluckName); - var functionFilterGroups = _getFilterGroups(); - var releaseNames = _getReleaseNames(uploadedNames, existingNames, functionFilterGroups); - // If not using function filters, then `deleteReleaseNames` should be equivalent to existingNames so that intersection is a noop - var deleteReleaseNames = functionFilterGroups.length > 0 ? releaseNames : existingNames; + var existingNames = _.map(existingFunctions, pluckName); + var functionFilterGroups = _getFilterGroups(); + var releaseNames = _getReleaseNames(uploadedNames, existingNames, functionFilterGroups); + // If not using function filters, then `deleteReleaseNames` should be equivalent to existingNames so that intersection is a noop + var deleteReleaseNames = functionFilterGroups.length > 0 ? releaseNames : existingNames; - _logFilters(existingNames, releaseNames, functionFilterGroups); + _logFilters(existingNames, releaseNames, functionFilterGroups); - // Create functions - _.chain(uploadedNames) - .difference(existingNames) - .intersection(releaseNames) - .forEach(function(functionName) { - var functionInfo = _.find(functionsInfo, {'name': functionName}); - var functionTrigger = _getFunctionTrigger(functionInfo); - utils.logBullet(chalk.bold.cyan('functions: ') + 'creating function ' + chalk.bold(functionName) + '...'); - logger.debug('Trigger is: ', JSON.stringify(functionTrigger)); - var eventType = functionTrigger.eventTrigger ? functionTrigger.eventTrigger.eventType : 'https'; - _startTimer(functionName, 'create'); + // Create functions + _.chain(uploadedNames) + .difference(existingNames) + .intersection(releaseNames) + .forEach(function(functionName) { + var functionInfo = _.find(functionsInfo, { name: functionName }); + var functionTrigger = _getFunctionTrigger(functionInfo); + utils.logBullet( + chalk.bold.cyan("functions: ") + "creating function " + chalk.bold(functionName) + "..." + ); + logger.debug("Trigger is: ", JSON.stringify(functionTrigger)); + var eventType = functionTrigger.eventTrigger + ? functionTrigger.eventTrigger.eventType + : "https"; + _startTimer(functionName, "create"); - deployments.push({ - functionName: functionName, - retryFunction: function() { - return gcp.cloudfunctions.create({ - projectId: projectId, - region: GCP_REGION, - eventType: eventType, - functionName: functionName, - entryPoint: functionInfo.entryPoint, - trigger: functionTrigger, - labels: CLI_DEPLOYMENT_LABELS, - sourceUploadUrl: sourceUrl - }); - }, - trigger: functionTrigger - }); - }).value(); - - // Update functions - _.chain(uploadedNames) - .intersection(existingNames) - .intersection(releaseNames) - .forEach(function(functionName) { - var functionInfo = _.find(functionsInfo, {'name': functionName}); - var functionTrigger = _getFunctionTrigger(functionInfo); - utils.logBullet(chalk.bold.cyan('functions: ') + 'updating function ' + chalk.bold(functionName) + '...'); - logger.debug('Trigger is: ', JSON.stringify(functionTrigger)); - var eventType = functionTrigger.eventTrigger ? functionTrigger.eventTrigger.eventType : 'https'; - var existingFunction = _.find(existingFunctions, { functionName: functionName }); - var existingEventType = _.get(existingFunction, 'eventTrigger.eventType'); - var migratingTrigger = false; - if (eventType.match(/google.storage.object./) - && existingEventType === 'providers/cloud.storage/eventTypes/object.change') { - migratingTrigger = true; - } else if (eventType === 'google.pubsub.topic.publish' - && existingEventType === 'providers/cloud.pubsub/eventTypes/topic.publish') { - migratingTrigger = true; - } - if (migratingTrigger) { - throw new FirebaseError('Function ' + chalk.bold(functionName) + ' was deployed using a legacy ' + - 'trigger type and cannot be updated with the new SDK. To proceed with this deployment, you must first delete the ' + - 'function by visiting the Cloud Console at: https://console.cloud.google.com/functions/list?project=' + projectId + - '\n\nTo avoid service interruption, you may wish to create an identical function with a different name before ' + - 'deleting this function.\n'); - } else { - _startTimer(functionName, 'update'); deployments.push({ functionName: functionName, retryFunction: function() { - return gcp.cloudfunctions.update({ + return gcp.cloudfunctions.create({ + projectId: projectId, + region: GCP_REGION, + eventType: eventType, + functionName: functionName, + entryPoint: functionInfo.entryPoint, + trigger: functionTrigger, + labels: CLI_DEPLOYMENT_LABELS, + sourceUploadUrl: sourceUrl, + }); + }, + trigger: functionTrigger, + }); + }) + .value(); + + // Update functions + _.chain(uploadedNames) + .intersection(existingNames) + .intersection(releaseNames) + .forEach(function(functionName) { + var functionInfo = _.find(functionsInfo, { name: functionName }); + var functionTrigger = _getFunctionTrigger(functionInfo); + utils.logBullet( + chalk.bold.cyan("functions: ") + "updating function " + chalk.bold(functionName) + "..." + ); + logger.debug("Trigger is: ", JSON.stringify(functionTrigger)); + var eventType = functionTrigger.eventTrigger + ? functionTrigger.eventTrigger.eventType + : "https"; + var existingFunction = _.find(existingFunctions, { + functionName: functionName, + }); + var existingEventType = _.get(existingFunction, "eventTrigger.eventType"); + var migratingTrigger = false; + if ( + eventType.match(/google.storage.object./) && + existingEventType === "providers/cloud.storage/eventTypes/object.change" + ) { + migratingTrigger = true; + } else if ( + eventType === "google.pubsub.topic.publish" && + existingEventType === "providers/cloud.pubsub/eventTypes/topic.publish" + ) { + migratingTrigger = true; + } + if (migratingTrigger) { + throw new FirebaseError( + "Function " + + chalk.bold(functionName) + + " was deployed using a legacy " + + "trigger type and cannot be updated with the new SDK. To proceed with this deployment, you must first delete the " + + "function by visiting the Cloud Console at: https://console.cloud.google.com/functions/list?project=" + + projectId + + "\n\nTo avoid service interruption, you may wish to create an identical function with a different name before " + + "deleting this function.\n" + ); + } else { + _startTimer(functionName, "update"); + deployments.push({ + functionName: functionName, + retryFunction: function() { + return gcp.cloudfunctions.update({ + projectId: projectId, + region: GCP_REGION, + functionName: functionName, + trigger: functionTrigger, + sourceUploadUrl: sourceUrl, + labels: CLI_DEPLOYMENT_LABELS, + }); + }, + trigger: functionTrigger, + }); + } + }) + .value(); + + // Delete functions + _.chain(existingFunctions) + .filter(function(functionInfo) { + if (typeof functionInfo.labels === "undefined") { + return ( + functionInfo.sourceArchiveUrl === legacySourceUrlOne || + functionInfo.sourceArchiveUrl === legacySourceUrlTwo + ); + } + return functionInfo.labels["deployment-tool"] === CLI_DEPLOYMENT_TOOL; + }) // only delete functions uploaded via firebase-tools + .map(pluckName) + .difference(uploadedNames) + .intersection(deleteReleaseNames) + .map(function(functionName) { + utils.logBullet( + chalk.bold.cyan("functions: ") + "deleting function " + chalk.bold(functionName) + "..." + ); + _startTimer(functionName, "delete"); + deployments.push({ + functionName: functionName, + retryFunction: function() { + return gcp.cloudfunctions.delete({ projectId: projectId, region: GCP_REGION, functionName: functionName, - trigger: functionTrigger, - sourceUploadUrl: sourceUrl, - labels: CLI_DEPLOYMENT_LABELS }); }, - trigger: functionTrigger }); - } - }).value(); + }) + .value(); - // Delete functions - _.chain(existingFunctions) - .filter(function(functionInfo) { - if (typeof functionInfo.labels === 'undefined') { - return functionInfo.sourceArchiveUrl === legacySourceUrlOne || functionInfo.sourceArchiveUrl === legacySourceUrlTwo; - } - return functionInfo.labels['deployment-tool'] === CLI_DEPLOYMENT_TOOL; - }) // only delete functions uploaded via firebase-tools - .map(pluckName) - .difference(uploadedNames) - .intersection(deleteReleaseNames) - .map(function(functionName) { - utils.logBullet(chalk.bold.cyan('functions: ') + 'deleting function ' + chalk.bold(functionName) + '...'); - _startTimer(functionName, 'delete'); - deployments.push({ - functionName: functionName, - retryFunction: function() { - return gcp.cloudfunctions.delete({ - projectId: projectId, - region: GCP_REGION, - functionName: functionName - }); + return RSVP.allSettled( + _.map(deployments, function(op) { + return op.retryFunction().then(function(res) { + return _.merge(op, res); + }); + }) + ); + }) + .then(function(allOps) { + var failedCalls = _.chain(allOps) + .filter({ state: "rejected" }) + .map("reason") + .value(); + var successfulCalls = _.chain(allOps) + .filter({ state: "fulfilled" }) + .map("value") + .value(); + failedDeployments += failedCalls.length; + + return _fetchTriggerUrls(successfulCalls) + .then(function() { + return _pollAndManageOperations(successfulCalls).catch(function() { + utils.logWarning( + chalk.bold.yellow("functions:") + " failed to get status of all the deployments" + ); + logger.info( + "You can check on their status at " + + utils.consoleUrl(options.project, "/functions/logs") + ); + return RSVP.reject(new FirebaseError("Failed to get status of functions deployments.")); + }); + }) + .then(function() { + if (deployments.length > 0) { + track("Functions Deploy (Result)", "failure", failedDeployments); + track("Functions Deploy (Result)", "success", deployments.length - failedDeployments); + } + + if (failedDeployments > 0) { + logger.info( + "\n\nFunctions deploy had errors. To continue deploying other features (such as database), run:" + ); + logger.info(" " + chalk.bold("firebase deploy --except functions")); + return RSVP.reject(new FirebaseError("Functions did not deploy properly.")); } }); - }).value(); - - return RSVP.allSettled(_.map(deployments, function(op) { - return op.retryFunction().then(function(res) { - return _.merge(op, res); - }); - })); - }).then(function(allOps) { - var failedCalls = _.chain(allOps).filter({'state': 'rejected'}).map('reason').value(); - var successfulCalls = _.chain(allOps).filter({'state': 'fulfilled'}).map('value').value(); - failedDeployments += failedCalls.length; - - return _fetchTriggerUrls(successfulCalls).then(function() { - return _pollAndManageOperations(successfulCalls).catch(function() { - utils.logWarning(chalk.bold.yellow('functions:') + ' failed to get status of all the deployments'); - logger.info('You can check on their status at ' + utils.consoleUrl(options.project, '/functions/logs')); - return RSVP.reject(new FirebaseError('Failed to get status of functions deployments.')); - }); - }).then(function() { - if (deployments.length > 0) { - track('Functions Deploy (Result)', 'failure', failedDeployments); - track('Functions Deploy (Result)', 'success', deployments.length - failedDeployments); - } - - if (failedDeployments > 0) { - logger.info('\n\nFunctions deploy had errors. To continue deploying other features (such as database), run:'); - logger.info(' ' + chalk.bold('firebase deploy --except functions')); - return RSVP.reject(new FirebaseError('Functions did not deploy properly.')); - } }); - }); }; diff --git a/lib/deploy/hosting/deploy.js b/lib/deploy/hosting/deploy.js index 409aaaca..4f4cf74f 100644 --- a/lib/deploy/hosting/deploy.js +++ b/lib/deploy/hosting/deploy.js @@ -1,16 +1,16 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var fs = require('fs-extra'); -var ProgressBar = require('progress'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var fs = require("fs-extra"); +var ProgressBar = require("progress"); +var RSVP = require("rsvp"); -var api = require('../../api'); -var logger = require('../../logger'); -var FirebaseError = require('../../error'); -var prepareUpload = require('../../prepareUpload'); -var utils = require('../../utils'); +var api = require("../../api"); +var logger = require("../../logger"); +var FirebaseError = require("../../error"); +var prepareUpload = require("../../prepareUpload"); +var utils = require("../../utils"); module.exports = function(context, options, payload) { var local = context.hosting; @@ -19,20 +19,27 @@ module.exports = function(context, options, payload) { return RSVP.resolve(); } - utils.logBullet(chalk.cyan.bold('hosting:') + ' preparing ' + chalk.bold(payload.hosting.public) + ' directory for upload...'); + utils.logBullet( + chalk.cyan.bold("hosting:") + + " preparing " + + chalk.bold(payload.hosting.public) + + " directory for upload..." + ); return prepareUpload(options, payload.hosting).then(function(upload) { if (!upload.foundIndex) { - utils.logWarning(chalk.bold('Warning:') + ' Public directory does not contain index.html'); + utils.logWarning(chalk.bold("Warning:") + " Public directory does not contain index.html"); } if (!upload.manifest.length) { - return utils.reject('Must have at least one file in public directory to deploy.', {exit: 1}); + return utils.reject("Must have at least one file in public directory to deploy.", { + exit: 1, + }); } _.assign(payload.hosting, { version: local.versionId, - prefix: local.versionId + '/', - manifest: [] // manifest is currently unused, saving payload size + prefix: local.versionId + "/", + manifest: [], // manifest is currently unused, saving payload size // manifest: upload.manifest.map(function(file) { // return {path: file, object: file}; @@ -44,81 +51,93 @@ module.exports = function(context, options, payload) { var lastPercent = 0; var bar; if (!options.nonInteractive && process.stderr) { - bar = new ProgressBar(chalk.bold('Uploading:') + ' [:bar] :percent', { + bar = new ProgressBar(chalk.bold("Uploading:") + " [:bar] :percent", { total: upload.manifest.length, width: 40, - complete: chalk.green('='), - incomplete: ' ', - clear: true + complete: chalk.green("="), + incomplete: " ", + clear: true, }); } else { - process.stdout.write(chalk.cyan.bold('\ni') + chalk.bold(' Progress: [')); + process.stdout.write(chalk.cyan.bold("\ni") + chalk.bold(" Progress: [")); } - local.versionRef.on('value', function(snap) { - var status = snap.child('status').val(); + local.versionRef.on("value", function(snap) { + var status = snap.child("status").val(); switch (status) { - case 'deploying': - var uc = snap.child('uploadedCount').val() || 0; - var percent = Math.round(100.0 * uc / upload.manifest.length); - if (bar) { - bar.tick(uc - lastCount); - } else { - process.stdout.write(_.repeat(chalk.green('.'), percent - lastPercent)); - lastPercent = percent; - } - lastCount = uc; - break; - case 'deployed': - if (bar) { - bar.terminate(); - } else { - process.stdout.write(chalk.bold(']') + '\n\n'); - } - utils.logSuccess(chalk.green.bold('hosting:') + ' ' + upload.manifest.length + ' files uploaded successfully'); - local.versionRef.off('value'); - resolve(); - break; - case 'removed': - reject(new FirebaseError('Not Implemented', { - exit: 2, - context: snap.val() - })); - break; - case null: - break; - default: - var msg = 'File upload failed'; - if (snap.hasChild('statusMessage')) { - msg += ': ' + snap.child('statusMessage').val(); - } + case "deploying": + var uc = snap.child("uploadedCount").val() || 0; + var percent = Math.round(100.0 * uc / upload.manifest.length); + if (bar) { + bar.tick(uc - lastCount); + } else { + process.stdout.write(_.repeat(chalk.green("."), percent - lastPercent)); + lastPercent = percent; + } + lastCount = uc; + break; + case "deployed": + if (bar) { + bar.terminate(); + } else { + process.stdout.write(chalk.bold("]") + "\n\n"); + } + utils.logSuccess( + chalk.green.bold("hosting:") + + " " + + upload.manifest.length + + " files uploaded successfully" + ); + local.versionRef.off("value"); + resolve(); + break; + case "removed": + reject( + new FirebaseError("Not Implemented", { + exit: 2, + context: snap.val(), + }) + ); + break; + case null: + break; + default: + var msg = "File upload failed"; + if (snap.hasChild("statusMessage")) { + msg += ": " + snap.child("statusMessage").val(); + } - reject(new FirebaseError(msg, { - exit: 2, - context: snap.val() - })); + reject( + new FirebaseError(msg, { + exit: 2, + context: snap.val(), + }) + ); } }); - api.request('PUT', '/v1/hosting/' + options.instance + '/uploads/' + local.versionId, { - auth: true, - query: { - fileCount: upload.manifest.length, - message: options.message - }, - files: { - site: { - filename: 'site.tar.gz', - stream: upload.stream, - contentType: 'application/x-gzip', - knownLength: upload.size - } - }, - origin: api.deployOrigin - }).then(function() { - logger.debug('[hosting] .tgz uploaded successfully, waiting for extraction'); - fs.removeSync(upload.file); - }).catch(reject); + api + .request("PUT", "/v1/hosting/" + options.instance + "/uploads/" + local.versionId, { + auth: true, + query: { + fileCount: upload.manifest.length, + message: options.message, + }, + files: { + site: { + filename: "site.tar.gz", + stream: upload.stream, + contentType: "application/x-gzip", + knownLength: upload.size, + }, + }, + origin: api.deployOrigin, + }) + .then(function() { + logger.debug("[hosting] .tgz uploaded successfully, waiting for extraction"); + fs.removeSync(upload.file); + }) + .catch(reject); }); }); }; diff --git a/lib/deploy/hosting/index.js b/lib/deploy/hosting/index.js index dbdb0b6d..dea94c68 100644 --- a/lib/deploy/hosting/index.js +++ b/lib/deploy/hosting/index.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; module.exports = { - prepare: require('./prepare'), - deploy: require('./deploy') + prepare: require("./prepare"), + deploy: require("./deploy"), }; diff --git a/lib/deploy/hosting/prepare.js b/lib/deploy/hosting/prepare.js index 03b3cf9f..8211ca0b 100644 --- a/lib/deploy/hosting/prepare.js +++ b/lib/deploy/hosting/prepare.js @@ -1,15 +1,18 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var resolveProjectPath = require('../../resolveProjectPath'); -var fsutils = require('../../fsutils'); -var utils = require('../../utils'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var resolveProjectPath = require("../../resolveProjectPath"); +var fsutils = require("../../fsutils"); +var utils = require("../../utils"); module.exports = function(context, options, payload) { context.hosting = { // TODO: Get Firebase subdomain - not always the same as the projectId - versionRef: options.firebaseRef.child('hosting/versions').child(options.instance).push() + versionRef: options.firebaseRef + .child("hosting/versions") + .child(options.instance) + .push(), }; context.hosting.versionId = context.hosting.versionRef.key(); @@ -17,16 +20,18 @@ module.exports = function(context, options, payload) { if (options.public) { // trigger legacy key import since public may not exist in firebase.json options.config.importLegacyHostingKeys(); - options.config.set('hosting.public', options.public); + options.config.set("hosting.public", options.public); } - payload.hosting = options.config.get('hosting'); + payload.hosting = options.config.get("hosting"); if (payload.hosting) { - if (!_.has(payload, 'hosting.public')) { - return utils.reject('No public directory specified, can\'t deploy hosting', {exit: 1}); + if (!_.has(payload, "hosting.public")) { + return utils.reject("No public directory specified, can't deploy hosting", { exit: 1 }); } else if (!fsutils.dirExistsSync(resolveProjectPath(options.cwd, payload.hosting.public))) { - return utils.reject('Specified public directory does not exist, can\'t deploy hosting', {exit: 1}); + return utils.reject("Specified public directory does not exist, can't deploy hosting", { + exit: 1, + }); } } diff --git a/lib/deploy/index.js b/lib/deploy/index.js index 16003a42..8ee62f89 100644 --- a/lib/deploy/index.js +++ b/lib/deploy/index.js @@ -1,22 +1,22 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var logger = require('../logger'); -var api = require('../api'); -var chalk = require('chalk'); -var _ = require('lodash'); -var getProjectId = require('../getProjectId'); -var utils = require('../utils'); -var FirebaseError = require('../error'); -var track = require('../track'); -var lifecycleHooks = require('./lifecycleHooks'); +var RSVP = require("rsvp"); +var logger = require("../logger"); +var api = require("../api"); +var chalk = require("chalk"); +var _ = require("lodash"); +var getProjectId = require("../getProjectId"); +var utils = require("../utils"); +var FirebaseError = require("../error"); +var track = require("../track"); +var lifecycleHooks = require("./lifecycleHooks"); var TARGETS = { - hosting: require('./hosting'), - database: require('./database'), - firestore: require('./firestore'), - functions: require('./functions'), - storage: require('./storage') + hosting: require("./hosting"), + database: require("./database"), + firestore: require("./firestore"), + functions: require("./functions"), + storage: require("./storage"), }; var _chain = function(fns, context, options, payload) { @@ -39,7 +39,7 @@ var deploy = function(targetNames, options) { var projectId = getProjectId(options); var payload = {}; // a shared context object for deploy targets to decorate as needed - var context = {projectId: projectId}; + var context = { projectId: projectId }; var predeploys = []; var prepares = []; var deploys = []; @@ -51,10 +51,12 @@ var deploy = function(targetNames, options) { var target = TARGETS[targetName]; if (!target) { - return RSVP.reject(new FirebaseError(chalk.bold(targetName) + ' is not a valid deploy target', {exit: 1})); + return RSVP.reject( + new FirebaseError(chalk.bold(targetName) + " is not a valid deploy target", { exit: 1 }) + ); } - predeploys.push(lifecycleHooks(targetName, 'predeploy')); + predeploys.push(lifecycleHooks(targetName, "predeploy")); if (target.prepare) { prepares.push(target.prepare); } @@ -64,49 +66,58 @@ var deploy = function(targetNames, options) { if (target.release) { releases.push(target.release); } - postdeploys.push(lifecycleHooks(targetName, 'postdeploy')); + postdeploys.push(lifecycleHooks(targetName, "postdeploy")); } logger.info(); - logger.info(chalk.bold(chalk.gray('===') + ' Deploying to \'' + projectId + '\'...')); + logger.info(chalk.bold(chalk.gray("===") + " Deploying to '" + projectId + "'...")); logger.info(); - utils.logBullet('deploying ' + chalk.bold(targetNames.join(', '))); + utils.logBullet("deploying " + chalk.bold(targetNames.join(", "))); - return _chain(predeploys, context, options, payload).then(function() { - return _chain(prepares, context, options, payload); - }).then(function() { - return _chain(deploys, context, options, payload); - }).then(function() { - return _chain(releases, context, options, payload); - }).then(function() { - return _chain(postdeploys, context, options, payload); - }).then(function() { - // skip talking to deploy server if only deploying functions - if (_.isEqual(targetNames, ['functions'])) { - return {body: {}}; - } + return _chain(predeploys, context, options, payload) + .then(function() { + return _chain(prepares, context, options, payload); + }) + .then(function() { + return _chain(deploys, context, options, payload); + }) + .then(function() { + return _chain(releases, context, options, payload); + }) + .then(function() { + return _chain(postdeploys, context, options, payload); + }) + .then(function() { + // skip talking to deploy server if only deploying functions + if (_.isEqual(targetNames, ["functions"])) { + return { body: {} }; + } - return api.request('POST', '/v1/projects/' + encodeURIComponent(projectId) + '/releases', { - data: payload, - auth: true, - origin: api.deployOrigin + return api.request("POST", "/v1/projects/" + encodeURIComponent(projectId) + "/releases", { + data: payload, + auth: true, + origin: api.deployOrigin, + }); + }) + .then(function(res) { + if (_.has(options, "config.notes.databaseRules")) { + track("Rules Deploy", options.config.notes.databaseRules); + } + + logger.info(); + utils.logSuccess(chalk.underline.bold("Deploy complete!")); + logger.info(); + var deployedHosting = _.includes(targetNames, "hosting"); + logger.info(chalk.bold("Project Console:"), utils.consoleUrl(options.project, "/overview")); + if (deployedHosting) { + logger.info( + chalk.bold("Hosting URL:"), + utils.addSubdomain(api.hostingOrigin, options.instance) + ); + } + return res.body; }); - }).then(function(res) { - if (_.has(options, 'config.notes.databaseRules')) { - track('Rules Deploy', options.config.notes.databaseRules); - } - - logger.info(); - utils.logSuccess(chalk.underline.bold('Deploy complete!')); - logger.info(); - var deployedHosting = _.includes(targetNames, 'hosting'); - logger.info(chalk.bold('Project Console:'), utils.consoleUrl(options.project, '/overview')); - if (deployedHosting) { - logger.info(chalk.bold('Hosting URL:'), utils.addSubdomain(api.hostingOrigin, options.instance)); - } - return res.body; - }); }; deploy.TARGETS = TARGETS; diff --git a/lib/deploy/lifecycleHooks.js b/lib/deploy/lifecycleHooks.js index d7d5933c..2a400984 100644 --- a/lib/deploy/lifecycleHooks.js +++ b/lib/deploy/lifecycleHooks.js @@ -1,32 +1,38 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var utils = require('../utils'); -var chalk = require('chalk'); -var childProcess = require('child_process'); -var FirebaseError = require('../error'); -var getProjectId = require('../getProjectId'); -var logger = require('../logger'); -var path = require('path'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var utils = require("../utils"); +var chalk = require("chalk"); +var childProcess = require("child_process"); +var FirebaseError = require("../error"); +var getProjectId = require("../getProjectId"); +var logger = require("../logger"); +var path = require("path"); function runCommand(command, childOptions) { - var translatedCommand = '"' + process.execPath + '" "' + path.resolve(require.resolve('cross-env'), '..', 'bin', 'cross-env.js') + '" ' + command; + var translatedCommand = + '"' + + process.execPath + + '" "' + + path.resolve(require.resolve("cross-env"), "..", "bin", "cross-env.js") + + '" ' + + command; return new Promise(function(resolve, reject) { - logger.info('Running command: ' + command); - if (translatedCommand === '') { + logger.info("Running command: " + command); + if (translatedCommand === "") { resolve(); } var child = childProcess.spawn(translatedCommand, [], childOptions); - child.on('error', function(err) { + child.on("error", function(err) { reject(err); }); - child.on('exit', function(code, signal) { + child.on("exit", function(code, signal) { if (signal) { - reject(new Error('Command terminated with signal ' + signal)); + reject(new Error("Command terminated with signal " + signal)); } else if (code !== 0) { - reject(new Error('Command terminated with non-zero exit code' + code)); + reject(new Error("Command terminated with non-zero exit code" + code)); } else { resolve(); } @@ -36,15 +42,15 @@ function runCommand(command, childOptions) { module.exports = function(target, hook) { // Errors in postdeploy script will not exit the process since it's too late to stop the deploy. - var exit = hook !== 'postdeploy' ? undefined : {exit: 2}; + var exit = hook !== "postdeploy" ? undefined : { exit: 2 }; return function(context, options) { - var commands = options.config.get(target + '.' + hook); + var commands = options.config.get(target + "." + hook); if (!commands) { return RSVP.resolve(); } - if (typeof commands === 'string') { + if (typeof commands === "string") { commands = [commands]; } @@ -55,40 +61,48 @@ module.exports = function(target, hook) { // location of hosting site or functions deploy, defaults project directory var resourceDir; switch (target) { - case 'hosting': - resourceDir = options.config.path(options.config.get('hosting.public')); - break; - case 'functions': - resourceDir = options.config.path(options.config.get('functions.source')); - break; - default: - resourceDir = options.config.path(options.config.projectDir); + case "hosting": + resourceDir = options.config.path(options.config.get("hosting.public")); + break; + case "functions": + resourceDir = options.config.path(options.config.get("functions.source")); + break; + default: + resourceDir = options.config.path(options.config.projectDir); } // Copying over environment variables var childEnv = _.assign({}, process.env, { GCLOUD_PROJECT: projectId, PROJECT_DIR: projectDir, - RESOURCE_DIR: resourceDir + RESOURCE_DIR: resourceDir, }); var childOptions = { cwd: options.config.projectDir, env: childEnv, shell: true, - stdio: [0, 1, 2] // Inherit STDIN, STDOUT, and STDERR + stdio: [0, 1, 2], // Inherit STDIN, STDOUT, and STDERR }; - var runAllCommands = _.reduce(commands, function(soFar, command) { - return soFar.then(function() { - return runCommand(command, childOptions); - }); - }, Promise.resolve()); + var runAllCommands = _.reduce( + commands, + function(soFar, command) { + return soFar.then(function() { + return runCommand(command, childOptions); + }); + }, + Promise.resolve() + ); - return runAllCommands.then(function() { - utils.logSuccess(chalk.green.bold(target + ':') + ' Finished running ' + chalk.bold(hook) + ' script.'); - }).catch(function(err) { - throw new FirebaseError(target + ' ' + hook + ' error: ' + err.message, exit); - }); + return runAllCommands + .then(function() { + utils.logSuccess( + chalk.green.bold(target + ":") + " Finished running " + chalk.bold(hook) + " script." + ); + }) + .catch(function(err) { + throw new FirebaseError(target + " " + hook + " error: " + err.message, exit); + }); }; }; diff --git a/lib/deploy/storage/deploy.js b/lib/deploy/storage/deploy.js index 68936372..62832b9c 100644 --- a/lib/deploy/storage/deploy.js +++ b/lib/deploy/storage/deploy.js @@ -1,10 +1,10 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); module.exports = function(context) { - var rulesDeploy = _.get(context, 'storage.rulesDeploy'); + var rulesDeploy = _.get(context, "storage.rulesDeploy"); if (!rulesDeploy) { return RSVP.resolve(); } diff --git a/lib/deploy/storage/index.js b/lib/deploy/storage/index.js index a832046c..8368af69 100644 --- a/lib/deploy/storage/index.js +++ b/lib/deploy/storage/index.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; module.exports = { - prepare: require('./prepare'), - deploy: require('./deploy'), - release: require('./release') + prepare: require("./prepare"), + deploy: require("./deploy"), + release: require("./release"), }; diff --git a/lib/deploy/storage/prepare.js b/lib/deploy/storage/prepare.js index 104b2817..9058ebab 100644 --- a/lib/deploy/storage/prepare.js +++ b/lib/deploy/storage/prepare.js @@ -1,35 +1,35 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var gcp = require('../../gcp'); -var RulesDeploy = require('../../RulesDeploy'); +var gcp = require("../../gcp"); +var RulesDeploy = require("../../RulesDeploy"); module.exports = function(context, options) { - var rulesConfig = options.config.get('storage'); + var rulesConfig = options.config.get("storage"); var next = RSVP.resolve(); if (!rulesConfig) { return next; } - _.set(context, 'storage.rules', rulesConfig); + _.set(context, "storage.rules", rulesConfig); - var rulesDeploy = new RulesDeploy(options, 'storage'); - _.set(context, 'storage.rulesDeploy', rulesDeploy); + var rulesDeploy = new RulesDeploy(options, "storage"); + _.set(context, "storage.rulesDeploy", rulesDeploy); if (_.isPlainObject(rulesConfig)) { next = gcp.storage.getDefaultBucket(options.project).then(function(defaultBucket) { - rulesConfig = [_.assign(rulesConfig, {bucket: defaultBucket})]; - _.set(context, 'storage.rules', rulesConfig); + rulesConfig = [_.assign(rulesConfig, { bucket: defaultBucket })]; + _.set(context, "storage.rules", rulesConfig); }); } return next.then(function() { rulesConfig.forEach(function(ruleConfig) { if (ruleConfig.target) { - options.rc.requireTarget(context.projectId, 'storage', ruleConfig.target); + options.rc.requireTarget(context.projectId, "storage", ruleConfig.target); } rulesDeploy.addFile(ruleConfig.rules); diff --git a/lib/deploy/storage/release.js b/lib/deploy/storage/release.js index 06ec350b..6b413252 100644 --- a/lib/deploy/storage/release.js +++ b/lib/deploy/storage/release.js @@ -1,13 +1,13 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var STORAGE_RELEASE_NAME = 'firebase.storage'; +var STORAGE_RELEASE_NAME = "firebase.storage"; module.exports = function(context, options) { - var rules = _.get(context, 'storage.rules', []); - var rulesDeploy = _.get(context, 'storage.rulesDeploy'); + var rules = _.get(context, "storage.rules", []); + var rulesDeploy = _.get(context, "storage.rulesDeploy"); if (!rules.length || !rulesDeploy) { return RSVP.resolve(); } @@ -15,15 +15,17 @@ module.exports = function(context, options) { var toRelease = []; rules.forEach(function(ruleConfig) { if (ruleConfig.target) { - options.rc.target(options.project, 'storage', ruleConfig.target).forEach(function(bucket) { - toRelease.push({bucket: bucket, rules: ruleConfig.rules}); + options.rc.target(options.project, "storage", ruleConfig.target).forEach(function(bucket) { + toRelease.push({ bucket: bucket, rules: ruleConfig.rules }); }); } else { - toRelease.push({bucket: ruleConfig.bucket, rules: ruleConfig.rules}); + toRelease.push({ bucket: ruleConfig.bucket, rules: ruleConfig.rules }); } }); - return RSVP.all(toRelease.map(function(release) { - return rulesDeploy.release(release.rules, [STORAGE_RELEASE_NAME, release.bucket].join('/')); - })); + return RSVP.all( + toRelease.map(function(release) { + return rulesDeploy.release(release.rules, [STORAGE_RELEASE_NAME, release.bucket].join("/")); + }) + ); }; diff --git a/lib/detectProjectRoot.js b/lib/detectProjectRoot.js index 132dabe9..5536501c 100644 --- a/lib/detectProjectRoot.js +++ b/lib/detectProjectRoot.js @@ -1,11 +1,11 @@ -'use strict'; +"use strict"; -var fsutils = require('./fsutils'); -var path = require('path'); +var fsutils = require("./fsutils"); +var path = require("path"); module.exports = function(cwd) { var projectRootDir = cwd || process.cwd(); - while (!fsutils.fileExistsSync(path.resolve(projectRootDir, './firebase.json'))) { + while (!fsutils.fileExistsSync(path.resolve(projectRootDir, "./firebase.json"))) { var parentDir = path.dirname(projectRootDir); if (parentDir === projectRootDir) { return null; diff --git a/lib/ensureApiEnabled.js b/lib/ensureApiEnabled.js index 059d185f..945e5c11 100644 --- a/lib/ensureApiEnabled.js +++ b/lib/ensureApiEnabled.js @@ -1,37 +1,47 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var api = require('./api'); -var utils = require('./utils'); +var api = require("./api"); +var utils = require("./utils"); var POLL_INTERVAL = 10000; // 10 seconds var POLLS_BEFORE_RETRY = 12; // Retry enabling the API after 2 minutes var _enableApiWithRetries; var _checkEnabled = function(projectId, apiName, prefix, silent) { - return api.request('GET', '/v1/services/' + apiName + '/projectSettings/' + projectId + '?view=CONSUMER_VIEW', { - auth: true, - origin: 'https://servicemanagement.googleapis.com' - }).then(function(response) { - var isEnabled = _.get(response.body, 'usageSettings.consumerEnableStatus') === 'ENABLED'; - if (isEnabled && !silent) { - utils.logSuccess(chalk.bold.green(prefix + ':') + ' all necessary APIs are enabled'); - } - return isEnabled; - }); + return api + .request( + "GET", + "/v1/services/" + apiName + "/projectSettings/" + projectId + "?view=CONSUMER_VIEW", + { + auth: true, + origin: "https://servicemanagement.googleapis.com", + } + ) + .then(function(response) { + var isEnabled = _.get(response.body, "usageSettings.consumerEnableStatus") === "ENABLED"; + if (isEnabled && !silent) { + utils.logSuccess(chalk.bold.green(prefix + ":") + " all necessary APIs are enabled"); + } + return isEnabled; + }); }; var _enableApi = function(projectId, apiName) { - return api.request('PATCH', '/v1/services/' + apiName + '/projectSettings/' + projectId + '?updateMask=usageSettings', { - auth: true, - data: { - usageSettings: {consumerEnableStatus: 'ENABLED'} - }, - origin: 'https://servicemanagement.googleapis.com' - }); + return api.request( + "PATCH", + "/v1/services/" + apiName + "/projectSettings/" + projectId + "?updateMask=usageSettings", + { + auth: true, + data: { + usageSettings: { consumerEnableStatus: "ENABLED" }, + }, + origin: "https://servicemanagement.googleapis.com", + } + ); }; var _pollCheckEnabled = function(projectId, apiName, prefix, enablementRetries, pollRetries) { @@ -41,13 +51,15 @@ var _pollCheckEnabled = function(projectId, apiName, prefix, enablementRetries, } return new RSVP.Promise(function(resolve) { - setTimeout(function() { resolve(); }, POLL_INTERVAL); + setTimeout(function() { + resolve(); + }, POLL_INTERVAL); }).then(function() { return _checkEnabled(projectId, apiName, prefix).then(function(isEnabled) { if (isEnabled) { return true; } - utils.logBullet(chalk.bold.cyan(prefix + ':') + ' waiting for APIs to activate...'); + utils.logBullet(chalk.bold.cyan(prefix + ":") + " waiting for APIs to activate..."); return _pollCheckEnabled(projectId, apiName, prefix, enablementRetries, pollRetries + 1); }); }); @@ -56,7 +68,7 @@ var _pollCheckEnabled = function(projectId, apiName, prefix, enablementRetries, _enableApiWithRetries = function(projectId, apiName, prefix, enablementRetries) { enablementRetries = enablementRetries || 0; if (enablementRetries > 1) { - return utils.reject('Timed out waiting for APIs to enable. Please try again in a few minutes.'); + return utils.reject("Timed out waiting for APIs to enable. Please try again in a few minutes."); } return _enableApi(projectId, apiName, prefix, enablementRetries).then(function() { return _pollCheckEnabled(projectId, apiName, prefix, enablementRetries); @@ -65,7 +77,7 @@ _enableApiWithRetries = function(projectId, apiName, prefix, enablementRetries) var _ensureApi = function(projectId, apiName, prefix, silent) { if (!silent) { - utils.logBullet(chalk.bold.cyan(prefix + ':') + ' ensuring necessary APIs are enabled...'); + utils.logBullet(chalk.bold.cyan(prefix + ":") + " ensuring necessary APIs are enabled..."); } return _checkEnabled(projectId, apiName, prefix, silent).then(function(isEnabled) { if (isEnabled) { @@ -73,7 +85,9 @@ var _ensureApi = function(projectId, apiName, prefix, silent) { } if (!silent) { - utils.logWarning(chalk.bold.yellow(prefix + ':') + ' missing necessary APIs. Enabling now...'); + utils.logWarning( + chalk.bold.yellow(prefix + ":") + " missing necessary APIs. Enabling now..." + ); } return _enableApiWithRetries(projectId, apiName, prefix); }); @@ -82,5 +96,5 @@ var _ensureApi = function(projectId, apiName, prefix, silent) { module.exports = { ensure: _ensureApi, check: _checkEnabled, - enable: _enableApi + enable: _enableApi, }; diff --git a/lib/ensureDefaultCredentials.js b/lib/ensureDefaultCredentials.js index e28400a8..2a4a42bf 100644 --- a/lib/ensureDefaultCredentials.js +++ b/lib/ensureDefaultCredentials.js @@ -1,18 +1,18 @@ -'use strict'; +"use strict"; -var fs = require('fs-extra'); -var path = require('path'); +var fs = require("fs-extra"); +var path = require("path"); -var api = require('./api'); -var configstore = require('./configstore'); -var logger = require('./logger'); +var api = require("./api"); +var configstore = require("./configstore"); +var logger = require("./logger"); var configDir = function() { // Windows has a dedicated low-rights location for apps at ~/Application Data - if (process.platform === 'win32') { + if (process.platform === "win32") { return process.env.APPDATA; } - return process.env.HOME && path.resolve(process.env.HOME, '.config'); + return process.env.HOME && path.resolve(process.env.HOME, ".config"); }; /* @@ -21,18 +21,21 @@ https://developers.google.com/identity/protocols/application-default-credentials */ module.exports = function() { if (!configDir()) { - logger.debug('Cannot ensure default credentials, no home directory found.'); + logger.debug("Cannot ensure default credentials, no home directory found."); return; } - var GCLOUD_CREDENTIAL_DIR = path.resolve(configDir(), 'gcloud'); - var GCLOUD_CREDENTIAL_PATH = path.join(GCLOUD_CREDENTIAL_DIR, 'application_default_credentials.json'); + var GCLOUD_CREDENTIAL_DIR = path.resolve(configDir(), "gcloud"); + var GCLOUD_CREDENTIAL_PATH = path.join( + GCLOUD_CREDENTIAL_DIR, + "application_default_credentials.json" + ); var credentials = { client_id: api.clientId, client_secret: api.clientSecret, - type: 'authorized_user', - refresh_token: configstore.get('tokens').refresh_token + type: "authorized_user", + refresh_token: configstore.get("tokens").refresh_token, }; // Mimic the effects of running "gcloud auth application-default login" fs.ensureDirSync(GCLOUD_CREDENTIAL_DIR); diff --git a/lib/error.js b/lib/error.js index 6149e627..623de1df 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,14 +1,14 @@ -'use strict'; +"use strict"; var FirebaseError = function(message, options) { options = options || {}; - this.name = 'FirebaseError'; + this.name = "FirebaseError"; this.message = message; this.children = options.children || []; this.status = options.status || 500; this.exit = options.exit || 1; - this.stack = (new Error()).stack; + this.stack = new Error().stack; this.original = options.original; this.context = options.context; }; diff --git a/lib/errorOut.js b/lib/errorOut.js index 089d4381..f04974e7 100644 --- a/lib/errorOut.js +++ b/lib/errorOut.js @@ -1,13 +1,13 @@ -'use strict'; +"use strict"; -var logError = require('./logError'); -var FirebaseError = require('./error'); +var logError = require("./logError"); +var FirebaseError = require("./error"); module.exports = function(client, error) { - if (error.name !== 'FirebaseError') { - error = new FirebaseError('An unexpected error has occurred.', { + if (error.name !== "FirebaseError") { + error = new FirebaseError("An unexpected error has occurred.", { original: error, - exit: 2 + exit: 2, }); } diff --git a/lib/extractTriggers.js b/lib/extractTriggers.js index d7074bc3..d3f100a5 100644 --- a/lib/extractTriggers.js +++ b/lib/extractTriggers.js @@ -1,16 +1,18 @@ // NOTE: DO NOT ADD NPM DEPENDENCIES TO THIS FILE. It is executed // as part of the trigger parser which should be a vanilla Node.js // script. -'use strict'; +"use strict"; var extractTriggers = function(mod, triggers, prefix) { - prefix = prefix || ''; + prefix = prefix || ""; for (var funcName in mod) { if (mod.hasOwnProperty(funcName)) { var child = mod[funcName]; - if (typeof child === 'function' && child.__trigger && typeof child.__trigger === 'object') { - if (funcName.indexOf('-') >= 0) { - throw new Error('Function name "' + funcName + '" is invalid. Function names cannot contain dashes.'); + if (typeof child === "function" && child.__trigger && typeof child.__trigger === "object") { + if (funcName.indexOf("-") >= 0) { + throw new Error( + 'Function name "' + funcName + '" is invalid. Function names cannot contain dashes.' + ); } var trigger = {}; @@ -20,10 +22,10 @@ var extractTriggers = function(mod, triggers, prefix) { } } trigger.name = prefix + funcName; - trigger.entryPoint = trigger.name.replace(/-/g, '.'); + trigger.entryPoint = trigger.name.replace(/-/g, "."); triggers.push(trigger); - } else if (typeof child === 'object') { - extractTriggers(child, triggers, prefix + funcName + '-'); + } else if (typeof child === "object") { + extractTriggers(child, triggers, prefix + funcName + "-"); } } } diff --git a/lib/fetchMOTD.js b/lib/fetchMOTD.js index fb95f5ba..dc29109c 100644 --- a/lib/fetchMOTD.js +++ b/lib/fetchMOTD.js @@ -1,54 +1,57 @@ -'use strict'; -var logger = require('./logger'); -var request = require('request'); -var configstore = require('./configstore'); -var _ = require('lodash'); -var pkg = require('../package.json'); -var semver = require('semver'); -var chalk = require('chalk'); -var utils = require('./utils'); -var api = require('./api'); +"use strict"; +var logger = require("./logger"); +var request = require("request"); +var configstore = require("./configstore"); +var _ = require("lodash"); +var pkg = require("../package.json"); +var semver = require("semver"); +var chalk = require("chalk"); +var utils = require("./utils"); +var api = require("./api"); var ONE_DAY_MS = 1000 * 60 * 60 * 24; module.exports = function() { - var motd = configstore.get('motd'); - var motdFetched = configstore.get('motd.fetched') || 0; + var motd = configstore.get("motd"); + var motdFetched = configstore.get("motd.fetched") || 0; if (motd && motdFetched > Date.now() - ONE_DAY_MS) { if (motd.minVersion && semver.gt(motd.minVersion, pkg.version)) { logger.error( - chalk.red('Error:'), - 'CLI is out of date (on', + chalk.red("Error:"), + "CLI is out of date (on", chalk.bold(pkg.version), - ', need at least', - chalk.bold(motd.minVersion) + ')\n\nRun', - chalk.bold('npm install -g firebase-tools'), - 'to upgrade.' + ", need at least", + chalk.bold(motd.minVersion) + ")\n\nRun", + chalk.bold("npm install -g firebase-tools"), + "to upgrade." ); process.exit(1); } if (motd.message && process.stdout.isTTY) { - var lastMessage = configstore.get('motd.lastMessage'); + var lastMessage = configstore.get("motd.lastMessage"); if (lastMessage !== motd.message) { logger.info(); logger.info(motd.message); logger.info(); - configstore.set('motd.lastMessage', motd.message); + configstore.set("motd.lastMessage", motd.message); } } } else { - request({ - url: utils.addSubdomain(api.realtimeOrigin, 'firebase-public') + '/cli.json', - json: true - }, function(err, res, body) { - if (err) { - return; + request( + { + url: utils.addSubdomain(api.realtimeOrigin, "firebase-public") + "/cli.json", + json: true, + }, + function(err, res, body) { + if (err) { + return; + } + motd = _.assign({}, body); + configstore.set("motd", motd); + configstore.set("motd.fetched", Date.now()); } - motd = _.assign({}, body); - configstore.set('motd', motd); - configstore.set('motd.fetched', Date.now()); - }); + ); } }; diff --git a/lib/fetchWebSetup.js b/lib/fetchWebSetup.js index fb976903..1e4cd508 100644 --- a/lib/fetchWebSetup.js +++ b/lib/fetchWebSetup.js @@ -1,15 +1,17 @@ -'use strict'; +"use strict"; -var getProjectNumber = require('./getProjectNumber'); -var api = require('./api'); +var getProjectNumber = require("./getProjectNumber"); +var api = require("./api"); module.exports = function(options) { - return getProjectNumber(options).then(function(projectNumber) { - return api.request('GET', '/v1/projects/' + projectNumber + '/clients/_:getWebAppConfig', { - auth: true, - origin: api.firedataOrigin + return getProjectNumber(options) + .then(function(projectNumber) { + return api.request("GET", "/v1/projects/" + projectNumber + "/clients/_:getWebAppConfig", { + auth: true, + origin: api.firedataOrigin, + }); + }) + .then(function(response) { + return response.body; }); - }).then(function(response) { - return response.body; - }); }; diff --git a/lib/filterTargets.js b/lib/filterTargets.js index fdf50d19..35dad524 100644 --- a/lib/filterTargets.js +++ b/lib/filterTargets.js @@ -1,24 +1,30 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var FirebaseError = require('../lib/error'); +var _ = require("lodash"); +var FirebaseError = require("../lib/error"); module.exports = function(options, validTargets) { var targets = validTargets.filter(function(t) { return options.config.has(t); }); if (options.only) { - targets = _.intersection(targets, options.only.split(',').map(function(opt) { - return opt.split(':')[0]; - })); + targets = _.intersection( + targets, + options.only.split(",").map(function(opt) { + return opt.split(":")[0]; + }) + ); } else if (options.except) { - targets = _.difference(targets, options.except.split(',')); + targets = _.difference(targets, options.except.split(",")); } if (targets.length === 0) { - throw new FirebaseError('Cannot understand what targets to deploy. Check that you specified valid targets' + - ' if you used the --only or --except flag. Otherwise, check your firebase.json to' + - ' ensure that your project is initialized for the desired features.', {exit: 1}); + throw new FirebaseError( + "Cannot understand what targets to deploy. Check that you specified valid targets" + + " if you used the --only or --except flag. Otherwise, check your firebase.json to" + + " ensure that your project is initialized for the desired features.", + { exit: 1 } + ); } return targets; }; diff --git a/lib/firestore/delete.js b/lib/firestore/delete.js index 689c17a7..57336861 100644 --- a/lib/firestore/delete.js +++ b/lib/firestore/delete.js @@ -1,12 +1,12 @@ -'use strict'; +"use strict"; -var api = require('../../lib/api'); -var chalk = require('chalk'); -var FirebaseError = require('../../lib/error'); -var logger = require('../../lib/logger'); -var ProgressBar = require('progress'); -var RSVP = require('rsvp'); -var utils = require('../../lib/utils'); +var api = require("../../lib/api"); +var chalk = require("chalk"); +var FirebaseError = require("../../lib/error"); +var logger = require("../../lib/logger"); +var ProgressBar = require("progress"); +var RSVP = require("rsvp"); +var utils = require("../../lib/utils"); /** * Construct a new Firestore delete operation. @@ -29,14 +29,14 @@ function FirestoreDelete(project, path, options) { // Remove any leading or trailing slashes from the path if (this.path) { - this.path = this.path.replace(/(^\/+|\/+$)/g, ''); + this.path = this.path.replace(/(^\/+|\/+$)/g, ""); } this.isDocumentPath = this._isDocumentPath(this.path); this.isCollectionPath = this._isCollectionPath(this.path); this.allDescendants = this.recursive; - this.parent = 'projects/' + project + '/databases/(default)/documents'; + this.parent = "projects/" + project + "/databases/(default)/documents"; // When --all-collections is passed any other flags or arguments are ignored if (!options.allCollections) { @@ -49,17 +49,17 @@ function FirestoreDelete(project, path, options) { */ FirestoreDelete.prototype._validateOptions = function() { if (this.recursive && this.shallow) { - throw new FirebaseError('Cannot pass recursive and shallow options together.'); + throw new FirebaseError("Cannot pass recursive and shallow options together."); } if (this.isCollectionPath && !this.recursive && !this.shallow) { - throw new FirebaseError('Must pass recursive or shallow option when deleting a collection.'); + throw new FirebaseError("Must pass recursive or shallow option when deleting a collection."); } - var pieces = this.path.split('/'); + var pieces = this.path.split("/"); if (pieces.length === 0) { - throw new FirebaseError('Path length must be greater than zero.'); + throw new FirebaseError("Path length must be greater than zero."); } var hasEmptySegment = pieces.some(function(piece) { @@ -67,7 +67,7 @@ FirestoreDelete.prototype._validateOptions = function() { }); if (hasEmptySegment) { - throw new FirebaseError('Path must not have any empty segments.'); + throw new FirebaseError("Path must not have any empty segments."); } }; @@ -83,7 +83,7 @@ FirestoreDelete.prototype._isDocumentPath = function(path) { return false; } - var pieces = path.split('/'); + var pieces = path.split("/"); return pieces.length % 2 === 0; }; @@ -115,50 +115,52 @@ FirestoreDelete.prototype._isCollectionPath = function(path) { FirestoreDelete.prototype._collectionDescendantsQuery = function(allDescendants, batchSize) { var nullChar = String.fromCharCode(0); - var startAt = this.parent + '/' + this.path + '/' + nullChar; - var endAt = this.parent + '/' + this.path + nullChar + '/' + nullChar; + var startAt = this.parent + "/" + this.path + "/" + nullChar; + var endAt = this.parent + "/" + this.path + nullChar + "/" + nullChar; var where = { compositeFilter: { - op: 'AND', + op: "AND", filters: [ { fieldFilter: { field: { - fieldPath: '__name__' + fieldPath: "__name__", }, - op: 'GREATER_THAN_OR_EQUAL', + op: "GREATER_THAN_OR_EQUAL", value: { - referenceValue: startAt - } - } + referenceValue: startAt, + }, + }, }, { fieldFilter: { field: { - fieldPath: '__name__' + fieldPath: "__name__", }, - op: 'LESS_THAN', + op: "LESS_THAN", value: { - referenceValue: endAt - } - } - } - ] - } + referenceValue: endAt, + }, + }, + }, + ], + }, }; return { structuredQuery: { where: where, limit: batchSize, - from: [{ - allDescendants: allDescendants - }], + from: [ + { + allDescendants: allDescendants, + }, + ], select: { - fields: [{fieldPath: '__name__'}] - } - } + fields: [{ fieldPath: "__name__" }], + }, + }, }; }; @@ -178,13 +180,15 @@ FirestoreDelete.prototype._docDescendantsQuery = function(allDescendants, batchS return { structuredQuery: { limit: batchSize, - from: [{ - allDescendants: allDescendants - }], + from: [ + { + allDescendants: allDescendants, + }, + ], select: { - fields: [{fieldPath: '__name__'}] - } - } + fields: [{ fieldPath: "__name__" }], + }, + }, }; }; @@ -202,26 +206,30 @@ FirestoreDelete.prototype._getDescendantBatch = function(allDescendants, batchSi var url; var body; if (this.isDocumentPath) { - url = this.parent + '/' + this.path + ':runQuery'; + url = this.parent + "/" + this.path + ":runQuery"; body = this._docDescendantsQuery(allDescendants, batchSize); } else { - url = this.parent + ':runQuery'; + url = this.parent + ":runQuery"; body = this._collectionDescendantsQuery(allDescendants, batchSize); } - return api.request('POST', '/v1beta1/' + url, { - auth: true, - data: body, - origin: api.firestoreOrigin - }).then(function(res) { - // Return the 'document' property for each element in the response, - // where it exists. - return res.body.filter(function(x) { - return x.document; - }).map(function(x) { - return x.document; + return api + .request("POST", "/v1beta1/" + url, { + auth: true, + data: body, + origin: api.firestoreOrigin, + }) + .then(function(res) { + // Return the 'document' property for each element in the response, + // where it exists. + return res.body + .filter(function(x) { + return x.document; + }) + .map(function(x) { + return x.document; + }); }); - }); }; /** @@ -234,34 +242,35 @@ FirestoreDelete.prototype._getDescendantBatch = function(allDescendants, batchSi * @return {Promise} a promise for the number of deleted documents. */ FirestoreDelete.prototype._deleteDocuments = function(docs) { - var url = this.parent + ':commit'; + var url = this.parent + ":commit"; var writes = docs.map(function(doc) { return { - delete: doc.name + delete: doc.name, }; }); var body = { - writes: writes + writes: writes, }; - return api.request('POST', '/v1beta1/' + url, { - auth: true, - data: body, - origin: api.firestoreOrigin - }).then(function(res) { - return res.body.writeResults.length; - }); + return api + .request("POST", "/v1beta1/" + url, { + auth: true, + data: body, + origin: api.firestoreOrigin, + }) + .then(function(res) { + return res.body.writeResults.length; + }); }; /** * Progress bar shared by the class. */ -FirestoreDelete.progressBar = new ProgressBar( - 'Deleted :current docs (:rate docs/s)', - { total: Number.MAX_SAFE_INTEGER } -); +FirestoreDelete.progressBar = new ProgressBar("Deleted :current docs (:rate docs/s)", { + total: Number.MAX_SAFE_INTEGER, +}); /** * Repeatedly query for descendants of a path and delete them in batches @@ -271,21 +280,19 @@ FirestoreDelete.progressBar = new ProgressBar( */ FirestoreDelete.prototype._recursiveBatchDelete = function() { var self = this; - return this._getDescendantBatch(this.allDescendants, this.batchSize) - .then(function(docs) { - if (docs.length <= 0) { - return RSVP.resolve(); - } + return this._getDescendantBatch(this.allDescendants, this.batchSize).then(function(docs) { + if (docs.length <= 0) { + return RSVP.resolve(); + } - return self._deleteDocuments(docs) - .then(function(numDeleted) { - // Tick the progress bar - FirestoreDelete.progressBar.tick(numDeleted); + return self._deleteDocuments(docs).then(function(numDeleted) { + // Tick the progress bar + FirestoreDelete.progressBar.tick(numDeleted); - // Recurse to delete another batch - return self._recursiveBatchDelete(); - }); + // Recurse to delete another batch + return self._recursiveBatchDelete(); }); + }); }; /** @@ -299,19 +306,18 @@ FirestoreDelete.prototype._deletePath = function() { var self = this; var initialDelete; if (this.isDocumentPath) { - var doc = { name: this.parent + '/' + this.path }; - initialDelete = this._deleteDocuments([doc]) - .catch(function(err) { - logger.debug('deletePath:initialDelete:error', err); - if (self.allDescendants) { - // On a recursive delete, we are insensitive to - // failures of the initial delete - return RSVP.resolve(); - } + var doc = { name: this.parent + "/" + this.path }; + initialDelete = this._deleteDocuments([doc]).catch(function(err) { + logger.debug("deletePath:initialDelete:error", err); + if (self.allDescendants) { + // On a recursive delete, we are insensitive to + // failures of the initial delete + return RSVP.resolve(); + } - // For a shallow delete, this error is fatal. - return utils.reject('Unable to delete ' + chalk.cyan(this.path)); - }); + // For a shallow delete, this error is fatal. + return utils.reject("Unable to delete " + chalk.cyan(this.path)); + }); } else { initialDelete = RSVP.resolve(); } @@ -327,14 +333,17 @@ FirestoreDelete.prototype._deletePath = function() { * @return {Promise} a promise for an array of collection IDs. */ FirestoreDelete.prototype._listCollectionIds = function() { - var url = '/v1beta1/projects/' + this.project + '/databases/(default)/documents:listCollectionIds'; + var url = + "/v1beta1/projects/" + this.project + "/databases/(default)/documents:listCollectionIds"; - return api.request('POST', url, { - auth: true, - origin: api.firestoreOrigin - }).then(function(res) { - return res.body.collectionIds || []; - }); + return api + .request("POST", url, { + auth: true, + origin: api.firestoreOrigin, + }) + .then(function(res) { + return res.body.collectionIds || []; + }); }; /** @@ -346,19 +355,19 @@ FirestoreDelete.prototype.deleteDatabase = function() { var self = this; return this._listCollectionIds() .catch(function(err) { - logger.debug('deleteDatabase:listCollectionIds:error', err); - return utils.reject('Unable to list collection IDs'); + logger.debug("deleteDatabase:listCollectionIds:error", err); + return utils.reject("Unable to list collection IDs"); }) .then(function(collectionIds) { var promises = []; - logger.info('Deleting the following collections: ' + chalk.cyan(collectionIds.join(', '))); + logger.info("Deleting the following collections: " + chalk.cyan(collectionIds.join(", "))); for (var i = 0; i < collectionIds.length; i++) { var collectionId = collectionIds[i]; var deleteOp = new FirestoreDelete(self.project, collectionId, { recursive: true, - batchSize: self.batchSize + batchSize: self.batchSize, }); promises.push(deleteOp.execute()); @@ -376,10 +385,9 @@ FirestoreDelete.prototype.deleteDatabase = function() { * children and false otherwise. */ FirestoreDelete.prototype.checkHasChildren = function() { - return this._getDescendantBatch(true, 1) - .then(function(docs) { - return docs.length > 0; - }); + return this._getDescendantBatch(true, 1).then(function(docs) { + return docs.length > 0; + }); }; /** @@ -388,14 +396,11 @@ FirestoreDelete.prototype.checkHasChildren = function() { FirestoreDelete.prototype.execute = function() { var verifyRecurseSafe; if (this.isDocumentPath && !this.recursive && !this.shallow) { - verifyRecurseSafe = this.checkHasChildren() - .then(function(multiple) { - if (multiple) { - return utils.reject( - 'Document has children, must specify -r or --shallow.', - {exit: 1}); - } - }); + verifyRecurseSafe = this.checkHasChildren().then(function(multiple) { + if (multiple) { + return utils.reject("Document has children, must specify -r or --shallow.", { exit: 1 }); + } + }); } else { verifyRecurseSafe = RSVP.resolve(); } diff --git a/lib/firestore/encodeFirestoreValue.js b/lib/firestore/encodeFirestoreValue.js index b4c42ffd..7412d6a6 100644 --- a/lib/firestore/encodeFirestoreValue.js +++ b/lib/firestore/encodeFirestoreValue.js @@ -1,40 +1,42 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var is = require('is'); +var _ = require("lodash"); +var is = require("is"); var encodeFirestoreValue = function(data) { var isPlainObject = function(input) { - return typeof input === 'object' && + return ( + typeof input === "object" && input !== null && - _.isEqual(Object.getPrototypeOf(input), Object.prototype); + _.isEqual(Object.getPrototypeOf(input), Object.prototype) + ); }; var encodeHelper = function(val) { if (is.string(val)) { return { - stringValue: val + stringValue: val, }; } if (is.boolean(val)) { return { - booleanValue: val + booleanValue: val, }; } if (is.integer(val)) { return { - integerValue: val + integerValue: val, }; } // Integers are handled above, the remaining numbers are treated as doubles if (is.number(val)) { return { - doubleValue: val + doubleValue: val, }; } if (is.date(val)) { return { - timestampValue: val.toISOString() + timestampValue: val.toISOString(), }; } if (is.array(val)) { @@ -47,30 +49,32 @@ var encodeFirestoreValue = function(data) { } return { arrayValue: { - values: encodedElements - } + values: encodedElements, + }, }; } if (is.nil(val)) { return { - nullValue: 'NULL_VALUE' + nullValue: "NULL_VALUE", }; } if (is.instanceof(val, Buffer) || is.instanceof(val, Uint8Array)) { return { - bytesValue: val + bytesValue: val, }; } if (isPlainObject(val)) { return { mapValue: { - fields: encodeFirestoreValue(val) - } + fields: encodeFirestoreValue(val), + }, }; } throw new Error( - 'Cannot encode ' + val + 'to a Firestore Value.' + - ' The emulator does not yet support Firestore document reference values or geo points.' + "Cannot encode " + + val + + "to a Firestore Value." + + " The emulator does not yet support Firestore document reference values or geo points." ); }; diff --git a/lib/firestore/indexes.js b/lib/firestore/indexes.js index 719029e9..a34a0080 100644 --- a/lib/firestore/indexes.js +++ b/lib/firestore/indexes.js @@ -1,15 +1,12 @@ -'use strict'; +"use strict"; -var api = require('../../lib/api'); -var chalk = require('chalk'); -var FirebaseError = require('../../lib/error'); -var loadCJSON = require('../../lib/loadCJSON'); -var RSVP = require('rsvp'); +var api = require("../../lib/api"); +var chalk = require("chalk"); +var FirebaseError = require("../../lib/error"); +var loadCJSON = require("../../lib/loadCJSON"); +var RSVP = require("rsvp"); -var VALID_INDEX_MODES = [ - 'ASCENDING', - 'DESCENDING' -]; +var VALID_INDEX_MODES = ["ASCENDING", "DESCENDING"]; /** * Validate an index is correctly formed, throws an exception for all @@ -43,8 +40,9 @@ var _validate = function(index) { } if (VALID_INDEX_MODES.indexOf(field.mode) < 0) { - throw new FirebaseError('Index field mode must be one of ' - + VALID_INDEX_MODES.join(', ') + ': ' + indexString); + throw new FirebaseError( + "Index field mode must be one of " + VALID_INDEX_MODES.join(", ") + ": " + indexString + ); } } }; @@ -63,11 +61,11 @@ var _validate = function(index) { var create = function(project, index) { _validate(index); - var url = 'projects/' + project + '/databases/(default)/indexes'; - return api.request('POST', '/v1beta1/' + url, { + var url = "projects/" + project + "/databases/(default)/indexes"; + return api.request("POST", "/v1beta1/" + url, { auth: true, data: index, - origin: api.firestoreOrigin + origin: api.firestoreOrigin, }); }; @@ -81,33 +79,35 @@ var create = function(project, index) { * @return {Promise} a promise for an array of indexes. */ var list = function(project) { - var url = 'projects/' + project + '/databases/(default)/indexes'; + var url = "projects/" + project + "/databases/(default)/indexes"; - return api.request('GET', '/v1beta1/' + url, { - auth: true, - origin: api.firestoreOrigin - }).then(function(res) { - var indexes = res.body.indexes || []; - var result = []; + return api + .request("GET", "/v1beta1/" + url, { + auth: true, + origin: api.firestoreOrigin, + }) + .then(function(res) { + var indexes = res.body.indexes || []; + var result = []; - // Clean up the index metadata so that they appear in the same - // format as they would be specified in firestore.indexes.json - for (var i = 0; i < indexes.length; i++) { - var index = indexes[i]; - var sanitized = {}; + // Clean up the index metadata so that they appear in the same + // format as they would be specified in firestore.indexes.json + for (var i = 0; i < indexes.length; i++) { + var index = indexes[i]; + var sanitized = {}; - sanitized.collectionId = index.collectionId; - sanitized.state = index.state; + sanitized.collectionId = index.collectionId; + sanitized.state = index.state; - sanitized.fields = index.fields.filter(function(field) { - return field.fieldPath !== '__name__'; - }); + sanitized.fields = index.fields.filter(function(field) { + return field.fieldPath !== "__name__"; + }); - result.push(sanitized); - } + result.push(sanitized); + } - return result; - }); + return result; + }); }; /** @@ -151,25 +151,25 @@ var equal = function(a, b) { * @return {string} a unique hash. */ var hash = function(index) { - var result = ''; + var result = ""; result += index.collectionId; - result += '['; + result += "["; for (var i = 0; i < index.fields.length; i++) { var field = index.fields[i]; // Skip __name__ fields - if (field.fieldPath === '__name__') { + if (field.fieldPath === "__name__") { continue; } // Append the field description - result += '('; - result += field.fieldPath + ',' + field.mode; - result += ')'; + result += "("; + result += field.fieldPath + "," + field.mode; + result += ")"; } - result += ']'; + result += "]"; return result; }; @@ -181,29 +181,29 @@ var hash = function(index) { * @return {string} string for logging. */ var toPrettyString = function(index) { - var result = ''; + var result = ""; if (index.state) { - var stateMsg = '[' + index.state + '] '; + var stateMsg = "[" + index.state + "] "; - if (index.state === 'READY') { + if (index.state === "READY") { result += chalk.green(stateMsg); - } else if (index.state === 'CREATING') { + } else if (index.state === "CREATING") { result += chalk.yellow(stateMsg); } else { result += chalk.red(stateMsg); } } - result += chalk.cyan('(' + index.collectionId + ')'); - result += ' -- '; + result += chalk.cyan("(" + index.collectionId + ")"); + result += " -- "; index.fields.forEach(function(field) { - if (field.fieldPath === '__name__') { + if (field.fieldPath === "__name__") { return; } - result += '(' + field.fieldPath + ',' + field.mode + ') '; + result += "(" + field.fieldPath + "," + field.mode + ") "; }); return result; @@ -213,7 +213,7 @@ var toPrettyString = function(index) { * Prepare indexes for deployment. */ var prepare = function(context, options) { - var indexesFileName = options.config.get('firestore.indexes'); + var indexesFileName = options.config.get("firestore.indexes"); var indexesPath = options.config.path(indexesFileName); var parsedSrc = loadCJSON(indexesPath); @@ -228,7 +228,7 @@ var prepare = function(context, options) { context.firestore = context.firestore || {}; context.firestore.indexes = { name: indexesFileName, - content: parsedSrc + content: parsedSrc, }; return RSVP.resolve(); @@ -240,5 +240,5 @@ module.exports = { equal: equal, hash: hash, toPrettyString: toPrettyString, - prepare: prepare + prepare: prepare, }; diff --git a/lib/fsAsync.js b/lib/fsAsync.js index 003a19c6..5c9a8ea9 100644 --- a/lib/fsAsync.js +++ b/lib/fsAsync.js @@ -1,10 +1,10 @@ -'use strict'; +"use strict"; -var fs = require('fs'); -var _ = require('lodash'); -var path = require('path'); -var RSVP = require('rsvp'); -var minimatch = require('minimatch'); +var fs = require("fs"); +var _ = require("lodash"); +var path = require("path"); +var RSVP = require("rsvp"); +var minimatch = require("minimatch"); // resolver creates a promise resolver for an errback function resolver(resolve, reject) { @@ -25,7 +25,7 @@ function readdir(location) { if (fs.readdir.length === 2) { fs.readdir(location, done); } else { - fs.readdir(location, {encoding: 'utf8'}, done); + fs.readdir(location, { encoding: "utf8" }, done); } }); } @@ -49,37 +49,40 @@ function rmdir(location) { } function _readdirRecursive(options) { - return readdir(options.path).then(function(dirContents) { - var work = _ - .chain(dirContents) - .map(function(shortName) { return path.join(options.path, shortName); }) - .reject(options.filter) - .map(function(file) { - return stat(file).then(function(fstat) { - if (fstat.isFile()) { - return { - name: file, - mode: fstat.mode - }; - } - if (!fstat.isDirectory()) { - return null; - } - return _readdirRecursive({ - path: file, - filter: options.filter + return readdir(options.path) + .then(function(dirContents) { + var work = _.chain(dirContents) + .map(function(shortName) { + return path.join(options.path, shortName); + }) + .reject(options.filter) + .map(function(file) { + return stat(file).then(function(fstat) { + if (fstat.isFile()) { + return { + name: file, + mode: fstat.mode, + }; + } + if (!fstat.isDirectory()) { + return null; + } + return _readdirRecursive({ + path: file, + filter: options.filter, + }); }); - }); - }) - .value(); + }) + .value(); - // Note: we cannot flatten in the chain because we have an array of Promises - // which might themselves resolve into an array. - return RSVP.Promise.all(work).then(_.flatten); - }).then(function(results) { - // Special files were returned as null; cut them from results. - return _.reject(results, _.isNull); - }); + // Note: we cannot flatten in the chain because we have an array of Promises + // which might themselves resolve into an array. + return RSVP.Promise.all(work).then(_.flatten); + }) + .then(function(results) { + // Special files were returned as null; cut them from results. + return _.reject(results, _.isNull); + }); } // Options contains a path and optionally files to ignore. @@ -88,27 +91,33 @@ function _readdirRecursive(options) { // @returns array of {name, mode} for files that match function readdirRecursive(options) { var mmopts = { matchBase: true, dot: true }; - var rules = _.map(options.ignore || [], function(glob) { return minimatch.filter(glob, mmopts); }); + var rules = _.map(options.ignore || [], function(glob) { + return minimatch.filter(glob, mmopts); + }); var filter = function(test) { - return _.some(rules, function(rule) { return rule(test); }); + return _.some(rules, function(rule) { + return rule(test); + }); }; return _readdirRecursive({ path: options.path, - filter: filter + filter: filter, }); } function rmdirRecursive(location) { return readdir(location).then(function(dirContents) { - var cleanThisDir = RSVP.Promise.all(_.map(dirContents, function(file) { - file = path.join(location, file); - return stat(file).then(function(fstat) { - if (fstat.isDirectory()) { - return rmdirRecursive(file); - } - return unlink(file); - }); - })); + var cleanThisDir = RSVP.Promise.all( + _.map(dirContents, function(file) { + file = path.join(location, file); + return stat(file).then(function(fstat) { + if (fstat.isDirectory()) { + return rmdirRecursive(file); + } + return unlink(file); + }); + }) + ); return cleanThisDir.then(function() { return rmdir(location); }); @@ -121,5 +130,5 @@ module.exports = { stat: stat, unlink: unlink, readdirRecursive: readdirRecursive, - rmdirRecursive: rmdirRecursive + rmdirRecursive: rmdirRecursive, }; diff --git a/lib/fsutils.js b/lib/fsutils.js index d6cf3033..0a25628c 100644 --- a/lib/fsutils.js +++ b/lib/fsutils.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; -var fs = require('fs'); +var fs = require("fs"); module.exports = { fileExistsSync: function(path) { @@ -18,5 +18,5 @@ module.exports = { } catch (e) { return false; } - } + }, }; diff --git a/lib/functionsConfig.js b/lib/functionsConfig.js index 4346816c..ab221d17 100644 --- a/lib/functionsConfig.js +++ b/lib/functionsConfig.js @@ -1,29 +1,29 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var chalk = require('chalk'); -var api = require('./api'); -var FirebaseError = require('./error'); -var runtimeconfig = require('./gcp/runtimeconfig'); -var getProjectId = require('./getProjectId'); -var getProjectNumber = require('./getProjectNumber'); -var ensureApiEnabled = require('./ensureApiEnabled').ensure; +var _ = require("lodash"); +var RSVP = require("rsvp"); +var chalk = require("chalk"); +var api = require("./api"); +var FirebaseError = require("./error"); +var runtimeconfig = require("./gcp/runtimeconfig"); +var getProjectId = require("./getProjectId"); +var getProjectNumber = require("./getProjectNumber"); +var ensureApiEnabled = require("./ensureApiEnabled").ensure; -exports.RESERVED_NAMESPACES = ['firebase']; +exports.RESERVED_NAMESPACES = ["firebase"]; var _keyToIds = function(key) { - var keyParts = key.split('.'); - var variable = keyParts.slice(1).join('/'); + var keyParts = key.split("."); + var variable = keyParts.slice(1).join("/"); return { config: keyParts[0], - variable: variable + variable: variable, }; }; var _setVariable = function(projectId, configId, varPath, val) { - if (configId === '' || varPath === '') { - var msg = 'Invalid argument, each config value must have a 2-part key (e.g. foo.bar).'; + if (configId === "" || varPath === "") { + var msg = "Invalid argument, each config value must have a 2-part key (e.g. foo.bar)."; throw new FirebaseError(msg); } return runtimeconfig.variables.set(projectId, configId, varPath, val); @@ -31,30 +31,31 @@ var _setVariable = function(projectId, configId, varPath, val) { exports.ensureApi = function(options) { var projectId = getProjectId(options); - return ensureApiEnabled(projectId, 'runtimeconfig.googleapis.com', 'runtimeconfig', true); + return ensureApiEnabled(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true); }; exports.varNameToIds = function(varName) { return { - config: varName.match(new RegExp('/configs/(.+)/variables/'))[1], - variable: varName.match(new RegExp('/variables/(.+)'))[1] + config: varName.match(new RegExp("/configs/(.+)/variables/"))[1], + variable: varName.match(new RegExp("/variables/(.+)"))[1], }; }; exports.idsToVarName = function(projectId, configId, varId) { - return _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/'); + return _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); }; exports.getFirebaseConfig = function(options) { return getProjectNumber(options) - .then(function(projectNumber) { - return api.request('GET', '/v1/projects/' + projectNumber + ':getServerAppConfig', { - auth: true, - origin: api.firedataOrigin + .then(function(projectNumber) { + return api.request("GET", "/v1/projects/" + projectNumber + ":getServerAppConfig", { + auth: true, + origin: api.firedataOrigin, + }); + }) + .then(function(response) { + return response.body; }); - }).then(function(response) { - return response.body; - }); }; // If you make changes to this function, run "node scripts/test-functions-config.js" @@ -71,10 +72,12 @@ exports.setVariablesRecursive = function(projectId, configId, varPath, val) { } // If 'parsed' is object, call again if (_.isPlainObject(parsed)) { - return RSVP.all(_.map(parsed, function(item, key) { - var newVarPath = varPath ? _.join([varPath, key], '/') : key; - return exports.setVariablesRecursive(projectId, configId, newVarPath, item); - })); + return RSVP.all( + _.map(parsed, function(item, key) { + var newVarPath = varPath ? _.join([varPath, key], "/") : key; + return exports.setVariablesRecursive(projectId, configId, newVarPath, item); + }) + ); } // 'val' wasn't more JSON, i.e. is a leaf node; set and return @@ -85,33 +88,41 @@ exports.materializeConfig = function(configName, output) { var _materializeVariable = function(varName) { return runtimeconfig.variables.get(varName).then(function(variable) { var id = exports.varNameToIds(variable.name); - var key = id.config + '.' + id.variable.split('/').join('.'); + var key = id.config + "." + id.variable.split("/").join("."); _.set(output, key, variable.text); }); }; var _traverseVariables = function(variables) { - return RSVP.all(_.map(variables, function(variable) { - return _materializeVariable(variable.name); - })); + return RSVP.all( + _.map(variables, function(variable) { + return _materializeVariable(variable.name); + }) + ); }; - return runtimeconfig.variables.list(configName).then(function(variables) { - return _traverseVariables(variables); - }).then(function() { - return output; - }); + return runtimeconfig.variables + .list(configName) + .then(function(variables) { + return _traverseVariables(variables); + }) + .then(function() { + return output; + }); }; exports.materializeAll = function(projectId) { var output = {}; return runtimeconfig.configs.list(projectId).then(function(configs) { - return RSVP.all(_.map(configs, function(config) { - if (config.name.match(new RegExp('configs/firebase'))) { // ignore firebase config - return RSVP.resolve(); - } - return exports.materializeConfig(config.name, output); - })).then(function() { + return RSVP.all( + _.map(configs, function(config) { + if (config.name.match(new RegExp("configs/firebase"))) { + // ignore firebase config + return RSVP.resolve(); + } + return exports.materializeConfig(config.name, output); + }) + ).then(function() { return output; }); }); @@ -120,25 +131,29 @@ exports.materializeAll = function(projectId) { exports.parseSetArgs = function(args) { var parsed = []; _.forEach(args, function(arg) { - var parts = arg.split('='); + var parts = arg.split("="); var key = parts[0]; if (parts.length < 2) { - throw new FirebaseError('Invalid argument ' + chalk.bold(arg) + ', must be in key=val format'); + throw new FirebaseError( + "Invalid argument " + chalk.bold(arg) + ", must be in key=val format" + ); } if (/[A-Z]/.test(key)) { - throw new FirebaseError('Invalid config name ' + chalk.bold(key) + ', cannot use upper case.'); + throw new FirebaseError( + "Invalid config name " + chalk.bold(key) + ", cannot use upper case." + ); } var id = _keyToIds(key); if (_.includes(exports.RESERVED_NAMESPACES, id.config.toLowerCase())) { - throw new FirebaseError('Cannot set to reserved namespace ' + chalk.bold(id.config)); + throw new FirebaseError("Cannot set to reserved namespace " + chalk.bold(id.config)); } - var val = parts.slice(1).join('='); // So that someone can have '=' within a variable value + var val = parts.slice(1).join("="); // So that someone can have '=' within a variable value parsed.push({ configId: id.config, varId: id.variable, - val: val + val: val, }); }); return parsed; @@ -146,16 +161,16 @@ exports.parseSetArgs = function(args) { exports.parseUnsetArgs = function(args) { var parsed = []; - args = args[0].split(','); + args = args[0].split(","); _.forEach(args, function(key) { var id = _keyToIds(key); if (_.includes(exports.RESERVED_NAMESPACES, id.config.toLowerCase())) { - throw new FirebaseError('Cannot unset reserved namespace ' + chalk.bold(id.config)); + throw new FirebaseError("Cannot unset reserved namespace " + chalk.bold(id.config)); } parsed.push({ configId: id.config, - varId: id.variable + varId: id.variable, }); }); return parsed; diff --git a/lib/functionsConfigClone.js b/lib/functionsConfigClone.js index d7a28ee1..94de49e6 100644 --- a/lib/functionsConfigClone.js +++ b/lib/functionsConfigClone.js @@ -1,20 +1,24 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var chalk = require('chalk'); -var FirebaseError = require('./error'); -var functionsConfig = require('./functionsConfig'); -var runtimeconfig = require('./gcp/runtimeconfig'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var chalk = require("chalk"); +var FirebaseError = require("./error"); +var functionsConfig = require("./functionsConfig"); +var runtimeconfig = require("./gcp/runtimeconfig"); // Tests whether short is a prefix of long var _matchPrefix = function(short, long) { if (short.length > long.length) { return false; } - return _.reduce(short, function(accum, x, i) { - return accum && x === long[i]; - }, true); + return _.reduce( + short, + function(accum, x, i) { + return accum && x === long[i]; + }, + true + ); }; var _applyExcept = function(json, except) { @@ -32,18 +36,20 @@ var _cloneVariable = function(varName, toProject) { var _cloneConfig = function(configName, toProject) { return runtimeconfig.variables.list(configName).then(function(variables) { - return RSVP.all(_.map(variables, function(variable) { - return _cloneVariable(variable.name, toProject); - })); + return RSVP.all( + _.map(variables, function(variable) { + return _cloneVariable(variable.name, toProject); + }) + ); }); }; var _cloneConfigOrVariable = function(key, fromProject, toProject) { - var parts = key.split('.'); + var parts = key.split("."); if (_.includes(exports.RESERVED_NAMESPACES, parts[0])) { - throw new FirebaseError('Cannot clone reserved namespace ' + chalk.bold(parts[0])); + throw new FirebaseError("Cannot clone reserved namespace " + chalk.bold(parts[0])); } - var configName = _.join(['projects', fromProject, 'configs', parts[0]], '/'); + var configName = _.join(["projects", fromProject, "configs", parts[0]], "/"); if (parts.length === 1) { return _cloneConfig(configName, toProject); } @@ -52,7 +58,7 @@ var _cloneConfigOrVariable = function(key, fromProject, toProject) { _.forEach(variables, function(variable) { var varId = functionsConfig.varNameToIds(variable.name).variable; var variablePrefixFilter = parts.slice(1); - if (_matchPrefix(variablePrefixFilter, varId.split('/'))) { + if (_matchPrefix(variablePrefixFilter, varId.split("/"))) { promises.push(_cloneVariable(variable.name, toProject)); } }); @@ -64,15 +70,19 @@ module.exports = function(fromProject, toProject, only, except) { except = except || []; if (only) { - return RSVP.all(_.map(only, function(key) { - return _cloneConfigOrVariable(key, fromProject, toProject); - })); + return RSVP.all( + _.map(only, function(key) { + return _cloneConfigOrVariable(key, fromProject, toProject); + }) + ); } return functionsConfig.materializeAll(fromProject).then(function(toClone) { - _.unset(toClone, 'firebase'); // Do not clone firebase config + _.unset(toClone, "firebase"); // Do not clone firebase config _applyExcept(toClone, except); - return RSVP.all(_.map(toClone, function(val, configId) { - return functionsConfig.setVariablesRecursive(toProject, configId, '', val); - })); + return RSVP.all( + _.map(toClone, function(val, configId) { + return functionsConfig.setVariablesRecursive(toProject, configId, "", val); + }) + ); }); }; diff --git a/lib/functionsEmulator.js b/lib/functionsEmulator.js index 8153586b..dcfabc18 100644 --- a/lib/functionsEmulator.js +++ b/lib/functionsEmulator.js @@ -1,16 +1,16 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var path = require('path'); -var RSVP = require('rsvp'); -var getProjectId = require('./getProjectId'); -var utils = require('./utils'); -var parseTriggers = require('./parseTriggers'); -var functionsConfig = require('./functionsConfig'); -var ensureDefaultCredentials = require('./ensureDefaultCredentials'); -var track = require('./track'); -var logger = require('./logger'); +var _ = require("lodash"); +var chalk = require("chalk"); +var path = require("path"); +var RSVP = require("rsvp"); +var getProjectId = require("./getProjectId"); +var utils = require("./utils"); +var parseTriggers = require("./parseTriggers"); +var functionsConfig = require("./functionsConfig"); +var ensureDefaultCredentials = require("./ensureDefaultCredentials"); +var track = require("./track"); +var logger = require("./logger"); var EmulatorController; @@ -25,14 +25,15 @@ var FunctionsEmulator = function(options) { var _pollOperation = function(op, controller) { return new RSVP.Promise(function(resolve, reject) { var poll = function() { - controller.client.getOperation(op[0].name) + controller.client + .getOperation(op[0].name) .then(function(results) { var operation = results[0]; if (operation.done) { if (operation.response) { resolve(operation.response.value); } else { - reject(operation.error || new Error('Deployment failed')); + reject(operation.error || new Error("Deployment failed")); } } else { setTimeout(poll, 500); @@ -65,9 +66,9 @@ FunctionsEmulator.prototype.stop = function() { FunctionsEmulator.prototype._getPorts = function() { var portsConfig = { supervisorPort: this.options.port, - restPort: this.options.port + 1 + restPort: this.options.port + 1, }; - if (_.includes(this.options.targets, 'hosting')) { + if (_.includes(this.options.targets, "hosting")) { return _.mapValues(portsConfig, function(port) { return port + 1; // bump up port numbers by 1 so hosting can be served on first port }); @@ -76,12 +77,12 @@ FunctionsEmulator.prototype._getPorts = function() { }; FunctionsEmulator.prototype._getConfigOptions = function() { - var logFilename = path.join(process.cwd(), '/cloud-functions-emulator.log'); + var logFilename = path.join(process.cwd(), "/cloud-functions-emulator.log"); var ports = this._getPorts(); return _.merge(ports, { logFile: logFilename, maxIdle: 540000, - tail: true + tail: true, }); }; @@ -91,120 +92,150 @@ FunctionsEmulator.prototype.start = function(shellMode) { var projectId = getProjectId(options); var emulatedFunctions = this.emulatedFunctions; var instance = this; - var functionsDir = path.join(options.config.projectDir, options.config.get('functions.source')); + var functionsDir = path.join(options.config.projectDir, options.config.get("functions.source")); var controllerConfig = this._getConfigOptions(); var firebaseConfig; var emulatedProviders = {}; try { // Require must be inside try/catch, since it's an optional dependency. As well, require may fail if node version incompatible. - var emulatorConfig = require('@google-cloud/functions-emulator/src/config'); + var emulatorConfig = require("@google-cloud/functions-emulator/src/config"); // Must set projectId here instead of later when initializing the controller, // otherwise emulator may crash since it is looking for a config file in /src/options.js - emulatorConfig.set('projectId', projectId); // creates config file in a directory known to the emulator - EmulatorController = require('@google-cloud/functions-emulator/src/cli/controller'); + emulatorConfig.set("projectId", projectId); // creates config file in a directory known to the emulator + EmulatorController = require("@google-cloud/functions-emulator/src/cli/controller"); } catch (err) { var msg = err; - utils.logWarning(chalk.yellow('functions:') + ' Cannot start emulator. ' + msg); + utils.logWarning(chalk.yellow("functions:") + " Cannot start emulator. " + msg); return RSVP.reject(); } this.controller = new EmulatorController(controllerConfig); var controller = this.controller; - utils.logBullet(chalk.cyan.bold('functions:') + ' Preparing to emulate functions.'); - logger.debug('Fetching environment'); + utils.logBullet(chalk.cyan.bold("functions:") + " Preparing to emulate functions."); + logger.debug("Fetching environment"); ensureDefaultCredentials(); - return functionsConfig.getFirebaseConfig(options) - .then(function(result) { - firebaseConfig = JSON.stringify(result); - process.env.FIREBASE_CONFIG = firebaseConfig; - process.env.FIREBASE_PROJECT = firebaseConfig; // To make pre-1.0 firebase-functions SDK work - process.env.GCLOUD_PROJECT = projectId; - logger.debug('Starting @google-cloud/functions-emulator'); - return controller.start(); - }).then(function() { - logger.debug('Parsing function triggers'); - return parseTriggers(projectId, functionsDir, {}, firebaseConfig).catch(function(e) { - utils.logWarning(chalk.yellow('functions:') + ' Failed to load functions source code. ' + - 'Ensure that you have the latest SDK by running ' + chalk.bold('npm i --save firebase-functions') + - ' inside the functions directory.'); - logger.debug('Error during trigger parsing: ', e.message); - return RSVP.reject(e.message); - }); - }).then(function(triggers) { - instance.triggers = triggers; - var promises = _.map(triggers, function(trigger) { - if (trigger.httpsTrigger) { - return controller.deploy(trigger.name, { - entryPoint: trigger.entryPoint, - firebase: true, - source: functionsDir, - triggerHttp: true - }).catch(function(e) { - logger.debug('Error while deploying to emulator: ' + e + '\n' + e.stack); - return RSVP.reject({name: trigger.name}); - }); - } - if (!shellMode) { - utils.logBullet(chalk.cyan.bold('functions:') + ' No HTTPS functions found. Use ' + - chalk.bold('firebase functions:shell') + ' if you would like to emulate other types of functions.'); - return RSVP.resolve(); // Don't emulate non-HTTPS functions if shell not running - } - logger.debug('Deploying functions locally'); - return controller.deploy(trigger.name, { - entryPoint: trigger.entryPoint, - eventType: trigger.eventTrigger.eventType, - firebase: true, - resource: trigger.eventTrigger.resource, - source: functionsDir - }).catch(function(e) { - logger.debug('Error while deploying to emulator: ' + e + '\n' + e.stack); - return RSVP.reject({name: trigger.name}); + return functionsConfig + .getFirebaseConfig(options) + .then(function(result) { + firebaseConfig = JSON.stringify(result); + process.env.FIREBASE_CONFIG = firebaseConfig; + process.env.FIREBASE_PROJECT = firebaseConfig; // To make pre-1.0 firebase-functions SDK work + process.env.GCLOUD_PROJECT = projectId; + logger.debug("Starting @google-cloud/functions-emulator"); + return controller.start(); + }) + .then(function() { + logger.debug("Parsing function triggers"); + return parseTriggers(projectId, functionsDir, {}, firebaseConfig).catch(function(e) { + utils.logWarning( + chalk.yellow("functions:") + + " Failed to load functions source code. " + + "Ensure that you have the latest SDK by running " + + chalk.bold("npm i --save firebase-functions") + + " inside the functions directory." + ); + logger.debug("Error during trigger parsing: ", e.message); + return RSVP.reject(e.message); }); - }); - return RSVP.allSettled(promises); - }).then(function(operations) { - return RSVP.all(_.map(operations, function(operation) { - if (operation.state === 'rejected') { - utils.logWarning(chalk.yellow('functions:') + ' Failed to emulate ' + _.get(operation, 'reason.name', '')); - return RSVP.resolve(); - } - if (!operation.value) { - return RSVP.resolve(); // Emulation was not attempted - } - return _pollOperation(operation.value, controller).then(function(res) { - var funcName = _.chain(res).get('name').split('/').last().value(); - emulatedFunctions.push(funcName); - if (res.httpsTrigger) { - emulatedProviders.HTTPS = true; - var message = chalk.green.bold('functions: ') + funcName.replace(/\-/g, '.'); - instance.urls[funcName] = res.httpsTrigger.url; - if (!shellMode) { - message += ': ' + chalk.bold(res.httpsTrigger.url); - } - utils.logSuccess(message); - } else { - var provider = utils.getFunctionsEventProvider(res.eventTrigger.eventType); - emulatedProviders[provider] = true; - utils.logSuccess(chalk.green.bold('functions: ') + funcName.replace(/\-/g, '.')); + }) + .then(function(triggers) { + instance.triggers = triggers; + var promises = _.map(triggers, function(trigger) { + if (trigger.httpsTrigger) { + return controller + .deploy(trigger.name, { + entryPoint: trigger.entryPoint, + firebase: true, + source: functionsDir, + triggerHttp: true, + }) + .catch(function(e) { + logger.debug("Error while deploying to emulator: " + e + "\n" + e.stack); + return RSVP.reject({ name: trigger.name }); + }); } + if (!shellMode) { + utils.logBullet( + chalk.cyan.bold("functions:") + + " No HTTPS functions found. Use " + + chalk.bold("firebase functions:shell") + + " if you would like to emulate other types of functions." + ); + return RSVP.resolve(); // Don't emulate non-HTTPS functions if shell not running + } + logger.debug("Deploying functions locally"); + return controller + .deploy(trigger.name, { + entryPoint: trigger.entryPoint, + eventType: trigger.eventTrigger.eventType, + firebase: true, + resource: trigger.eventTrigger.resource, + source: functionsDir, + }) + .catch(function(e) { + logger.debug("Error while deploying to emulator: " + e + "\n" + e.stack); + return RSVP.reject({ name: trigger.name }); + }); }); - })); - }).then(function() { - var providerList = _.keys(emulatedProviders).sort().join(','); - if (emulatedFunctions.length > 0) { - track('Functions Emulation', providerList, emulatedFunctions.length); - } else { + return RSVP.allSettled(promises); + }) + .then(function(operations) { + return RSVP.all( + _.map(operations, function(operation) { + if (operation.state === "rejected") { + utils.logWarning( + chalk.yellow("functions:") + + " Failed to emulate " + + _.get(operation, "reason.name", "") + ); + return RSVP.resolve(); + } + if (!operation.value) { + return RSVP.resolve(); // Emulation was not attempted + } + return _pollOperation(operation.value, controller).then(function(res) { + var funcName = _.chain(res) + .get("name") + .split("/") + .last() + .value(); + emulatedFunctions.push(funcName); + if (res.httpsTrigger) { + emulatedProviders.HTTPS = true; + var message = chalk.green.bold("functions: ") + funcName.replace(/\-/g, "."); + instance.urls[funcName] = res.httpsTrigger.url; + if (!shellMode) { + message += ": " + chalk.bold(res.httpsTrigger.url); + } + utils.logSuccess(message); + } else { + var provider = utils.getFunctionsEventProvider(res.eventTrigger.eventType); + emulatedProviders[provider] = true; + utils.logSuccess(chalk.green.bold("functions: ") + funcName.replace(/\-/g, ".")); + } + }); + }) + ); + }) + .then(function() { + var providerList = _.keys(emulatedProviders) + .sort() + .join(","); + if (emulatedFunctions.length > 0) { + track("Functions Emulation", providerList, emulatedFunctions.length); + } else { + return instance.stop(); + } + }) + .catch(function(e) { + if (e) { + utils.logWarning(chalk.yellow("functions:") + " Error from emulator. " + e); + logger.debug(e.stack); + } return instance.stop(); - } - }).catch(function(e) { - if (e) { - utils.logWarning(chalk.yellow('functions:') + ' Error from emulator. ' + e); - logger.debug(e.stack); - } - return instance.stop(); - }); + }); }; module.exports = FunctionsEmulator; diff --git a/lib/functionsShellCommandAction.js b/lib/functionsShellCommandAction.js index 229f616d..bd5f98f2 100644 --- a/lib/functionsShellCommandAction.js +++ b/lib/functionsShellCommandAction.js @@ -1,55 +1,62 @@ -'use strict'; +"use strict"; -var repl = require('repl'); -var _ = require('lodash'); -var RSVP = require('rsvp'); -var request = require('request'); -var util = require('util'); +var repl = require("repl"); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var request = require("request"); +var util = require("util"); -var FunctionsEmulator = require('../lib/functionsEmulator'); -var LocalFunction = require('../lib/localFunction'); -var logger = require('../lib/logger'); +var FunctionsEmulator = require("../lib/functionsEmulator"); +var LocalFunction = require("../lib/localFunction"); +var logger = require("../lib/logger"); module.exports = function(options) { options.port = parseInt(options.port, 10); var emulator = new FunctionsEmulator(options); - return emulator.start(true).then(function() { - if (emulator.emulatedFunctions.length === 0) { - logger.info('No functions emulated.'); - process.exit(); - } - - var writer = function(output) { - // Prevent full print out of Request object when a request is made - if (output instanceof request.Request) { - return 'Sent request to function.'; + return emulator + .start(true) + .then(function() { + if (emulator.emulatedFunctions.length === 0) { + logger.info("No functions emulated."); + process.exit(); } - return util.inspect(output); - }; - var prompt = 'firebase > '; + var writer = function(output) { + // Prevent full print out of Request object when a request is made + if (output instanceof request.Request) { + return "Sent request to function."; + } + return util.inspect(output); + }; - var replServer = repl.start({ - prompt: prompt, - writer: writer, - useColors: true - }); + var prompt = "firebase > "; - _.forEach(emulator.triggers, function(trigger) { - if (_.includes(emulator.emulatedFunctions, trigger.name)) { - var localFunction = new LocalFunction(trigger, emulator.urls, emulator.controller); - var triggerNameDotNotation = trigger.name.replace(/\-/g, '.'); - _.set(replServer.context, triggerNameDotNotation, localFunction.call); - } - }); - replServer.context.help = 'Instructions for the Functions Shell can be found at: ' + - 'https://firebase.google.com/docs/functions/local-emulator'; - }).then(function() { - return new RSVP.Promise(function(resolve) { - process.on('SIGINT', function() { - return emulator.stop().then(resolve).catch(resolve); + var replServer = repl.start({ + prompt: prompt, + writer: writer, + useColors: true, + }); + + _.forEach(emulator.triggers, function(trigger) { + if (_.includes(emulator.emulatedFunctions, trigger.name)) { + var localFunction = new LocalFunction(trigger, emulator.urls, emulator.controller); + var triggerNameDotNotation = trigger.name.replace(/\-/g, "."); + _.set(replServer.context, triggerNameDotNotation, localFunction.call); + } + }); + replServer.context.help = + "Instructions for the Functions Shell can be found at: " + + "https://firebase.google.com/docs/functions/local-emulator"; + }) + .then(function() { + return new RSVP.Promise(function(resolve) { + process.on("SIGINT", function() { + return emulator + .stop() + .then(resolve) + .catch(resolve); + }); }); }); - }); }; diff --git a/lib/gcp/cloudfunctions.js b/lib/gcp/cloudfunctions.js index 5a46097a..54fc859c 100644 --- a/lib/gcp/cloudfunctions.js +++ b/lib/gcp/cloudfunctions.js @@ -1,20 +1,22 @@ -'use strict'; +"use strict"; -var api = require('../api'); -var RSVP = require('rsvp'); -var utils = require('../utils'); -var _ = require('lodash'); -var logger = require('../logger'); -var chalk = require('chalk'); +var api = require("../api"); +var RSVP = require("rsvp"); +var utils = require("../utils"); +var _ = require("lodash"); +var logger = require("../logger"); +var chalk = require("chalk"); -var API_VERSION = 'v1'; +var API_VERSION = "v1"; function _functionsOpLogReject(func, type, err) { - utils.logWarning(chalk.bold.yellow('functions:') + ' failed to ' + type + ' function ' + func); + utils.logWarning(chalk.bold.yellow("functions:") + " failed to " + type + " function " + func); if (err.context.response.statusCode === 429) { logger.debug(err.message); - logger.info('You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, ' + - 'and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more.'); + logger.info( + "You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, " + + "and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more." + ); } else { logger.info(err.message); } @@ -22,30 +24,37 @@ function _functionsOpLogReject(func, type, err) { } function _generateUploadUrl(projectId, location) { - var parent = 'projects/' + projectId + '/locations/' + location; - var endpoint = '/' + API_VERSION + '/' + parent + '/functions:generateUploadUrl'; - return api.request('POST', endpoint, { - auth: true, - json: false, - origin: api.functionsOrigin - }).then(function(result) { - var responseBody = JSON.parse(result.body); - return RSVP.resolve(responseBody.uploadUrl); - }, function(err) { - logger.info('\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support.'); - return RSVP.reject(err); - }); + var parent = "projects/" + projectId + "/locations/" + location; + var endpoint = "/" + API_VERSION + "/" + parent + "/functions:generateUploadUrl"; + return api + .request("POST", endpoint, { + auth: true, + json: false, + origin: api.functionsOrigin, + }) + .then( + function(result) { + var responseBody = JSON.parse(result.body); + return RSVP.resolve(responseBody.uploadUrl); + }, + function(err) { + logger.info( + "\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support." + ); + return RSVP.reject(err); + } + ); } function _createFunction(options) { - var location = 'projects/' + options.projectId + '/locations/' + options.region; - var func = location + '/functions/' + options.functionName; - var endpoint = '/' + API_VERSION + '/' + location + '/functions'; + var location = "projects/" + options.projectId + "/locations/" + options.region; + var func = location + "/functions/" + options.functionName; + var endpoint = "/" + API_VERSION + "/" + location + "/functions"; var data = { sourceUploadUrl: options.sourceUploadUrl, name: func, entryPoint: options.entryPoint, - labels: options.labels + labels: options.labels, }; if (options.availableMemory) { data.availableMemoryMb = options.availableMemory; @@ -53,108 +62,156 @@ function _createFunction(options) { if (options.functionTimeout) { data.timeout = options.functionTimeout; } - return api.request('POST', endpoint, { - auth: true, - data: _.assign(data, options.trigger), - origin: api.functionsOrigin - }).then(function(resp) { - return RSVP.resolve({func: func, eventType: options.eventType, done: false, name: resp.body.name, type: 'create'}); - }, function(err) { - return _functionsOpLogReject(options.functionName, 'create', err); - }); + return api + .request("POST", endpoint, { + auth: true, + data: _.assign(data, options.trigger), + origin: api.functionsOrigin, + }) + .then( + function(resp) { + return RSVP.resolve({ + func: func, + eventType: options.eventType, + done: false, + name: resp.body.name, + type: "create", + }); + }, + function(err) { + return _functionsOpLogReject(options.functionName, "create", err); + } + ); } function _updateFunction(options) { - var location = 'projects/' + options.projectId + '/locations/' + options.region; - var func = location + '/functions/' + options.functionName; - var endpoint = '/' + API_VERSION + '/' + func; - var data = _.assign({ - sourceUploadUrl: options.sourceUploadUrl, - name: func, - labels: options.labels - }, options.trigger); + var location = "projects/" + options.projectId + "/locations/" + options.region; + var func = location + "/functions/" + options.functionName; + var endpoint = "/" + API_VERSION + "/" + func; + var data = _.assign( + { + sourceUploadUrl: options.sourceUploadUrl, + name: func, + labels: options.labels, + }, + options.trigger + ); - var masks = ['sourceUploadUrl', 'name', 'labels']; + var masks = ["sourceUploadUrl", "name", "labels"]; if (options.trigger.eventTrigger) { - masks = _.concat(masks, _.map(_.keys(options.trigger.eventTrigger), function(subkey) { - return 'eventTrigger.' + subkey; - })); + masks = _.concat( + masks, + _.map(_.keys(options.trigger.eventTrigger), function(subkey) { + return "eventTrigger." + subkey; + }) + ); } else { - masks = _.concat(masks, 'httpsTrigger'); + masks = _.concat(masks, "httpsTrigger"); } - return api.request('PATCH', endpoint, { - qs: { - updateMask: masks.join(',') - }, - auth: true, - data: data, - origin: api.functionsOrigin - }).then(function(resp) { - return RSVP.resolve({func: func, done: false, name: resp.body.name, type: 'update'}); - }, function(err) { - return _functionsOpLogReject(options.functionName, 'update', err); - }); + return api + .request("PATCH", endpoint, { + qs: { + updateMask: masks.join(","), + }, + auth: true, + data: data, + origin: api.functionsOrigin, + }) + .then( + function(resp) { + return RSVP.resolve({ + func: func, + done: false, + name: resp.body.name, + type: "update", + }); + }, + function(err) { + return _functionsOpLogReject(options.functionName, "update", err); + } + ); } function _deleteFunction(options) { - var location = 'projects/' + options.projectId + '/locations/' + options.region; - var func = location + '/functions/' + options.functionName; - var endpoint = '/' + API_VERSION + '/' + func; - return api.request('DELETE', endpoint, { - auth: true, - origin: api.functionsOrigin - }).then(function(resp) { - return RSVP.resolve({func: func, done: false, name: resp.body.name, type: 'delete'}); - }, function(err) { - return _functionsOpLogReject(options.functionName, 'delete', err); - }); + var location = "projects/" + options.projectId + "/locations/" + options.region; + var func = location + "/functions/" + options.functionName; + var endpoint = "/" + API_VERSION + "/" + func; + return api + .request("DELETE", endpoint, { + auth: true, + origin: api.functionsOrigin, + }) + .then( + function(resp) { + return RSVP.resolve({ + func: func, + done: false, + name: resp.body.name, + type: "delete", + }); + }, + function(err) { + return _functionsOpLogReject(options.functionName, "delete", err); + } + ); } function _listFunctions(projectId, region) { - var endpoint = '/' + API_VERSION + '/projects/' + projectId + '/locations/' + region + '/functions'; - return api.request('GET', endpoint, { - auth: true, - origin: api.functionsOrigin - }).then(function(resp) { - var functionsList = resp.body.functions || []; - _.forEach(functionsList, function(f) { - f.functionName = f.name.substring(f.name.lastIndexOf('/') + 1); - }); - return RSVP.resolve(functionsList); - }, function(err) { - logger.debug('[functions] failed to list functions for ' + projectId); - logger.debug('[functions] ' + err.message); - return RSVP.reject(err.message); - }); + var endpoint = + "/" + API_VERSION + "/projects/" + projectId + "/locations/" + region + "/functions"; + return api + .request("GET", endpoint, { + auth: true, + origin: api.functionsOrigin, + }) + .then( + function(resp) { + var functionsList = resp.body.functions || []; + _.forEach(functionsList, function(f) { + f.functionName = f.name.substring(f.name.lastIndexOf("/") + 1); + }); + return RSVP.resolve(functionsList); + }, + function(err) { + logger.debug("[functions] failed to list functions for " + projectId); + logger.debug("[functions] " + err.message); + return RSVP.reject(err.message); + } + ); } function _checkOperation(operation) { - return api.request('GET', '/' + API_VERSION + '/' + operation.name, { - auth: true, - origin: api.functionsOrigin - }).then(function(resp) { - if (resp.body.done) { - operation.done = true; - } - if (_.has(resp.body, 'error')) { - operation.error = resp.body.error; - } - return RSVP.resolve(operation); - }, function(err) { - logger.debug('[functions] failed to get status of operation: ' + operation.name); - logger.debug('[functions] ' + err.message); - operation.error = err; - return RSVP.reject(err.message); - }); + return api + .request("GET", "/" + API_VERSION + "/" + operation.name, { + auth: true, + origin: api.functionsOrigin, + }) + .then( + function(resp) { + if (resp.body.done) { + operation.done = true; + } + if (_.has(resp.body, "error")) { + operation.error = resp.body.error; + } + return RSVP.resolve(operation); + }, + function(err) { + logger.debug("[functions] failed to get status of operation: " + operation.name); + logger.debug("[functions] " + err.message); + operation.error = err; + return RSVP.reject(err.message); + } + ); } module.exports = { - DEFAULT_REGION: 'us-central1', + DEFAULT_REGION: "us-central1", generateUploadUrl: _generateUploadUrl, create: _createFunction, update: _updateFunction, delete: _deleteFunction, list: _listFunctions, - check: _checkOperation + check: _checkOperation, }; diff --git a/lib/gcp/cloudlogging.js b/lib/gcp/cloudlogging.js index 9cf0762b..d80e1e7e 100644 --- a/lib/gcp/cloudlogging.js +++ b/lib/gcp/cloudlogging.js @@ -1,25 +1,27 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var api = require('../api'); +var RSVP = require("rsvp"); +var api = require("../api"); -var version = 'v2beta1'; +var version = "v2beta1"; var _listEntries = function(projectId, filter, pageSize, order) { - return api.request('POST', '/' + version + '/entries:list', { - auth: true, - data: { - projectIds: [projectId], - filter: filter, - orderBy: 'timestamp ' + order, - pageSize: pageSize - }, - origin: api.cloudloggingOrigin - }).then(function(result) { - return RSVP.resolve(result.body.entries); - }); + return api + .request("POST", "/" + version + "/entries:list", { + auth: true, + data: { + projectIds: [projectId], + filter: filter, + orderBy: "timestamp " + order, + pageSize: pageSize, + }, + origin: api.cloudloggingOrigin, + }) + .then(function(result) { + return RSVP.resolve(result.body.entries); + }); }; module.exports = { - listEntries: _listEntries + listEntries: _listEntries, }; diff --git a/lib/gcp/index.js b/lib/gcp/index.js index 88d0bb64..75817a57 100644 --- a/lib/gcp/index.js +++ b/lib/gcp/index.js @@ -1,8 +1,8 @@ -'use strict'; +"use strict"; module.exports = { - cloudfunctions: require('./cloudfunctions'), - cloudlogging: require('./cloudlogging'), - storage: require('./storage'), - rules: require('./rules') + cloudfunctions: require("./cloudfunctions"), + cloudlogging: require("./cloudlogging"), + storage: require("./storage"), + rules: require("./rules"), }; diff --git a/lib/gcp/rules.js b/lib/gcp/rules.js index 42fd22ce..c5529e0a 100644 --- a/lib/gcp/rules.js +++ b/lib/gcp/rules.js @@ -1,18 +1,20 @@ -'use strict'; +"use strict"; -var api = require('../api'); -var logger = require('../logger'); -var utils = require('../utils'); +var api = require("../api"); +var logger = require("../logger"); +var utils = require("../utils"); -var API_VERSION = 'v1'; +var API_VERSION = "v1"; function _handleErrorResponse(response) { if (response.body && response.body.error) { - return utils.reject(response.body.error, {code: 2}); + return utils.reject(response.body.error, { code: 2 }); } - logger.debug('[rules] error:', response.status, response.body); - return utils.reject('Unexpected error encountered deploying rules.', {code: 2}); + logger.debug("[rules] error:", response.status, response.body); + return utils.reject("Unexpected error encountered deploying rules.", { + code: 2, + }); } /** @@ -21,20 +23,22 @@ function _handleErrorResponse(response) { * @param {Array} files Array of `{name, content}` for the source files. */ function createRuleset(projectId, files) { - var payload = {source: {files: files}}; + var payload = { source: { files: files } }; - return api.request('POST', '/' + API_VERSION + '/projects/' + projectId + '/rulesets', { - auth: true, - data: payload, - origin: api.rulesOrigin - }).then(function(response) { - if (response.status === 200) { - logger.debug('[rules] created ruleset', response.body.name); - return response.body.name; - } + return api + .request("POST", "/" + API_VERSION + "/projects/" + projectId + "/rulesets", { + auth: true, + data: payload, + origin: api.rulesOrigin, + }) + .then(function(response) { + if (response.status === 200) { + logger.debug("[rules] created ruleset", response.body.name); + return response.body.name; + } - return _handleErrorResponse(response); - }); + return _handleErrorResponse(response); + }); } /** @@ -45,22 +49,24 @@ function createRuleset(projectId, files) { */ function createRelease(projectId, rulesetName, releaseName) { var payload = { - name: 'projects/' + projectId + '/releases/' + releaseName, - rulesetName: rulesetName + name: "projects/" + projectId + "/releases/" + releaseName, + rulesetName: rulesetName, }; - return api.request('POST', '/' + API_VERSION + '/projects/' + projectId + '/releases', { - auth: true, - data: payload, - origin: api.rulesOrigin - }).then(function(response) { - if (response.status === 200) { - logger.debug('[rules] created release', response.body.name); - return response.body.name; - } + return api + .request("POST", "/" + API_VERSION + "/projects/" + projectId + "/releases", { + auth: true, + data: payload, + origin: api.rulesOrigin, + }) + .then(function(response) { + if (response.status === 200) { + logger.debug("[rules] created release", response.body.name); + return response.body.name; + } - return _handleErrorResponse(response); - }); + return _handleErrorResponse(response); + }); } /** @@ -72,41 +78,47 @@ function createRelease(projectId, rulesetName, releaseName) { function updateRelease(projectId, rulesetName, releaseName) { var payload = { release: { - name: 'projects/' + projectId + '/releases/' + releaseName, - rulesetName: rulesetName - } + name: "projects/" + projectId + "/releases/" + releaseName, + rulesetName: rulesetName, + }, }; - return api.request('PATCH', '/' + API_VERSION + '/projects/' + projectId + '/releases/' + releaseName, { - auth: true, - data: payload, - origin: api.rulesOrigin - }).then(function(response) { - if (response.status === 200) { - logger.debug('[rules] updated release', response.body.name); - return response.body.name; - } + return api + .request("PATCH", "/" + API_VERSION + "/projects/" + projectId + "/releases/" + releaseName, { + auth: true, + data: payload, + origin: api.rulesOrigin, + }) + .then(function(response) { + if (response.status === 200) { + logger.debug("[rules] updated release", response.body.name); + return response.body.name; + } - return _handleErrorResponse(response); - }); + return _handleErrorResponse(response); + }); } function updateOrCreateRelease(projectId, rulesetName, releaseName) { - logger.debug('[rules] releasing', releaseName, 'with ruleset', rulesetName); + logger.debug("[rules] releasing", releaseName, "with ruleset", rulesetName); return updateRelease(projectId, rulesetName, releaseName).catch(function() { - logger.debug('[rules] ruleset update failed, attempting to create instead'); + logger.debug("[rules] ruleset update failed, attempting to create instead"); return createRelease(projectId, rulesetName, releaseName); }); } function testRuleset(projectId, files) { - return api.request('POST', '/' + API_VERSION + '/projects/' + encodeURIComponent(projectId) + ':test', { - origin: api.rulesOrigin, - data: { - source: {files: files} - }, - auth: true - }); + return api.request( + "POST", + "/" + API_VERSION + "/projects/" + encodeURIComponent(projectId) + ":test", + { + origin: api.rulesOrigin, + data: { + source: { files: files }, + }, + auth: true, + } + ); } module.exports = { @@ -114,5 +126,5 @@ module.exports = { createRelease: createRelease, updateRelease: updateRelease, updateOrCreateRelease: updateOrCreateRelease, - testRuleset: testRuleset + testRuleset: testRuleset, }; diff --git a/lib/gcp/runtimeconfig.js b/lib/gcp/runtimeconfig.js index f0a98c24..fe711fab 100644 --- a/lib/gcp/runtimeconfig.js +++ b/lib/gcp/runtimeconfig.js @@ -1,20 +1,19 @@ -'use strict'; +"use strict"; -var api = require('../api'); -var RSVP = require('rsvp'); -var utils = require('../utils'); -var logger = require('../logger'); -var _ = require('lodash'); +var api = require("../api"); +var RSVP = require("rsvp"); +var utils = require("../utils"); +var logger = require("../logger"); +var _ = require("lodash"); -var API_VERSION = 'v1beta1'; +var API_VERSION = "v1beta1"; function _retryOnServerError(requestFunction) { return requestFunction().catch(function(err) { - if (_.includes([500, 503], _.get(err, 'context.response.statusCode'))) { + if (_.includes([500, 503], _.get(err, "context.response.statusCode"))) { return new Promise(function(resolve) { setTimeout(resolve, 1000); - }) - .then(requestFunction); + }).then(requestFunction); } return RSVP.reject(err); }); @@ -22,9 +21,9 @@ function _retryOnServerError(requestFunction) { function _listConfigs(projectId) { return _retryOnServerError(function() { - return api.request('GET', utils.endpoint([API_VERSION, 'projects', projectId, 'configs']), { + return api.request("GET", utils.endpoint([API_VERSION, "projects", projectId, "configs"]), { auth: true, - origin: api.runtimeconfigOrigin + origin: api.runtimeconfigOrigin, }); }).then(function(resp) { return RSVP.resolve(resp.body.configs); @@ -32,19 +31,19 @@ function _listConfigs(projectId) { } function _createConfig(projectId, configId) { - var path = _.join(['projects', projectId, 'configs'], '/'); + var path = _.join(["projects", projectId, "configs"], "/"); var endpoint = utils.endpoint([API_VERSION, path]); return _retryOnServerError(function() { - return api.request('POST', endpoint, { + return api.request("POST", endpoint, { auth: true, origin: api.runtimeconfigOrigin, data: { - name: path + '/' + configId - } + name: path + "/" + configId, + }, }); }).catch(function(err) { - if (_.get(err, 'context.response.statusCode') === 409) { - // Config has already been created as part of a parallel operation during firebase functions:config:set + if (_.get(err, "context.response.statusCode") === 409) { + // Config has already been created as part of a parallel operation during firebase functions:config:set return RSVP.resolve(); } return RSVP.reject(err); @@ -53,13 +52,17 @@ function _createConfig(projectId, configId) { function _deleteConfig(projectId, configId) { return _retryOnServerError(function() { - return api.request('DELETE', utils.endpoint([API_VERSION, 'projects', projectId, 'configs', configId]), { - auth: true, - origin: api.runtimeconfigOrigin - }); + return api.request( + "DELETE", + utils.endpoint([API_VERSION, "projects", projectId, "configs", configId]), + { + auth: true, + origin: api.runtimeconfigOrigin, + } + ); }).catch(function(err) { - if (_.get(err, 'context.response.statusCode') === 404) { - logger.debug('Config already deleted.'); + if (_.get(err, "context.response.statusCode") === 404) { + logger.debug("Config already deleted."); return RSVP.resolve(); } return RSVP.reject(err); @@ -68,9 +71,9 @@ function _deleteConfig(projectId, configId) { function _listVariables(configPath) { return _retryOnServerError(function() { - return api.request('GET', utils.endpoint([API_VERSION, configPath, 'variables']), { + return api.request("GET", utils.endpoint([API_VERSION, configPath, "variables"]), { auth: true, - origin: api.runtimeconfigOrigin + origin: api.runtimeconfigOrigin, }); }).then(function(resp) { return RSVP.resolve(resp.body.variables); @@ -79,9 +82,9 @@ function _listVariables(configPath) { function _getVariable(varPath) { return _retryOnServerError(function() { - return api.request('GET', utils.endpoint([API_VERSION, varPath]), { + return api.request("GET", utils.endpoint([API_VERSION, varPath]), { auth: true, - origin: api.runtimeconfigOrigin + origin: api.runtimeconfigOrigin, }); }).then(function(resp) { return RSVP.resolve(resp.body); @@ -89,19 +92,20 @@ function _getVariable(varPath) { } function _createVariable(projectId, configId, varId, value) { - var path = _.join(['projects', projectId, 'configs', configId, 'variables'], '/'); + var path = _.join(["projects", projectId, "configs", configId, "variables"], "/"); var endpoint = utils.endpoint([API_VERSION, path]); return _retryOnServerError(function() { - return api.request('POST', endpoint, { + return api.request("POST", endpoint, { auth: true, origin: api.runtimeconfigOrigin, data: { - name: path + '/' + varId, - text: value - } + name: path + "/" + varId, + text: value, + }, }); }).catch(function(err) { - if (_.get(err, 'context.response.statusCode') === 404) { // parent config doesn't exist yet + if (_.get(err, "context.response.statusCode") === 404) { + // parent config doesn't exist yet return _createConfig(projectId, configId).then(function() { return _createVariable(projectId, configId, varId, value); }); @@ -111,43 +115,46 @@ function _createVariable(projectId, configId, varId, value) { } function _updateVariable(projectId, configId, varId, value) { - var path = _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/'); + var path = _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); var endpoint = utils.endpoint([API_VERSION, path]); return _retryOnServerError(function() { - return api.request('PUT', endpoint, { + return api.request("PUT", endpoint, { auth: true, origin: api.runtimeconfigOrigin, data: { name: path, - text: value - } + text: value, + }, }); }); } function _setVariable(projectId, configId, varId, value) { - var path = _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/'); - return _getVariable(path).then(function() { - return _updateVariable(projectId, configId, varId, value); - }).catch(function(err) { - if (_.get(err, 'context.response.statusCode') === 404) { - return _createVariable(projectId, configId, varId, value); - } - return RSVP.reject(err); - }); + var path = _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); + return _getVariable(path) + .then(function() { + return _updateVariable(projectId, configId, varId, value); + }) + .catch(function(err) { + if (_.get(err, "context.response.statusCode") === 404) { + return _createVariable(projectId, configId, varId, value); + } + return RSVP.reject(err); + }); } function _deleteVariable(projectId, configId, varId) { - var endpoint = utils.endpoint([API_VERSION, 'projects', projectId, 'configs', configId, 'variables', varId]) - + '?recursive=true'; + var endpoint = + utils.endpoint([API_VERSION, "projects", projectId, "configs", configId, "variables", varId]) + + "?recursive=true"; return _retryOnServerError(function() { - return api.request('DELETE', endpoint, { + return api.request("DELETE", endpoint, { auth: true, - origin: api.runtimeconfigOrigin + origin: api.runtimeconfigOrigin, }); }).catch(function(err) { - if (_.get(err, 'context.response.statusCode') === 404) { - logger.debug('Variable already deleted.'); + if (_.get(err, "context.response.statusCode") === 404) { + logger.debug("Variable already deleted."); return RSVP.resolve(); } return RSVP.reject(err); @@ -158,12 +165,12 @@ module.exports = { configs: { list: _listConfigs, create: _createConfig, - delete: _deleteConfig + delete: _deleteConfig, }, variables: { list: _listVariables, get: _getVariable, set: _setVariable, - delete: _deleteVariable - } + delete: _deleteVariable, + }, }; diff --git a/lib/gcp/storage.js b/lib/gcp/storage.js index fd26103b..ebe448a1 100644 --- a/lib/gcp/storage.js +++ b/lib/gcp/storage.js @@ -1,39 +1,50 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var api = require('../api'); -var logger = require('../logger'); -var FirebaseError = require('../error'); +var RSVP = require("rsvp"); +var api = require("../api"); +var logger = require("../logger"); +var FirebaseError = require("../error"); function _getDefaultBucket(projectId) { - return api.request('GET', '/v1/apps/' + projectId, { - auth: true, - origin: api.appengineOrigin - }).then(function(resp) { - if (resp.body.defaultBucket === 'undefined') { - logger.debug('Default storage bucket is undefined.'); - return RSVP.reject(new FirebaseError('Your project is being set up. Please wait a minute before deploying again.')); - } - return RSVP.resolve(resp.body.defaultBucket); - }, function(err) { - logger.info('\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support.'); - return RSVP.reject(err); - }); + return api + .request("GET", "/v1/apps/" + projectId, { + auth: true, + origin: api.appengineOrigin, + }) + .then( + function(resp) { + if (resp.body.defaultBucket === "undefined") { + logger.debug("Default storage bucket is undefined."); + return RSVP.reject( + new FirebaseError( + "Your project is being set up. Please wait a minute before deploying again." + ) + ); + } + return RSVP.resolve(resp.body.defaultBucket); + }, + function(err) { + logger.info( + "\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support." + ); + return RSVP.reject(err); + } + ); } function _uploadSource(source, uploadUrl) { - return api.request('PUT', uploadUrl, { + return api.request("PUT", uploadUrl, { data: source.stream, headers: { - 'Content-Type': 'application/zip', - 'x-goog-content-length-range': '0,104857600' + "Content-Type": "application/zip", + "x-goog-content-length-range": "0,104857600", }, json: false, - origin: api.storageOrigin + origin: api.storageOrigin, }); } module.exports = { getDefaultBucket: _getDefaultBucket, - upload: _uploadSource + upload: _uploadSource, }; diff --git a/lib/getInstanceId.js b/lib/getInstanceId.js index 16b3eced..30ba02bb 100644 --- a/lib/getInstanceId.js +++ b/lib/getInstanceId.js @@ -1,8 +1,8 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var api = require('./api'); -var FirebaseError = require('./error'); +var _ = require("lodash"); +var api = require("./api"); +var FirebaseError = require("./error"); /** * Tries to determine the instance ID for the provided @@ -12,9 +12,9 @@ var FirebaseError = require('./error'); */ module.exports = function(options) { return api.getProject(options.project).then(function(project) { - if (!_.has(project, ['instances', 'database', 0])) { - throw new FirebaseError('No instance found for project. Please try a different project.', { - exit: 1 + if (!_.has(project, ["instances", "database", 0])) { + throw new FirebaseError("No instance found for project. Please try a different project.", { + exit: 1, }); } return project.instances.database[0]; diff --git a/lib/getProjectId.js b/lib/getProjectId.js index 4e965d4e..5ea5f1a3 100644 --- a/lib/getProjectId.js +++ b/lib/getProjectId.js @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); +var _ = require("lodash"); +var chalk = require("chalk"); -var FirebaseError = require('./error'); +var FirebaseError = require("./error"); /** * Tries to determine the correct app name for commands that @@ -16,19 +16,30 @@ var FirebaseError = require('./error'); */ module.exports = function(options, allowNull) { if (!options.project && !allowNull) { - var aliases = _.get(options, 'rc.projects', {}); + var aliases = _.get(options, "rc.projects", {}); var aliasCount = _.size(aliases); if (aliasCount === 0) { - throw new FirebaseError('No project active. Run with ' + chalk.bold('--project ') + ' or define an alias by\nrunning ' + chalk.bold('firebase use --add'), { - exit: 1 - }); + throw new FirebaseError( + "No project active. Run with " + + chalk.bold("--project ") + + " or define an alias by\nrunning " + + chalk.bold("firebase use --add"), + { + exit: 1, + } + ); } else { var aliasList = _.map(aliases, function(projectId, aname) { - return ' ' + aname + ' (' + projectId + ')'; - }).join('\n'); + return " " + aname + " (" + projectId + ")"; + }).join("\n"); - throw new FirebaseError('No project active, but project aliases are available.\n\nRun ' + chalk.bold('firebase use ') + ' with one of these options:\n\n' + aliasList); + throw new FirebaseError( + "No project active, but project aliases are available.\n\nRun " + + chalk.bold("firebase use ") + + " with one of these options:\n\n" + + aliasList + ); } } return options.project; diff --git a/lib/getProjectNumber.js b/lib/getProjectNumber.js index 7aa83989..46cb4d5d 100644 --- a/lib/getProjectNumber.js +++ b/lib/getProjectNumber.js @@ -1,19 +1,21 @@ -'use strict'; +"use strict"; -var getProjectId = require('./getProjectId'); -var api = require('./api'); -var RSVP = require('rsvp'); +var getProjectId = require("./getProjectId"); +var api = require("./api"); +var RSVP = require("rsvp"); module.exports = function(options) { if (options.projectNumber) { return RSVP.resolve(options.projectNumber); } var projectId = getProjectId(options); - return api.request('GET', '/v1/projects/' + projectId, { - auth: true, - origin: api.resourceManagerOrigin - }).then(function(response) { - options.projectNumber = response.body.projectNumber; - return options.projectNumber; - }); + return api + .request("GET", "/v1/projects/" + projectId, { + auth: true, + origin: api.resourceManagerOrigin, + }) + .then(function(response) { + options.projectNumber = response.body.projectNumber; + return options.projectNumber; + }); }; diff --git a/lib/handlePreviewToggles.js b/lib/handlePreviewToggles.js index 5f2cbae1..5e200932 100644 --- a/lib/handlePreviewToggles.js +++ b/lib/handlePreviewToggles.js @@ -1,26 +1,26 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var auth = require('./auth'); -var configstore = require('./configstore'); -var previews = require('./previews'); +var auth = require("./auth"); +var configstore = require("./configstore"); +var previews = require("./previews"); var _errorOut = function(name) { - console.log(chalk.bold.red('Error:'), 'Did not recognize preview feature', chalk.bold(name)); + console.log(chalk.bold.red("Error:"), "Did not recognize preview feature", chalk.bold(name)); process.exit(1); }; module.exports = function(args) { var isValidPreview = _.has(previews, args[1]); - if (args[0] === '--open-sesame') { + if (args[0] === "--open-sesame") { if (isValidPreview) { - console.log('Enabling preview feature', chalk.bold(args[1]) + '...'); + console.log("Enabling preview feature", chalk.bold(args[1]) + "..."); previews[args[1]] = true; - configstore.set('previews', previews); - var tokens = configstore.get('tokens'); + configstore.set("previews", previews); + var tokens = configstore.get("tokens"); var next; if (tokens && tokens.refresh_token) { @@ -29,19 +29,19 @@ module.exports = function(args) { next = RSVP.resolve(); } return next.then(function() { - console.log('Preview feature enabled!'); + console.log("Preview feature enabled!"); console.log(); - console.log('Please run', chalk.bold('firebase login'), 'to re-authorize the CLI.'); + console.log("Please run", chalk.bold("firebase login"), "to re-authorize the CLI."); return process.exit(0); }); } _errorOut(); - } else if (args[0] === '--close-sesame') { + } else if (args[0] === "--close-sesame") { if (isValidPreview) { - console.log('Disabling preview feature', chalk.bold(args[1])); + console.log("Disabling preview feature", chalk.bold(args[1])); _.unset(previews, args[1]); - configstore.set('previews', previews); + configstore.set("previews", previews); return process.exit(0); } diff --git a/lib/hosting/functionsProxy.js b/lib/hosting/functionsProxy.js index 06d1851e..a0380c06 100644 --- a/lib/hosting/functionsProxy.js +++ b/lib/hosting/functionsProxy.js @@ -1,95 +1,107 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var logger = require('../logger'); -var request = require('request'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var logger = require("../logger"); +var request = require("request"); +var RSVP = require("rsvp"); -var getProjectId = require('../getProjectId'); +var getProjectId = require("../getProjectId"); function _makeVary(vary) { if (!vary) { - return 'Accept-Encoding, Authorization, Cookie'; + return "Accept-Encoding, Authorization, Cookie"; } var varies = vary.split(/, ?/).map(function(v) { - return v.split('-').map(function(part) { return _.capitalize(part); }).join('-'); + return v + .split("-") + .map(function(part) { + return _.capitalize(part); + }) + .join("-"); }); - ['Accept-Encoding', 'Authorization', 'Cookie'].forEach(function(requiredVary) { + ["Accept-Encoding", "Authorization", "Cookie"].forEach(function(requiredVary) { if (!_.includes(varies, requiredVary)) { varies.push(requiredVary); } }); - return varies.join(', '); + return varies.join(", "); } module.exports = function(options) { return function(rewrite) { var url; var destLabel; - if (_.includes(options.targets, 'functions')) { - destLabel = 'local'; - url = 'http://localhost:' + (options.port + 1) + '/' + getProjectId(options) + '/us-central1/' + rewrite.function; + if (_.includes(options.targets, "functions")) { + destLabel = "local"; + url = + "http://localhost:" + + (options.port + 1) + + "/" + + getProjectId(options) + + "/us-central1/" + + rewrite.function; } else { - destLabel = 'live'; - url = 'https://us-central1-' + getProjectId(options) + '.cloudfunctions.net/' + rewrite.function; + destLabel = "live"; + url = + "https://us-central1-" + getProjectId(options) + ".cloudfunctions.net/" + rewrite.function; } return RSVP.resolve(function(req, res, next) { - logger.info('[hosting] Rewriting', req.url, 'to', destLabel, 'function', rewrite.function); + logger.info("[hosting] Rewriting", req.url, "to", destLabel, "function", rewrite.function); // Extract the __session cookie from headers to forward it to the functions - var sessionCookie = (req.headers.cookie || '') - .split(/; ?/) - .find(function(c) { - return c.trim().indexOf('__session=') === 0; - }); + var sessionCookie = (req.headers.cookie || "").split(/; ?/).find(function(c) { + return c.trim().indexOf("__session=") === 0; + }); var proxied = request({ method: req.method, qs: req.query, url: url + req.url, headers: { - 'X-Forwarded-Host': req.headers.host, - 'X-Original-Url': req.url, - 'Pragma': 'no-cache', - 'Cache-Control': 'no-cache, no-store', + "X-Forwarded-Host": req.headers.host, + "X-Original-Url": req.url, + Pragma: "no-cache", + "Cache-Control": "no-cache, no-store", // forward the parsed __session cookie if any - 'Cookie': sessionCookie + Cookie: sessionCookie, }, followRedirect: false, - timeout: 60000 + timeout: 60000, }); req.pipe(proxied); - proxied.on('error', function(err) { - if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') { + proxied.on("error", function(err) { + if (err.code === "ETIMEDOUT" || err.code === "ESOCKETTIMEDOUT") { res.statusCode = 504; - res.end('Timed out waiting for function to respond.'); + res.end("Timed out waiting for function to respond."); } res.statusCode = 500; - return res.end('An internal error occurred while connecting to Cloud Function "' + rewrite.function + '"'); + return res.end( + 'An internal error occurred while connecting to Cloud Function "' + rewrite.function + '"' + ); }); - return proxied.on('response', function(response) { + return proxied.on("response", function(response) { if ( response.statusCode === 404 && - response.headers['x-cascade'] && - response.headers['x-cascade'].toUpperCase() === 'PASS' + response.headers["x-cascade"] && + response.headers["x-cascade"].toUpperCase() === "PASS" ) { return next(); } // default to private cache - if (!response.headers['cache-control']) { - response.headers['cache-control'] = 'private'; + if (!response.headers["cache-control"]) { + response.headers["cache-control"] = "private"; } // don't allow cookies to be set on non-private cached responses - if (response.headers['cache-control'].indexOf('private') < 0) { - delete response.headers['set-cookie']; + if (response.headers["cache-control"].indexOf("private") < 0) { + delete response.headers["set-cookie"]; } response.headers.vary = _makeVary(response.headers.vary); diff --git a/lib/hosting/implicitInit.js b/lib/hosting/implicitInit.js index 72d8bf02..e80472bb 100644 --- a/lib/hosting/implicitInit.js +++ b/lib/hosting/implicitInit.js @@ -1,17 +1,17 @@ -'use strict'; +"use strict"; -var fs = require('fs'); +var fs = require("fs"); -var fetchWebSetup = require('../fetchWebSetup'); +var fetchWebSetup = require("../fetchWebSetup"); -var INIT_TEMPLATE = fs.readFileSync(__dirname + '/../../templates/hosting/init.js', 'utf8'); +var INIT_TEMPLATE = fs.readFileSync(__dirname + "/../../templates/hosting/init.js", "utf8"); module.exports = function(options) { return fetchWebSetup(options).then(function(config) { var configJson = JSON.stringify(config, null, 2); return { - js: INIT_TEMPLATE.replace('{/*--CONFIG--*/}', configJson), - json: configJson + js: INIT_TEMPLATE.replace("{/*--CONFIG--*/}", configJson), + json: configJson, }; }); }; diff --git a/lib/hosting/initMiddleware.js b/lib/hosting/initMiddleware.js index 0687d829..cee60bfd 100644 --- a/lib/hosting/initMiddleware.js +++ b/lib/hosting/initMiddleware.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; -var request = require('request'); +var request = require("request"); var SDK_PATH_REGEXP = /^\/__\/firebase\/([^/]+)\/([^/]+)$/; @@ -8,18 +8,18 @@ module.exports = function(init) { return function(req, res, next) { var match = req.url.match(SDK_PATH_REGEXP); if (match) { - var url = 'https://www.gstatic.com/firebasejs/' + match[1] + '/' + match[2]; - var preq = request(url).on('response', function(pres) { + var url = "https://www.gstatic.com/firebasejs/" + match[1] + "/" + match[2]; + var preq = request(url).on("response", function(pres) { if (pres.statusCode === 404) { return next(); } return preq.pipe(res); }); - } else if (req.url === '/__/firebase/init.js') { - res.setHeader('Content-Type', 'application/javascript'); + } else if (req.url === "/__/firebase/init.js") { + res.setHeader("Content-Type", "application/javascript"); res.end(init.js); - } else if (req.url === '/__/firebase/init.json') { - res.setHeader('Content-Type', 'application/json'); + } else if (req.url === "/__/firebase/init.json") { + res.setHeader("Content-Type", "application/json"); res.end(init.json); } else { next(); diff --git a/lib/identifierToProjectId.js b/lib/identifierToProjectId.js index 44c7d64f..b85dab4f 100644 --- a/lib/identifierToProjectId.js +++ b/lib/identifierToProjectId.js @@ -1,8 +1,8 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); +var _ = require("lodash"); -var api = require('./api'); +var api = require("./api"); module.exports = function(id) { return api.getProjects().then(function(projects) { @@ -13,7 +13,7 @@ module.exports = function(id) { for (var projectId in projects) { if (projects.hasOwnProperty(projectId)) { - var instance = _.get(projects, [projectId, 'instances', 'database', '0']); + var instance = _.get(projects, [projectId, "instances", "database", "0"]); if (id === instance) { return projectId; } diff --git a/lib/init/features/database.js b/lib/init/features/database.js index b3728014..9119efb8 100644 --- a/lib/init/features/database.js +++ b/lib/init/features/database.js @@ -1,35 +1,53 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var api = require('../../api'); -var prompt = require('../../prompt'); -var logger = require('../../logger'); -var utils = require('../../utils'); -var fsutils = require('../../fsutils'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var api = require("../../api"); +var prompt = require("../../prompt"); +var logger = require("../../logger"); +var utils = require("../../utils"); +var fsutils = require("../../fsutils"); +var RSVP = require("rsvp"); -var defaultRules = JSON.stringify({rules: {'.read': 'auth != null', '.write': 'auth != null'}}, null, 2); +var defaultRules = JSON.stringify( + { rules: { ".read": "auth != null", ".write": "auth != null" } }, + null, + 2 +); var _getDBRules = function(instance) { if (!instance) { return RSVP.resolve(defaultRules); } - return api.request('GET', '/.settings/rules.json', { - auth: true, - origin: utils.addSubdomain(api.realtimeOrigin, instance) - }).then(function(response) { - return response.body; - }); + return api + .request("GET", "/.settings/rules.json", { + auth: true, + origin: utils.addSubdomain(api.realtimeOrigin, instance), + }) + .then(function(response) { + return response.body; + }); }; var _writeDBRules = function(instance, filename, config) { - return _getDBRules(instance).then(function(rules) { - return config.writeProjectFile(filename, rules); - }).then(function() { - utils.logSuccess('Database Rules for ' + chalk.bold(instance) + ' have been downloaded to ' + chalk.bold(filename) + '.'); - logger.info('Future modifications to ' + chalk.bold(filename) + ' will update Database Rules when you run'); - logger.info(chalk.bold('firebase deploy') + '.'); - }); + return _getDBRules(instance) + .then(function(rules) { + return config.writeProjectFile(filename, rules); + }) + .then(function() { + utils.logSuccess( + "Database Rules for " + + chalk.bold(instance) + + " have been downloaded to " + + chalk.bold(filename) + + "." + ); + logger.info( + "Future modifications to " + + chalk.bold(filename) + + " will update Database Rules when you run" + ); + logger.info(chalk.bold("firebase deploy") + "."); + }); }; module.exports = function(setup, config) { @@ -38,38 +56,49 @@ module.exports = function(setup, config) { var filename = null; logger.info(); - logger.info('Firebase Realtime Database Rules allow you to define how your data should be'); - logger.info('structured and when your data can be read from and written to.'); + logger.info("Firebase Realtime Database Rules allow you to define how your data should be"); + logger.info("structured and when your data can be read from and written to."); logger.info(); return prompt(setup.config.database, [ { - type: 'input', - name: 'rules', - message: 'What file should be used for Database Rules?', - default: 'database.rules.json' - } - ]).then(function() { - filename = setup.config.database.rules; + type: "input", + name: "rules", + message: "What file should be used for Database Rules?", + default: "database.rules.json", + }, + ]) + .then(function() { + filename = setup.config.database.rules; - if (fsutils.fileExistsSync(filename)) { - var msg = 'File ' + chalk.bold(filename) + ' already exists.' - + ' Do you want to overwrite it with the Database Rules for ' + chalk.bold(instance) - + ' from the Firebase Console?'; - return prompt.once({ - type: 'confirm', - message: msg, - default: false - }); - } - return RSVP.resolve(true); - }).then(function(overwrite) { - if (overwrite) { - return _writeDBRules(instance, filename, config); - } - logger.info('Skipping overwrite of Database Rules.'); - logger.info('The rules defined in ' + chalk.bold(filename) + ' will be published when you do ' + chalk.bold('firebase deploy') + '.'); - return RSVP.resolve(); - }); + if (fsutils.fileExistsSync(filename)) { + var msg = + "File " + + chalk.bold(filename) + + " already exists." + + " Do you want to overwrite it with the Database Rules for " + + chalk.bold(instance) + + " from the Firebase Console?"; + return prompt.once({ + type: "confirm", + message: msg, + default: false, + }); + } + return RSVP.resolve(true); + }) + .then(function(overwrite) { + if (overwrite) { + return _writeDBRules(instance, filename, config); + } + logger.info("Skipping overwrite of Database Rules."); + logger.info( + "The rules defined in " + + chalk.bold(filename) + + " will be published when you do " + + chalk.bold("firebase deploy") + + "." + ); + return RSVP.resolve(); + }); }; - diff --git a/lib/init/features/firestore.js b/lib/init/features/firestore.js index 5ca0aa19..1e4dc200 100644 --- a/lib/init/features/firestore.js +++ b/lib/init/features/firestore.js @@ -1,28 +1,34 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var fs = require('fs'); +var chalk = require("chalk"); +var fs = require("fs"); -var prompt = require('../../prompt'); -var logger = require('../../logger'); +var prompt = require("../../prompt"); +var logger = require("../../logger"); -var RULES_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/firestore/firestore.rules', 'utf8'); -var INDEXES_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/firestore/firestore.indexes.json', 'utf8'); +var RULES_TEMPLATE = fs.readFileSync( + __dirname + "/../../../templates/init/firestore/firestore.rules", + "utf8" +); +var INDEXES_TEMPLATE = fs.readFileSync( + __dirname + "/../../../templates/init/firestore/firestore.indexes.json", + "utf8" +); var _initRules = function(setup, config) { logger.info(); - logger.info('Firestore Security Rules allow you to define how and when to allow'); - logger.info('requests. You can keep these rules in your project directory'); - logger.info('and publish them with ' + chalk.bold('firebase deploy') + '.'); + logger.info("Firestore Security Rules allow you to define how and when to allow"); + logger.info("requests. You can keep these rules in your project directory"); + logger.info("and publish them with " + chalk.bold("firebase deploy") + "."); logger.info(); return prompt(setup.config.firestore, [ { - type: 'input', - name: 'rules', - message: 'What file should be used for Firestore Rules?', - default: 'firestore.rules' - } + type: "input", + name: "rules", + message: "What file should be used for Firestore Rules?", + default: "firestore.rules", + }, ]).then(function() { return config.writeProjectFile(setup.config.firestore.rules, RULES_TEMPLATE); }); @@ -30,20 +36,19 @@ var _initRules = function(setup, config) { var _initIndexes = function(setup, config) { logger.info(); - logger.info('Firestore indexes allow you to perform complex queries while'); - logger.info('maintaining performance that scales with the size of the result'); - logger.info('set. You can keep index definitions in your project directory'); - logger.info('and publish them with ' + chalk.bold('firebase deploy') + '.'); + logger.info("Firestore indexes allow you to perform complex queries while"); + logger.info("maintaining performance that scales with the size of the result"); + logger.info("set. You can keep index definitions in your project directory"); + logger.info("and publish them with " + chalk.bold("firebase deploy") + "."); logger.info(); - return prompt(setup.config.firestore, [ { - type: 'input', - name: 'indexes', - message: 'What file should be used for Firestore indexes?', - default: 'firestore.indexes.json' - } + type: "input", + name: "indexes", + message: "What file should be used for Firestore indexes?", + default: "firestore.indexes.json", + }, ]).then(function() { return config.writeProjectFile(setup.config.firestore.indexes, INDEXES_TEMPLATE); }); @@ -52,8 +57,7 @@ var _initIndexes = function(setup, config) { module.exports = function(setup, config) { setup.config.firestore = {}; - return _initRules(setup, config) - .then(function() { - return _initIndexes(setup, config); - }); + return _initRules(setup, config).then(function() { + return _initIndexes(setup, config); + }); }; diff --git a/lib/init/features/functions/index.js b/lib/init/features/functions/index.js index 2577875b..9a66e69b 100644 --- a/lib/init/features/functions/index.js +++ b/lib/init/features/functions/index.js @@ -1,28 +1,32 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var RSVP = require('rsvp'); -var _ = require('lodash'); +var chalk = require("chalk"); +var RSVP = require("rsvp"); +var _ = require("lodash"); -var logger = require('../../../logger'); -var prompt = require('../../../prompt'); -var enableApi = require('../../../ensureApiEnabled').enable; -var requireAccess = require('../../../requireAccess'); -var scopes = require('../../../scopes'); +var logger = require("../../../logger"); +var prompt = require("../../../prompt"); +var enableApi = require("../../../ensureApiEnabled").enable; +var requireAccess = require("../../../requireAccess"); +var scopes = require("../../../scopes"); 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( + "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 = {}; - var projectId = _.get(setup, 'rcfile.projects.default'); + var projectId = _.get(setup, "rcfile.projects.default"); var enableApis; if (projectId) { - enableApis = requireAccess({project: projectId}, [scopes.CLOUD_PLATFORM]).then(function() { - enableApi(projectId, 'cloudfunctions.googleapis.com'); - enableApi(projectId, 'runtimeconfig.googleapis.com'); + enableApis = requireAccess({ project: projectId }, [scopes.CLOUD_PLATFORM]).then(function() { + enableApi(projectId, "cloudfunctions.googleapis.com"); + enableApi(projectId, "runtimeconfig.googleapis.com"); }); } else { enableApis = RSVP.resolve(); @@ -30,22 +34,23 @@ module.exports = function(setup, config) { return enableApis.then(function() { return prompt(setup.functions, [ { - type: 'list', - name: 'language', - message: 'What language would you like to use to write Cloud Functions?', - default: 'javascript', + type: "list", + name: "language", + message: "What language would you like to use to write Cloud Functions?", + default: "javascript", choices: [ { - name: 'JavaScript', - value: 'javascript' - }, { - name: 'TypeScript', - value: 'typescript' - } - ] - } + name: "JavaScript", + value: "javascript", + }, + { + name: "TypeScript", + value: "typescript", + }, + ], + }, ]).then(function() { - return require('./' + setup.functions.language)(setup, config); + return require("./" + setup.functions.language)(setup, config); }); }); }; diff --git a/lib/init/features/functions/javascript.js b/lib/init/features/functions/javascript.js index 9bb3378e..dad905ec 100644 --- a/lib/init/features/functions/javascript.js +++ b/lib/init/features/functions/javascript.js @@ -1,37 +1,48 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var fs = require('fs'); -var path = require('path'); +var _ = require("lodash"); +var fs = require("fs"); +var path = require("path"); -var npmDependencies = require('./npm-dependencies'); -var prompt = require('../../../prompt'); +var npmDependencies = require("./npm-dependencies"); +var prompt = require("../../../prompt"); -var TEMPLATE_ROOT = path.resolve(__dirname, '../../../../templates/init/functions/javascript/'); -var INDEX_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'index.js'), 'utf8'); -var PACKAGE_LINTING_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'package.lint.json'), 'utf8'); -var PACKAGE_NO_LINTING_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'package.nolint.json'), 'utf8'); -var ESLINT_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'eslint.json'), 'utf8'); +var TEMPLATE_ROOT = path.resolve(__dirname, "../../../../templates/init/functions/javascript/"); +var INDEX_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "index.js"), "utf8"); +var PACKAGE_LINTING_TEMPLATE = fs.readFileSync( + path.join(TEMPLATE_ROOT, "package.lint.json"), + "utf8" +); +var PACKAGE_NO_LINTING_TEMPLATE = fs.readFileSync( + path.join(TEMPLATE_ROOT, "package.nolint.json"), + "utf8" +); +var ESLINT_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "eslint.json"), "utf8"); module.exports = function(setup, config) { return prompt(setup.functions, [ { - name: 'lint', - type: 'confirm', - message: 'Do you want to use ESLint to catch probable bugs and enforce style?', - default: true - } - ]).then(function() { - if (setup.functions.lint) { - _.set(setup, 'config.functions.predeploy', ['npm --prefix "$RESOURCE_DIR" run lint']); - return config.askWriteProjectFile('functions/package.json', PACKAGE_LINTING_TEMPLATE).then(function() { - config.askWriteProjectFile('functions/.eslintrc.json', ESLINT_TEMPLATE); - }); - } - return config.askWriteProjectFile('functions/package.json', PACKAGE_NO_LINTING_TEMPLATE); - }).then(function() { - return config.askWriteProjectFile('functions/index.js', INDEX_TEMPLATE); - }).then(function() { - return npmDependencies.askInstallDependencies(setup, config); - }); + name: "lint", + type: "confirm", + message: "Do you want to use ESLint to catch probable bugs and enforce style?", + default: true, + }, + ]) + .then(function() { + if (setup.functions.lint) { + _.set(setup, "config.functions.predeploy", ['npm --prefix "$RESOURCE_DIR" run lint']); + return config + .askWriteProjectFile("functions/package.json", PACKAGE_LINTING_TEMPLATE) + .then(function() { + config.askWriteProjectFile("functions/.eslintrc.json", ESLINT_TEMPLATE); + }); + } + return config.askWriteProjectFile("functions/package.json", PACKAGE_NO_LINTING_TEMPLATE); + }) + .then(function() { + return config.askWriteProjectFile("functions/index.js", INDEX_TEMPLATE); + }) + .then(function() { + return npmDependencies.askInstallDependencies(setup, config); + }); }; diff --git a/lib/init/features/functions/npm-dependencies.js b/lib/init/features/functions/npm-dependencies.js index 13f093a2..6def9029 100644 --- a/lib/init/features/functions/npm-dependencies.js +++ b/lib/init/features/functions/npm-dependencies.js @@ -1,37 +1,37 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var spawn = require('cross-spawn'); +var RSVP = require("rsvp"); +var spawn = require("cross-spawn"); -var logger = require('../../../logger'); -var prompt = require('../../../prompt'); +var logger = require("../../../logger"); +var prompt = require("../../../prompt"); exports.askInstallDependencies = function(setup, config) { return prompt(setup.functions, [ { - name: 'npm', - type: 'confirm', - message: 'Do you want to install dependencies with npm now?', - default: true - } + 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' + var installer = spawn("npm", ["install"], { + cwd: config.projectDir + "/functions", + stdio: "inherit", }); - installer.on('error', function(err) { + installer.on("error", function(err) { logger.debug(err.stack); }); - installer.on('close', function(code) { + installer.on("close", function(code) { if (code === 0) { return resolve(); } logger.info(); - logger.error('NPM install failed, continuing with Firebase initialization...'); + logger.error("NPM install failed, continuing with Firebase initialization..."); return resolve(); }); }); diff --git a/lib/init/features/functions/typescript.js b/lib/init/features/functions/typescript.js index bc4f7ae7..12e782bd 100644 --- a/lib/init/features/functions/typescript.js +++ b/lib/init/features/functions/typescript.js @@ -1,41 +1,56 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var fs = require('fs'); -var path = require('path'); +var _ = require("lodash"); +var fs = require("fs"); +var path = require("path"); -var npmDependencies = require('./npm-dependencies'); -var prompt = require('../../../prompt'); +var npmDependencies = require("./npm-dependencies"); +var prompt = require("../../../prompt"); -var TEMPLATE_ROOT = path.resolve(__dirname, '../../../../templates/init/functions/typescript/'); -var PACKAGE_LINTING_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'package.lint.json'), 'utf8'); -var PACKAGE_NO_LINTING_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'package.nolint.json'), 'utf8'); -var TSLINT_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'tslint.json'), 'utf8'); -var TSCONFIG_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'tsconfig.json'), 'utf8'); -var INDEX_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, 'index.ts'), 'utf8'); +var TEMPLATE_ROOT = path.resolve(__dirname, "../../../../templates/init/functions/typescript/"); +var PACKAGE_LINTING_TEMPLATE = fs.readFileSync( + path.join(TEMPLATE_ROOT, "package.lint.json"), + "utf8" +); +var PACKAGE_NO_LINTING_TEMPLATE = fs.readFileSync( + path.join(TEMPLATE_ROOT, "package.nolint.json"), + "utf8" +); +var TSLINT_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "tslint.json"), "utf8"); +var TSCONFIG_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "tsconfig.json"), "utf8"); +var INDEX_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "index.ts"), "utf8"); module.exports = function(setup, config) { return prompt(setup.functions, [ { - name: 'lint', - type: 'confirm', - message: 'Do you want to use TSLint to catch probable bugs and enforce style?', - default: true - } - ]).then(function() { - if (setup.functions.lint) { - _.set(setup, 'config.functions.predeploy', ['npm --prefix "$RESOURCE_DIR" run lint', 'npm --prefix "$RESOURCE_DIR" run build']); - return config.askWriteProjectFile('functions/package.json', PACKAGE_LINTING_TEMPLATE).then(function() { - config.askWriteProjectFile('functions/tslint.json', TSLINT_TEMPLATE); - }); - } - _.set(setup, 'config.functions.predeploy', 'npm --prefix "$RESOURCE_DIR" run build'); - return config.askWriteProjectFile('functions/package.json', PACKAGE_NO_LINTING_TEMPLATE); - }).then(function() { - return config.askWriteProjectFile('functions/tsconfig.json', TSCONFIG_TEMPLATE); - }).then(function() { - return config.askWriteProjectFile('functions/src/index.ts', INDEX_TEMPLATE); - }).then(function() { - return npmDependencies.askInstallDependencies(setup, config); - }); + name: "lint", + type: "confirm", + message: "Do you want to use TSLint to catch probable bugs and enforce style?", + default: true, + }, + ]) + .then(function() { + if (setup.functions.lint) { + _.set(setup, "config.functions.predeploy", [ + 'npm --prefix "$RESOURCE_DIR" run lint', + 'npm --prefix "$RESOURCE_DIR" run build', + ]); + return config + .askWriteProjectFile("functions/package.json", PACKAGE_LINTING_TEMPLATE) + .then(function() { + config.askWriteProjectFile("functions/tslint.json", TSLINT_TEMPLATE); + }); + } + _.set(setup, "config.functions.predeploy", 'npm --prefix "$RESOURCE_DIR" run build'); + return config.askWriteProjectFile("functions/package.json", PACKAGE_NO_LINTING_TEMPLATE); + }) + .then(function() { + return config.askWriteProjectFile("functions/tsconfig.json", TSCONFIG_TEMPLATE); + }) + .then(function() { + return config.askWriteProjectFile("functions/src/index.ts", INDEX_TEMPLATE); + }) + .then(function() { + return npmDependencies.askInstallDependencies(setup, config); + }); }; diff --git a/lib/init/features/hosting.js b/lib/init/features/hosting.js index c596ba9e..4356e285 100644 --- a/lib/init/features/hosting.js +++ b/lib/init/features/hosting.js @@ -1,64 +1,78 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var fs = require('fs'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var fs = require("fs"); +var RSVP = require("rsvp"); -var api = require('../../api'); -var logger = require('../../logger'); -var prompt = require('../../prompt'); +var api = require("../../api"); +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'); -var DEFAULT_IGNORES = [ - 'firebase.json', - '**/.*', - '**/node_modules/**' -]; +var INDEX_TEMPLATE = fs.readFileSync( + __dirname + "/../../../templates/init/hosting/index.html", + "utf8" +); +var MISSING_TEMPLATE = fs.readFileSync( + __dirname + "/../../../templates/init/hosting/404.html", + "utf8" +); +var DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"]; module.exports = function(setup, config) { setup.hosting = {}; 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 be 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( + "Your " + + chalk.bold("public") + + " directory is the folder (relative to your project directory) that" + ); + logger.info( + "will contain Hosting assets to be 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: "public", + type: "input", + default: "public", + message: "What do you want to use as your public directory?", }, { - name: 'spa', - type: 'confirm', + name: "spa", + type: "confirm", default: false, - message: 'Configure as a single-page app (rewrite all urls to /index.html)?' - } + message: "Configure as a single-page app (rewrite all urls to /index.html)?", + }, ]).then(function() { - setup.config.hosting = {public: setup.hosting.public, ignore: DEFAULT_IGNORES}; + setup.config.hosting = { + public: setup.hosting.public, + ignore: DEFAULT_IGNORES, + }; var next; if (setup.hosting.spa) { - setup.config.hosting.rewrites = [ - {source: '**', destination: '/index.html'} - ]; + 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); + next = config.askWriteProjectFile(setup.hosting.public + "/404.html", MISSING_TEMPLATE); } - return next.then(function() { - return api.request('GET', '/firebasejs/releases.json', { - origin: 'https://www.gstatic.com', - json: true + return next + .then(function() { + return api.request("GET", "/firebasejs/releases.json", { + origin: "https://www.gstatic.com", + json: true, + }); + }) + .then(function(response) { + return config.askWriteProjectFile( + setup.hosting.public + "/index.html", + INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version) + ); }); - }).then(function(response) { - return config.askWriteProjectFile(setup.hosting.public + '/index.html', INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version)); - }); }); }; diff --git a/lib/init/features/index.js b/lib/init/features/index.js index b455970e..07f237e8 100644 --- a/lib/init/features/index.js +++ b/lib/init/features/index.js @@ -1,11 +1,11 @@ -'use strict'; +"use strict"; module.exports = { - database: require('./database'), - firestore: require('./firestore'), - functions: require('./functions'), - hosting: require('./hosting'), - storage: require('./storage'), + database: require("./database"), + firestore: require("./firestore"), + functions: require("./functions"), + hosting: require("./hosting"), + storage: require("./storage"), // always runs, sets up .firebaserc - project: require('./project') + project: require("./project"), }; diff --git a/lib/init/features/project.js b/lib/init/features/project.js index f00289aa..e6872e04 100644 --- a/lib/init/features/project.js +++ b/lib/init/features/project.js @@ -1,64 +1,68 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); +var chalk = require("chalk"); -var _ = require('lodash'); -var api = require('../../api'); -var prompt = require('../../prompt'); -var logger = require('../../logger'); -var utils = require('../../utils'); +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]'; +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('First, let\'s associate this project directory with a Firebase project.'); - logger.info('You can create multiple project aliases by running ' + chalk.bold('firebase use --add') + ', '); - logger.info('but for now we\'ll just set up a default project.'); + logger.info("First, let's associate this project directory with a Firebase project."); + logger.info( + "You can create multiple project aliases by running " + chalk.bold("firebase use --add") + ", " + ); + logger.info("but for now we'll just set up a default project."); logger.info(); return api.getProjects().then(function(projects) { var choices = _.map(projects, function(info, projectId) { return { name: projectId, - label: info.name + ' (' + projectId + ')' + label: info.name + " (" + projectId + ")", }; }); - var nameOptions = [NO_PROJECT].concat(_.map(choices, 'label')).concat([NEW_PROJECT]); + var nameOptions = [NO_PROJECT].concat(_.map(choices, "label")).concat([NEW_PROJECT]); - if (_.has(setup.rcfile, 'projects.default')) { - utils.logBullet('.firebaserc already has a default project, skipping'); + 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: 'Select a default Firebase project for this directory:', - validate: function(answer) { - if (!_.includes(nameOptions, answer)) { - return 'Must specify a Firebase to which you have access.'; + return prompt + .once({ + type: "list", + name: "id", + message: "Select a default Firebase project for this directory:", + 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) { + setup.createProject = true; + return; + } else if (projectId === NO_PROJECT) { + return; } - return true; - }, - choices: nameOptions - }).then(function(projectId) { - if (projectId === NEW_PROJECT) { - setup.createProject = true; - return; - } else if (projectId === NO_PROJECT) { - return; - } - projectId = prompt.listLabelToValue(projectId, choices); - var instance = projects[projectId].instances.database[0]; + projectId = prompt.listLabelToValue(projectId, choices); + var instance = projects[projectId].instances.database[0]; - // write "default" alias and activate it immediately - _.set(setup.rcfile, 'projects.default', projectId); - setup.instance = instance; - utils.makeActiveProject(config.projectDir, projectId); - }); + // write "default" alias and activate it immediately + _.set(setup.rcfile, "projects.default", projectId); + setup.instance = instance; + utils.makeActiveProject(config.projectDir, projectId); + }); }); }; diff --git a/lib/init/features/storage.js b/lib/init/features/storage.js index e1a7c4c9..be540c32 100644 --- a/lib/init/features/storage.js +++ b/lib/init/features/storage.js @@ -1,29 +1,32 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var fs = require('fs'); +var chalk = require("chalk"); +var fs = require("fs"); -var prompt = require('../../prompt'); -var logger = require('../../logger'); +var prompt = require("../../prompt"); +var logger = require("../../logger"); -var RULES_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/storage/storage.rules', 'utf8'); +var RULES_TEMPLATE = fs.readFileSync( + __dirname + "/../../../templates/init/storage/storage.rules", + "utf8" +); module.exports = function(setup, config) { setup.config.storage = {}; logger.info(); - logger.info('Firebase Storage Security Rules allow you to define how and when to allow'); - logger.info('uploads and downloads. You can keep these rules in your project directory'); - logger.info('and publish them with ' + chalk.bold('firebase deploy') + '.'); + logger.info("Firebase Storage Security Rules allow you to define how and when to allow"); + logger.info("uploads and downloads. You can keep these rules in your project directory"); + logger.info("and 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 Storage Rules?', - default: 'storage.rules' - } + type: "input", + name: "rules", + message: "What file should be used for 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 index 95df6760..2a4306dc 100644 --- a/lib/init/index.js +++ b/lib/init/index.js @@ -1,16 +1,16 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var logger = require('../logger'); -var features = require('./features'); +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')); + 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); }); diff --git a/lib/kits/deploy.js b/lib/kits/deploy.js index 377ab2ee..5c5b94e0 100644 --- a/lib/kits/deploy.js +++ b/lib/kits/deploy.js @@ -1,31 +1,31 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var FirebaseError = require('../error'); -var functionsConfig = require('../functionsConfig'); -var gcp = require('../gcp'); -var getProjectId = require('../getProjectId'); -var pollKits = require('./pollKits'); -var utils = require('../utils'); +var FirebaseError = require("../error"); +var functionsConfig = require("../functionsConfig"); +var gcp = require("../gcp"); +var getProjectId = require("../getProjectId"); +var pollKits = require("./pollKits"); +var utils = require("../utils"); var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION; function _getFunctionTrigger(cloudfunction, firebaseconfig, config) { if (cloudfunction.httpsTrigger) { - return _.pick(cloudfunction, 'httpsTrigger'); + return _.pick(cloudfunction, "httpsTrigger"); } else if (cloudfunction.eventTrigger) { var trigger = cloudfunction.eventTrigger; - var resource = 'projects/_/buckets/' + firebaseconfig.storageBucket; + var resource = "projects/_/buckets/" + firebaseconfig.storageBucket; if (config.OBJECT_PREFIX) { - resource = resource + '/' + config.OBJECT_PREFIX; + resource = resource + "/" + config.OBJECT_PREFIX; } trigger.resource = resource; - return {eventTrigger: trigger}; + return { eventTrigger: trigger }; } - return new FirebaseError('Could not parse function trigger, unknown trigger type.'); + return new FirebaseError("Could not parse function trigger, unknown trigger type."); } function _deployKitFunctions(functions, options, config, sourceUploadUrl) { @@ -36,38 +36,42 @@ function _deployKitFunctions(functions, options, config, sourceUploadUrl) { } return functionsConfig.getFirebaseConfig(options).then(function(firebaseconfig) { - // TODO: Do we deal with nested functions? How would we deal with nested functions - return RSVP.all(_.map(functions, function(cloudfunction) { - var functionTrigger = _getFunctionTrigger(cloudfunction, firebaseconfig, config); - return gcp.cloudfunctions.create({ - entryPoint: cloudfunction.entryPoint, - functionName: config.kitname + '-' + cloudfunction.name, - labels: { - 'goog-kit-source': config.kitsource, - 'goog-kit-name': config.kitname - }, - projectId: projectId, - region: DEFAULT_REGION, - sourceUploadUrl: sourceUploadUrl, - trigger: functionTrigger - }); - })); + // TODO: Do we deal with nested functions? How would we deal with nested functions + return RSVP.all( + _.map(functions, function(cloudfunction) { + var functionTrigger = _getFunctionTrigger(cloudfunction, firebaseconfig, config); + return gcp.cloudfunctions.create({ + entryPoint: cloudfunction.entryPoint, + functionName: config.kitname + "-" + cloudfunction.name, + labels: { + "goog-kit-source": config.kitsource, + "goog-kit-name": config.kitname, + }, + projectId: projectId, + region: DEFAULT_REGION, + sourceUploadUrl: sourceUploadUrl, + trigger: functionTrigger, + }); + }) + ); }); } module.exports = function(functions, options, config, sourceUploadUrl) { - return _deployKitFunctions(functions, options, config, sourceUploadUrl) - .then(function(operations) { - var printSuccess = function() { - return utils.logSuccess(chalk.green.bold('kits: ') + 'Your kit has successfully been deployed'); - }; + return _deployKitFunctions(functions, options, config, sourceUploadUrl).then(function( + operations + ) { + var printSuccess = function() { + return utils.logSuccess( + chalk.green.bold("kits: ") + "Your kit has successfully been deployed" + ); + }; - var printFail = function(reason) { - utils.logWarning('Your kit could not be deployed.'); - utils.logWarning(reason); - return new FirebaseError('Your kit could not be deployed.'); - }; - return pollKits(operations, printSuccess, printFail); - }); + var printFail = function(reason) { + utils.logWarning("Your kit could not be deployed."); + utils.logWarning(reason); + return new FirebaseError("Your kit could not be deployed."); + }; + return pollKits(operations, printSuccess, printFail); + }); }; - diff --git a/lib/kits/index.js b/lib/kits/index.js index 001938d3..bf5f8d05 100644 --- a/lib/kits/index.js +++ b/lib/kits/index.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; module.exports = { - prepareKitsConfig: require('./prepareKitsConfig'), - prepareKitsUpload: require('./prepareKitsUpload'), - deploy: require('./deploy') + prepareKitsConfig: require("./prepareKitsConfig"), + prepareKitsUpload: require("./prepareKitsUpload"), + deploy: require("./deploy"), }; diff --git a/lib/kits/pollKits.js b/lib/kits/pollKits.js index a47ee6c7..cda2c60c 100644 --- a/lib/kits/pollKits.js +++ b/lib/kits/pollKits.js @@ -2,13 +2,13 @@ * Wrapper around pollOperations.js specifically for polling functions deployed through kits */ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); -var gcp = require('../gcp'); -var pollOperations = require('../pollOperations'); +var gcp = require("../gcp"); +var pollOperations = require("../pollOperations"); function _pollKitFunctions(operations) { var pollFunction = gcp.cloudfunctions.check; @@ -16,13 +16,13 @@ function _pollKitFunctions(operations) { // Code from functions/release.js var retryCondition = function(result) { - // The error codes from a Google.LongRunning operation follow google.rpc.Code format. + // The error codes from a Google.LongRunning operation follow google.rpc.Code format. var retryableCodes = [ - 1, // cancelled by client - 4, // deadline exceeded + 1, // cancelled by client + 4, // deadline exceeded 10, // aborted (typically due to concurrency issue) - 14 // unavailable + 14, // unavailable ]; if (_.includes(retryableCodes, result.error.code)) { @@ -32,20 +32,32 @@ function _pollKitFunctions(operations) { }; var success = function(op) { - return RSVP.resolve(_.last(op.func.split('/')).match(/[^-]*/)[0]); + return RSVP.resolve(_.last(op.func.split("/")).match(/[^-]*/)[0]); }; var fail = function(op) { - return RSVP.reject({'kit': _.last(op.func.split('/')).match(/[^-]*/)[0], 'reason': op.error.message}); + return RSVP.reject({ + kit: _.last(op.func.split("/")).match(/[^-]*/)[0], + reason: op.error.message, + }); }; - return pollOperations.pollAndRetry(operations, pollFunction, interval, success, fail, retryCondition); + return pollOperations.pollAndRetry( + operations, + pollFunction, + interval, + success, + fail, + retryCondition + ); } module.exports = function(operations, printSuccess, printFail) { - return _pollKitFunctions(operations).then(function(successes) { - return printSuccess(successes); - }).catch(function(reason) { - // since poll operations uses RSVP.all, will catch immediately on first failure - return printFail(reason); - }); + return _pollKitFunctions(operations) + .then(function(successes) { + return printSuccess(successes); + }) + .catch(function(reason) { + // since poll operations uses RSVP.all, will catch immediately on first failure + return printFail(reason); + }); }; diff --git a/lib/kits/prepareKitsConfig.js b/lib/kits/prepareKitsConfig.js index e556e095..74fb4c95 100644 --- a/lib/kits/prepareKitsConfig.js +++ b/lib/kits/prepareKitsConfig.js @@ -1,83 +1,95 @@ -'use strict'; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +"use strict"; +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var FirebaseError = require('../error'); -var prompt = require('../prompt'); -var utils = require('../utils'); +var FirebaseError = require("../error"); +var prompt = require("../prompt"); +var utils = require("../utils"); function _promptForKitConfig(kitName, kitConfig) { var prompts = []; // TODO: name does not allow for dashes prompts.push({ - name: 'kitname', - type: 'input', + name: "kitname", + type: "input", default: kitName, - message: 'What would you like to name this kit?' + message: "What would you like to name this kit?", }); for (var i = 0; i < kitConfig.length; i++) { prompts.push({ name: kitConfig[i].name, - type: 'input', + type: "input", default: kitConfig[i].default, - message: kitConfig[i].label + message: kitConfig[i].label, }); } - return prompt({kitsource: kitName}, prompts).then(function(answers) { - // prompting again if needed - var promptAgains = _.chain(kitConfig) - .filter(function(question) { - var value = answers[question.name]; - return !('default' in question) && !value; - }).map(function(needsAnswer) { - utils.logWarning(chalk.yellow.bold('kits: ') + chalk.bold(needsAnswer.name) + ' requires a value.'); - return { - name: needsAnswer.name, - type: 'input', - message: needsAnswer.label - }; - }).value(); - return prompt(answers, promptAgains); - }).then(function(configList) { - // checking that all values that need to be set are set and of the correct type - return RSVP.allSettled(_.map(kitConfig, function(question) { + return prompt({ kitsource: kitName }, prompts) + .then(function(answers) { + // prompting again if needed + var promptAgains = _.chain(kitConfig) + .filter(function(question) { + var value = answers[question.name]; + return !("default" in question) && !value; + }) + .map(function(needsAnswer) { + utils.logWarning( + chalk.yellow.bold("kits: ") + chalk.bold(needsAnswer.name) + " requires a value." + ); + return { + name: needsAnswer.name, + type: "input", + message: needsAnswer.label, + }; + }) + .value(); + return prompt(answers, promptAgains); + }) + .then(function(configList) { + // checking that all values that need to be set are set and of the correct type + return RSVP.allSettled( + _.map(kitConfig, function(question) { + return new RSVP.Promise(function(resolve, reject) { + var configValue = configList[question.name]; + // if value still isn't set + if (!("default" in question) && !configValue) { + reject(new FirebaseError("The following value needs to be set: " + question.name)); + } + switch (question.type) { + case "number": + configList[question.name] = _.toNumber(configValue); + break; + case "boolean": + // why is there no toBoolean in lodash + configList[question.name] = configValue === "true"; + break; + default: + // keep as a string + configList[question.name] = _.toString(configValue); + } + resolve(configList); + }); + }) + ); + }) + .then(function(allPrompts) { return new RSVP.Promise(function(resolve, reject) { - var configValue = configList[question.name]; - // if value still isn't set - if (!('default' in question) && !configValue) { - reject(new FirebaseError('The following value needs to be set: ' + question.name)); + var failed = _.chain(allPrompts) + .filter({ state: "rejected" }) + .map("reason") + .value(); + var config = _.find(allPrompts, function(succeeded) { + return succeeded.state === "fulfilled"; + }); + if (failed.length > 0) { + return reject(new FirebaseError("The following values need to be set.\n" + failed)); } - switch (question.type) { - case 'number': - configList[question.name] = _.toNumber(configValue); - break; - case 'boolean': - // why is there no toBoolean in lodash - configList[question.name] = configValue === 'true'; - break; - default: - // keep as a string - configList[question.name] = _.toString(configValue); - } - resolve(configList); + return resolve(config.value); }); - })); - }).then(function(allPrompts) { - return new RSVP.Promise(function(resolve, reject) { - var failed = _.chain(allPrompts).filter({'state': 'rejected'}).map('reason').value(); - var config = _.find(allPrompts, function(succeeded) { - return succeeded.state === 'fulfilled'; - }); - if (failed.length > 0) { - return reject(new FirebaseError('The following values need to be set.\n' + failed)); - } - return resolve(config.value); }); - }); } module.exports = { - prompt: _promptForKitConfig + prompt: _promptForKitConfig, }; diff --git a/lib/kits/prepareKitsUpload.js b/lib/kits/prepareKitsUpload.js index 23031e97..8ba584e2 100644 --- a/lib/kits/prepareKitsUpload.js +++ b/lib/kits/prepareKitsUpload.js @@ -1,142 +1,186 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var archiver = require('archiver'); -var chalk = require('chalk'); -var filesize = require('filesize'); -var fs = require('fs'); -var tar = require('tar'); -var path = require('path'); -var request = require('request'); -var tmp = require('tmp'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var archiver = require("archiver"); +var chalk = require("chalk"); +var filesize = require("filesize"); +var fs = require("fs"); +var tar = require("tar"); +var path = require("path"); +var request = require("request"); +var tmp = require("tmp"); +var RSVP = require("rsvp"); -var fsAsync = require('../fsAsync'); -var api = require('../api'); -var FirebaseError = require('../error'); -var gcp = require('../gcp'); -var utils = require('../utils'); +var fsAsync = require("../fsAsync"); +var api = require("../api"); +var FirebaseError = require("../error"); +var gcp = require("../gcp"); +var utils = require("../utils"); var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION; -var CONFIG_DEST_FILE = '.runtimeconfig.json'; +var CONFIG_DEST_FILE = ".runtimeconfig.json"; var _pipeAsync = function(from, to) { return new RSVP.Promise(function(resolve, reject) { - to.on('finish', resolve); - to.on('error', reject); + to.on("finish", resolve); + to.on("error", reject); from.pipe(to); }); }; function _retrieveFile(githubConfig) { - var endpoint = '/repos/' + githubConfig.owner + '/' + githubConfig.repo + '/contents/' + githubConfig.manifestPath; - return api.request('GET', endpoint, { - auth: false, - origin: 'https://api.github.com', - headers: { - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': githubConfig.repo + '-kitsIntaller' - } - }).then(function(result) { - if (result.status !== 200) { - return RSVP.reject(new FirebaseError(githubConfig.path + ' could not be retrieved for kit at ' + githubConfig.repo)); - } - var buf = Buffer.from(result.body.content, 'base64'); - return RSVP.resolve(buf); - }).catch(function(error) { - return RSVP.reject(error); - }); + var endpoint = + "/repos/" + + githubConfig.owner + + "/" + + githubConfig.repo + + "/contents/" + + githubConfig.manifestPath; + return api + .request("GET", endpoint, { + auth: false, + origin: "https://api.github.com", + headers: { + Accept: "application/vnd.github.v3+json", + "User-Agent": githubConfig.repo + "-kitsIntaller", + }, + }) + .then(function(result) { + if (result.status !== 200) { + return RSVP.reject( + new FirebaseError( + githubConfig.path + " could not be retrieved for kit at " + githubConfig.repo + ) + ); + } + var buf = Buffer.from(result.body.content, "base64"); + return RSVP.resolve(buf); + }) + .catch(function(error) { + return RSVP.reject(error); + }); } function _downloadSource(githubConfig) { var owner = githubConfig.owner; var repo = githubConfig.repo; var ref = githubConfig.ref; - var tmpDir = tmp.dirSync({prefix: 'kits-source-'}).name; + var tmpDir = tmp.dirSync({ prefix: "kits-source-" }).name; - var endpoint = '/repos/' + owner + '/' + repo + '/tarball/' + ref; + var endpoint = "/repos/" + owner + "/" + repo + "/tarball/" + ref; var download = request({ - url: 'https://api.github.com' + endpoint, + url: "https://api.github.com" + endpoint, headers: { - 'Accept': 'application/vnd.github.v3.sha', - 'User-Agent': repo + '-kitsIntaller' - } + Accept: "application/vnd.github.v3.sha", + "User-Agent": repo + "-kitsIntaller", + }, }); var untar = tar.x({ cwd: tmpDir, // GitHub embeds everything in a folder named as the git // -- - strip: 1 - }); - return _pipeAsync(download, untar).then(function() { - utils.logSuccess(chalk.green.bold('kits: ') + 'Fetched kits source code.'); - return tmpDir; - }, function(err) { - throw new FirebaseError('There was an error with fetching the kit', { - original: err, - exit: 2 - }); + strip: 1, }); + return _pipeAsync(download, untar).then( + function() { + utils.logSuccess(chalk.green.bold("kits: ") + "Fetched kits source code."); + return tmpDir; + }, + function(err) { + throw new FirebaseError("There was an error with fetching the kit", { + original: err, + exit: 2, + }); + } + ); } /** * Scaffolding code. Adapted from prepareFunctionsUpload.js -**/ + **/ var _packageSource = function(sourceDir, githubContext, configValues) { - var tmpFile = tmp.fileSync({prefix: 'kits-upload-', postfix: '.zip'}).name; + var tmpFile = tmp.fileSync({ prefix: "kits-upload-", postfix: ".zip" }).name; var fileStream = fs.createWriteStream(tmpFile, { - flags: 'w', - defaultEncoding: 'binary' + flags: "w", + defaultEncoding: "binary", }); - var archive = archiver('zip'); + var archive = archiver("zip"); var archiveDone = _pipeAsync(archive, fileStream); - return fsAsync.readdirRecursive({ - path: sourceDir, - ignore: [githubContext.manfiestPath /* kit.json */, CONFIG_DEST_FILE /* .runtimeconfig.json */, 'node_modules'] - }).then(function(files) { - _.forEach(files, function(file) { - archive.file(file.name, {name: path.relative(sourceDir, file.name), mode: file.mode}); - }); - archive.append(JSON.stringify(configValues, null, 2), {name: CONFIG_DEST_FILE, mode: 420 /* 0o644 */}); - archive.finalize(); - return archiveDone; - }).then(function() { - utils.logBullet(chalk.cyan.bold('kits:') + ' packaged kit source (' + filesize(archive.pointer()) + ') for uploading'); - return { - file: tmpFile, - stream: fs.createReadStream(tmpFile), - size: archive.pointer() - }; - }, function(err) { - throw new FirebaseError('Could not read source directory. Remove links and shortcuts and try again.', { - original: err, - exit: 1 - }); - }); + return fsAsync + .readdirRecursive({ + path: sourceDir, + ignore: [ + githubContext.manfiestPath /* kit.json */, + CONFIG_DEST_FILE /* .runtimeconfig.json */, + "node_modules", + ], + }) + .then(function(files) { + _.forEach(files, function(file) { + archive.file(file.name, { + name: path.relative(sourceDir, file.name), + mode: file.mode, + }); + }); + archive.append(JSON.stringify(configValues, null, 2), { + name: CONFIG_DEST_FILE, + mode: 420 /* 0o644 */, + }); + archive.finalize(); + return archiveDone; + }) + .then( + function() { + utils.logBullet( + chalk.cyan.bold("kits:") + + " packaged kit source (" + + filesize(archive.pointer()) + + ") for uploading" + ); + return { + file: tmpFile, + stream: fs.createReadStream(tmpFile), + size: archive.pointer(), + }; + }, + function(err) { + throw new FirebaseError( + "Could not read source directory. Remove links and shortcuts and try again.", + { + original: err, + exit: 1, + } + ); + } + ); }; function _uploadSourceCode(projectId, source) { var fullUrl; - return gcp.cloudfunctions.generateUploadUrl(projectId, DEFAULT_REGION).then(function(uploadUrl) { - fullUrl = uploadUrl; - uploadUrl = _.replace(uploadUrl, 'https://storage.googleapis.com', ''); - return gcp.storage.upload(source, uploadUrl); - }).then(function() { - return fullUrl; - }); + return gcp.cloudfunctions + .generateUploadUrl(projectId, DEFAULT_REGION) + .then(function(uploadUrl) { + fullUrl = uploadUrl; + uploadUrl = _.replace(uploadUrl, "https://storage.googleapis.com", ""); + return gcp.storage.upload(source, uploadUrl); + }) + .then(function() { + return fullUrl; + }); } function _upload(projectId, githubContext, options) { - return _downloadSource(githubContext).then(function(sourceDir) { - return _packageSource(sourceDir, githubContext, options); - }).then(function(source) { - return _uploadSourceCode(projectId, source); - }); + return _downloadSource(githubContext) + .then(function(sourceDir) { + return _packageSource(sourceDir, githubContext, options); + }) + .then(function(source) { + return _uploadSourceCode(projectId, source); + }); } - module.exports = { retrieveFile: _retrieveFile, - upload: _upload + upload: _upload, }; diff --git a/lib/listFiles.js b/lib/listFiles.js index 705f8783..3675a308 100644 --- a/lib/listFiles.js +++ b/lib/listFiles.js @@ -1,14 +1,14 @@ -'use strict'; +"use strict"; -var glob = require('glob').sync; +var glob = require("glob").sync; module.exports = function(cwd, ignore) { - return glob('**/*', { + return glob("**/*", { cwd: cwd, - ignore: ['**/firebase-debug.log'].concat(ignore || []), + ignore: ["**/firebase-debug.log"].concat(ignore || []), nodir: true, nosort: true, follow: true, - dot: true + dot: true, }); }; diff --git a/lib/loadCJSON.js b/lib/loadCJSON.js index 37b01fdf..f145f9e6 100644 --- a/lib/loadCJSON.js +++ b/lib/loadCJSON.js @@ -1,15 +1,15 @@ -'use strict'; +"use strict"; -var FirebaseError = require('./error'); -var cjson = require('cjson'); +var FirebaseError = require("./error"); +var cjson = require("cjson"); module.exports = function(path) { try { return cjson.load(path); } catch (e) { - if (e.code === 'ENOENT') { - throw new FirebaseError('File ' + path + ' does not exist', {exit: 1}); + if (e.code === "ENOENT") { + throw new FirebaseError("File " + path + " does not exist", { exit: 1 }); } - throw new FirebaseError('Parse Error in ' + path + ':\n\n' + e.message); + throw new FirebaseError("Parse Error in " + path + ":\n\n" + e.message); } }; diff --git a/lib/localFunction.js b/lib/localFunction.js index a1cb02ea..83d1b15d 100644 --- a/lib/localFunction.js +++ b/lib/localFunction.js @@ -1,10 +1,10 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var request = require('request'); +var _ = require("lodash"); +var request = require("request"); -var encodeFirestoreValue = require('./firestore/encodeFirestoreValue'); -var utils = require('./utils'); +var encodeFirestoreValue = require("./firestore/encodeFirestoreValue"); +var utils = require("./utils"); var LocalFunction = function(trigger, urls, controller) { this.name = trigger.name; @@ -15,7 +15,7 @@ var LocalFunction = function(trigger, urls, controller) { this.call = request.defaults({ callback: this._requestCallBack, baseUrl: _.get(urls, this.name), - uri: '' + uri: "", }); } else { this.call = this._call.bind(this); @@ -23,15 +23,15 @@ var LocalFunction = function(trigger, urls, controller) { }; LocalFunction.prototype._isDatabaseFunc = function(eventTrigger) { - return utils.getFunctionsEventProvider(eventTrigger.eventType) === 'Database'; + return utils.getFunctionsEventProvider(eventTrigger.eventType) === "Database"; }; LocalFunction.prototype._isFirestoreFunc = function(eventTrigger) { - return utils.getFunctionsEventProvider(eventTrigger.eventType) === 'Firestore'; + return utils.getFunctionsEventProvider(eventTrigger.eventType) === "Firestore"; }; LocalFunction.prototype._substituteParams = function(resource, params) { - var wildcardRegex = new RegExp('{[^/{}]*}', 'g'); + var wildcardRegex = new RegExp("{[^/{}]*}", "g"); return resource.replace(wildcardRegex, function(wildcard) { var wildcardNoBraces = wildcard.slice(1, -1); // .slice removes '{' and '}' from wildcard var sub = _.get(params, wildcardNoBraces); @@ -40,39 +40,40 @@ LocalFunction.prototype._substituteParams = function(resource, params) { }; LocalFunction.prototype._constructAuth = function(auth, authType) { - if (_.get(auth, 'admin') || _.get(auth, 'variable')) { + if (_.get(auth, "admin") || _.get(auth, "variable")) { return auth; // User is providing the wire auth format already. } - if (typeof authType !== 'undefined') { + if (typeof authType !== "undefined") { switch (authType) { - case 'USER': - return { - variable: { - uid: _.get(auth, 'uid', ''), - token: _.get(auth, 'token', {}) + case "USER": + return { + variable: { + uid: _.get(auth, "uid", ""), + token: _.get(auth, "token", {}), + }, + }; + case "ADMIN": + if (_.get(auth, "uid") || _.get(auth, "token")) { + throw new Error("authType and auth are incompatible."); } - }; - case 'ADMIN': - if (_.get(auth, 'uid') || _.get(auth, 'token')) { - throw new Error('authType and auth are incompatible.'); - } - return { admin: true }; - case 'UNAUTHENTICATED': - if (_.get(auth, 'uid') || _.get(auth, 'token')) { - throw new Error('authType and auth are incompatible.'); - } - return { admin: false }; - default: - throw new Error('Unrecognized authType, valid values are: ' + - 'ADMIN, USER, and UNAUTHENTICATED'); + return { admin: true }; + case "UNAUTHENTICATED": + if (_.get(auth, "uid") || _.get(auth, "token")) { + throw new Error("authType and auth are incompatible."); + } + return { admin: false }; + default: + throw new Error( + "Unrecognized authType, valid values are: " + "ADMIN, USER, and UNAUTHENTICATED" + ); } } if (auth) { return { variable: { uid: auth.uid, - token: auth.token || {} - } + token: auth.token || {}, + }, }; } // Default to admin @@ -81,10 +82,10 @@ LocalFunction.prototype._constructAuth = function(auth, authType) { LocalFunction.prototype._requestCallBack = function(err, response, body) { if (err) { - return console.warn('\nERROR SENDING REQUEST: ' + err); + return console.warn("\nERROR SENDING REQUEST: " + err); } - var status = response ? response.statusCode + ', ' : ''; - return console.log('\nRESPONSE RECEIVED FROM FUNCTION: ' + status + body); + var status = response ? response.statusCode + ", " : ""; + return console.log("\nRESPONSE RECEIVED FROM FUNCTION: " + status + body); }; LocalFunction.prototype._call = function(data, opts) { @@ -93,11 +94,11 @@ LocalFunction.prototype._call = function(data, opts) { var dataPayload; var makeFirestoreValue = function(input) { - var currentTime = (new Date()).toISOString(); + var currentTime = new Date().toISOString(); return { fields: encodeFirestoreValue(input), createTime: currentTime, - updateTime: currentTime + updateTime: currentTime, }; }; @@ -105,49 +106,51 @@ LocalFunction.prototype._call = function(data, opts) { this.controller.call(this.name, data || {}); } else if (this.eventTrigger) { if (this._isDatabaseFunc(this.eventTrigger)) { - operationType = _.last(this.eventTrigger.eventType.split('.')); + operationType = _.last(this.eventTrigger.eventType.split(".")); switch (operationType) { - case 'create': - dataPayload = { - data: null, - delta: data - }; - break; - case 'delete': - dataPayload = { - data: data, - delta: null - }; - break; - default: // 'update' or 'write' - dataPayload = { - data: data.before, - delta: data.after - }; + case "create": + dataPayload = { + data: null, + delta: data, + }; + break; + case "delete": + dataPayload = { + data: data, + delta: null, + }; + break; + default: + // 'update' or 'write' + dataPayload = { + data: data.before, + delta: data.after, + }; } opts.resource = this._substituteParams(this.eventTrigger.resource, opts.params); opts.auth = this._constructAuth(opts.auth, opts.authType); this.controller.call(this.name, dataPayload, opts); } else if (this._isFirestoreFunc(this.eventTrigger)) { - operationType = _.last(this.eventTrigger.eventType.split('.')); + operationType = _.last(this.eventTrigger.eventType.split(".")); switch (operationType) { - case 'create': - dataPayload = { - value: makeFirestoreValue(data), - oldValue: {} - }; - break; - case 'delete': - dataPayload = { - value: {}, - oldValue: makeFirestoreValue(data) - }; - break; - default: // 'update' or 'write' - dataPayload = { - value: makeFirestoreValue(data.after), - oldValue: makeFirestoreValue(data.before) - }; + case "create": + dataPayload = { + value: makeFirestoreValue(data), + oldValue: {}, + }; + break; + case "delete": + dataPayload = { + value: {}, + oldValue: makeFirestoreValue(data), + }; + break; + default: + // 'update' or 'write' + dataPayload = { + value: makeFirestoreValue(data.after), + oldValue: makeFirestoreValue(data.before), + }; } opts.resource = this._substituteParams(this.eventTrigger.resource, opts.params); this.controller.call(this.name, dataPayload, opts); @@ -155,7 +158,7 @@ LocalFunction.prototype._call = function(data, opts) { this.controller.call(this.name, data || {}, opts); } } - return 'Successfully invoked function.'; + return "Successfully invoked function."; }; module.exports = LocalFunction; diff --git a/lib/logError.js b/lib/logError.js index 54c12dcd..33af9876 100644 --- a/lib/logError.js +++ b/lib/logError.js @@ -1,23 +1,29 @@ -'use strict'; +"use strict"; -var logger = require('./logger'); -var chalk = require('chalk'); +var logger = require("./logger"); +var chalk = require("chalk"); /* istanbul ignore next */ module.exports = function(error) { if (error.children && error.children.length) { - logger.error(chalk.bold.red('Error:'), chalk.underline(error.message) + ':'); + logger.error(chalk.bold.red("Error:"), chalk.underline(error.message) + ":"); error.children.forEach(function(child) { - var out = '- '; - if (child.name) { out += chalk.bold(child.name) + ' '; } + var out = "- "; + if (child.name) { + out += chalk.bold(child.name) + " "; + } out += child.message; logger.error(out); }); } else { - if (error.original) { logger.debug(error.original.stack); } + if (error.original) { + logger.debug(error.original.stack); + } logger.error(); - logger.error(chalk.bold.red('Error:'), error.message); + logger.error(chalk.bold.red("Error:"), error.message); + } + if (error.context) { + logger.debug("Error Context:", JSON.stringify(error.context, undefined, 2)); } - if (error.context) { logger.debug('Error Context:', JSON.stringify(error.context, undefined, 2)); } }; diff --git a/lib/logger.js b/lib/logger.js index baf3e827..4c53beec 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; -var winston = require('winston'); +var winston = require("winston"); function expandErrors(logger) { var oldLogFunc = logger.log; @@ -18,7 +18,7 @@ var logger = expandErrors(new winston.Logger()); var debug = logger.debug; logger.debug = function() { - arguments[0] = '[' + new Date().toISOString() + '] ' + (arguments[0] || ''); + arguments[0] = "[" + new Date().toISOString() + "] " + (arguments[0] || ""); debug.apply(null, arguments); }; diff --git a/lib/parseBoltRules.js b/lib/parseBoltRules.js index b7c7a7b6..82423cd5 100644 --- a/lib/parseBoltRules.js +++ b/lib/parseBoltRules.js @@ -1,25 +1,30 @@ -'use strict'; +"use strict"; -var fs = require('fs'); -var spawn = require('cross-spawn'); -var FirebaseError = require('./error'); -var chalk = require('chalk'); +var fs = require("fs"); +var spawn = require("cross-spawn"); +var FirebaseError = require("./error"); +var chalk = require("chalk"); module.exports = function(filename) { var ruleSrc = fs.readFileSync(filename); - var result = spawn.sync('firebase-bolt', { + var result = spawn.sync("firebase-bolt", { input: ruleSrc, timeout: 10000, - encoding: 'utf-8' + encoding: "utf-8", }); - if (result.error && result.error.code === 'ENOENT') { - throw new FirebaseError('Bolt not installed, run ' + chalk.bold('npm install -g firebase-bolt'), {exit: 1}); + if (result.error && result.error.code === "ENOENT") { + throw new FirebaseError( + "Bolt not installed, run " + chalk.bold("npm install -g firebase-bolt"), + { exit: 1 } + ); } else if (result.error) { - throw new FirebaseError('Unexpected error parsing Bolt rules file', {exit: 2}); + throw new FirebaseError("Unexpected error parsing Bolt rules file", { + exit: 2, + }); } else if (result.status > 0) { - throw new FirebaseError(result.stderr, {exit: 1}); + throw new FirebaseError(result.stderr, { exit: 1 }); } return result.stdout; diff --git a/lib/parseTriggers.js b/lib/parseTriggers.js index 9a2cf04a..52947c3f 100644 --- a/lib/parseTriggers.js +++ b/lib/parseTriggers.js @@ -1,12 +1,12 @@ -'use strict'; +"use strict"; -var FirebaseError = require('./error'); -var fork = require('child_process').fork; -var path = require('path'); -var RSVP = require('rsvp'); -var _ = require('lodash'); +var FirebaseError = require("./error"); +var fork = require("child_process").fork; +var path = require("path"); +var RSVP = require("rsvp"); +var _ = require("lodash"); -var TRIGGER_PARSER = path.resolve(__dirname, './triggerParser.js'); +var TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js"); module.exports = function(projectId, sourceDir, configValues, firebaseConfig) { return new RSVP.Promise(function(resolve, reject) { @@ -19,27 +19,33 @@ module.exports = function(projectId, sourceDir, configValues, firebaseConfig) { env.FIREBASE_CONFIG = JSON.stringify(configValues.firebase); } } - if (firebaseConfig) { // This value will be populated during functions emulation + if (firebaseConfig) { + // This value will be populated during functions emulation // Make legacy firbase-functions SDK work env.FIREBASE_PROJECT = firebaseConfig; // In case user has `admin.initalizeApp()` at the top of the file and it was executed before firebase-functions v1 // is loaded, which would normally set FIREBASE_CONFIG. env.FIREBASE_CONFIG = firebaseConfig; } - var parser = fork(TRIGGER_PARSER, [sourceDir], {silent: true, env: env}); + var parser = fork(TRIGGER_PARSER, [sourceDir], { silent: true, env: env }); - parser.on('message', function(message) { + parser.on("message", function(message) { if (message.triggers) { resolve(message.triggers); } else if (message.error) { - reject(new FirebaseError(message.error, {exit: 1})); + reject(new FirebaseError(message.error, { exit: 1 })); } }); - parser.on('exit', function(code) { + parser.on("exit", function(code) { if (code !== 0) { - reject(new FirebaseError('There was an unknown problem while trying to parse function triggers. ' + - 'Please ensure you are using Node.js v6 or greater.', {exit: 2})); + reject( + new FirebaseError( + "There was an unknown problem while trying to parse function triggers. " + + "Please ensure you are using Node.js v6 or greater.", + { exit: 2 } + ) + ); } }); }); diff --git a/lib/pollOperations.js b/lib/pollOperations.js index d2a7704c..0a56e958 100644 --- a/lib/pollOperations.js +++ b/lib/pollOperations.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var RSVP = require("rsvp"); var MAX_POLL_RETRIES = 2; @@ -9,49 +9,63 @@ function pollOperation(op, pollFunction, interval, pollFailCount) { pollFailCount = pollFailCount || 0; return new RSVP.Promise(function(resolve, reject) { function poll() { - pollFunction(op).then(function(result) { - if (result.done) { - resolve(result); - } else { - setTimeout(poll, interval); - } - }).catch(function() { - if (pollFailCount < MAX_POLL_RETRIES) { - pollFailCount += 1; - setTimeout(poll, interval * 2); - } else { - reject('Failed to get status of operation.'); - } - }); + pollFunction(op) + .then(function(result) { + if (result.done) { + resolve(result); + } else { + setTimeout(poll, interval); + } + }) + .catch(function() { + if (pollFailCount < MAX_POLL_RETRIES) { + pollFailCount += 1; + setTimeout(poll, interval * 2); + } else { + reject("Failed to get status of operation."); + } + }); } poll(); }); } -function pollAndRetryOperations(operations, pollFunction, interval, printSuccess, printFail, retryCondition) { +function pollAndRetryOperations( + operations, + pollFunction, + interval, + printSuccess, + printFail, + retryCondition +) { // This function assumes that a Google.LongRunning operation is being polled - return RSVP.all(_.map(operations, function(op) { - return pollOperation(op, pollFunction, interval).then(function(result) { - if (!result.error) { - return printSuccess(op); - } - if (!retryCondition(result)) { - return printFail(op); - } - - return op.retryFunction().then(function(retriedOperation) { - return pollOperation(retriedOperation, pollFunction, interval); - }).then(function(retriedResult) { - if (retriedResult.error) { + return RSVP.all( + _.map(operations, function(op) { + return pollOperation(op, pollFunction, interval).then(function(result) { + if (!result.error) { + return printSuccess(op); + } + if (!retryCondition(result)) { return printFail(op); } - return printSuccess(op); + + return op + .retryFunction() + .then(function(retriedOperation) { + return pollOperation(retriedOperation, pollFunction, interval); + }) + .then(function(retriedResult) { + if (retriedResult.error) { + return printFail(op); + } + return printSuccess(op); + }); }); - }); - })); + }) + ); } module.exports = { pollAndRetry: pollAndRetryOperations, - poll: pollOperation + poll: pollOperation, }; diff --git a/lib/prepareFirebaseRules.js b/lib/prepareFirebaseRules.js index 5b682156..e3423656 100644 --- a/lib/prepareFirebaseRules.js +++ b/lib/prepareFirebaseRules.js @@ -1,48 +1,64 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var fs = require('fs'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var fs = require("fs"); +var RSVP = require("rsvp"); -var api = require('./api'); -var utils = require('./utils'); +var api = require("./api"); +var utils = require("./utils"); var prepareFirebaseRules = function(component, options, payload) { - var rulesFileName = component + '.rules'; + var rulesFileName = component + ".rules"; var rulesPath = options.config.get(rulesFileName); if (rulesPath) { rulesPath = options.config.path(rulesPath); - var src = fs.readFileSync(rulesPath, 'utf8'); - utils.logBullet(chalk.bold.cyan(component + ':') + ' checking rules for compilation errors...'); - return api.request('POST', '/v1/projects/' + encodeURIComponent(options.project) + ':test', { - origin: api.rulesOrigin, - data: { - source: { - files: [{ - content: src, - name: rulesFileName - }] + var src = fs.readFileSync(rulesPath, "utf8"); + utils.logBullet(chalk.bold.cyan(component + ":") + " checking rules for compilation errors..."); + return api + .request("POST", "/v1/projects/" + encodeURIComponent(options.project) + ":test", { + origin: api.rulesOrigin, + data: { + source: { + files: [ + { + content: src, + name: rulesFileName, + }, + ], + }, + }, + auth: true, + }) + .then(function(response) { + if (response.body && response.body.issues && response.body.issues.length > 0) { + var add = response.body.issues.length === 1 ? "" : "s"; + var message = + "Compilation error" + + add + + " in " + + chalk.bold(options.config.get(rulesFileName)) + + ":\n"; + response.body.issues.forEach(function(issue) { + message += + "\n[" + + issue.severity.substring(0, 1) + + "] " + + issue.sourcePosition.line + + ":" + + issue.sourcePosition.column + + " - " + + issue.description; + }); + + return utils.reject(message, { exit: 1 }); } - }, - auth: true - }).then(function(response) { - if (response.body && response.body.issues && response.body.issues.length > 0) { - var add = response.body.issues.length === 1 ? '' : 's'; - var message = 'Compilation error' + add + ' in ' + chalk.bold(options.config.get(rulesFileName)) + ':\n'; - response.body.issues.forEach(function(issue) { - message += '\n[' + issue.severity.substring(0, 1) + '] ' - + issue.sourcePosition.line + ':' + issue.sourcePosition.column + ' - ' + issue.description; - }); - return utils.reject(message, {exit: 1}); - } - - utils.logSuccess(chalk.bold.green(component + ':') + ' rules file compiled successfully'); - payload[component] = {rules: [ - {name: options.config.get(rulesFileName), content: src} - ]}; - return RSVP.resolve(); - }); + utils.logSuccess(chalk.bold.green(component + ":") + " rules file compiled successfully"); + payload[component] = { + rules: [{ name: options.config.get(rulesFileName), content: src }], + }; + return RSVP.resolve(); + }); } return RSVP.resolve(); diff --git a/lib/prepareFunctionsUpload.js b/lib/prepareFunctionsUpload.js index aa8ff106..6b3cbc58 100644 --- a/lib/prepareFunctionsUpload.js +++ b/lib/prepareFunctionsUpload.js @@ -1,103 +1,128 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var archiver = require('archiver'); -var chalk = require('chalk'); -var filesize = require('filesize'); -var fs = require('fs'); -var path = require('path'); -var RSVP = require('rsvp'); -var tmp = require('tmp'); +var _ = require("lodash"); +var archiver = require("archiver"); +var chalk = require("chalk"); +var filesize = require("filesize"); +var fs = require("fs"); +var path = require("path"); +var RSVP = require("rsvp"); +var tmp = require("tmp"); -var FirebaseError = require('./error'); -var functionsConfig = require('./functionsConfig'); -var getProjectId = require('./getProjectId'); -var logger = require('./logger'); -var utils = require('./utils'); -var parseTriggers = require('./parseTriggers'); -var fsAsync = require('./fsAsync'); +var FirebaseError = require("./error"); +var functionsConfig = require("./functionsConfig"); +var getProjectId = require("./getProjectId"); +var logger = require("./logger"); +var utils = require("./utils"); +var parseTriggers = require("./parseTriggers"); +var fsAsync = require("./fsAsync"); -var CONFIG_DEST_FILE = '.runtimeconfig.json'; +var CONFIG_DEST_FILE = ".runtimeconfig.json"; var _getFunctionsConfig = function(context) { var next = RSVP.resolve({}); if (context.runtimeConfigEnabled) { next = functionsConfig.materializeAll(context.firebaseConfig.projectId).catch(function(err) { logger.debug(err); - var errorCode = _.get(err, 'context.response.statusCode'); + var errorCode = _.get(err, "context.response.statusCode"); if (errorCode === 500 || errorCode === 503) { - throw new FirebaseError('Cloud Runtime Config is currently experiencing issues, ' + - 'which is preventing your functions from being deployed. ' + - 'Please wait a few minutes and then try to deploy your functions again.' + - '\nRun `firebase deploy --except functions` if you want to continue deploying the rest of your project.'); + throw new FirebaseError( + "Cloud Runtime Config is currently experiencing issues, " + + "which is preventing your functions from being deployed. " + + "Please wait a few minutes and then try to deploy your functions again." + + "\nRun `firebase deploy --except functions` if you want to continue deploying the rest of your project." + ); } }); } return next.then(function(config) { - var firebaseConfig = _.get(context, 'firebaseConfig'); - _.set(config, 'firebase', firebaseConfig); + var firebaseConfig = _.get(context, "firebaseConfig"); + _.set(config, "firebase", firebaseConfig); return config; }); }; var _pipeAsync = function(from, to) { return new RSVP.Promise(function(resolve, reject) { - to.on('finish', resolve); - to.on('error', reject); + to.on("finish", resolve); + to.on("error", reject); from.pipe(to); }); }; var _packageSource = function(options, sourceDir, configValues) { - var tmpFile = tmp.fileSync({prefix: 'firebase-functions-', postfix: '.zip'}).name; + var tmpFile = tmp.fileSync({ prefix: "firebase-functions-", postfix: ".zip" }).name; var fileStream = fs.createWriteStream(tmpFile, { - flags: 'w', - defaultEncoding: 'binary' + flags: "w", + defaultEncoding: "binary", }); - var archive = archiver('zip'); + var archive = archiver("zip"); var archiveDone = _pipeAsync(archive, fileStream); // We must ignore firebase-debug.log or weird things happen if // you're in the public dir when you deploy. // We ignore any CONFIG_DEST_FILE that already exists, and write another one // with current config values into the archive in the "end" handler for reader - var ignore = options.config.get('functions.ignore', ['node_modules']); - ignore.push('firebase-debug.log', CONFIG_DEST_FILE /* .runtimeconfig.json */); - return fsAsync.readdirRecursive({ path: sourceDir, ignore: ignore }) - .then(function(files) { - _.forEach(files, function(file) { - archive.file(file.name, {name: path.relative(sourceDir, file.name), mode: file.mode}); - }); - archive.append(JSON.stringify(configValues, null, 2), {name: CONFIG_DEST_FILE, mode: 420 /* 0o644 */}); - archive.finalize(); - return archiveDone; - }).then(function() { - utils.logBullet(chalk.cyan.bold('functions:') + ' packaged ' + chalk.bold(options.config.get('functions.source')) + ' (' + filesize(archive.pointer()) + ') for uploading'); - return { - file: tmpFile, - stream: fs.createReadStream(tmpFile), - size: archive.pointer() - }; - }, function(err) { - throw new FirebaseError('Could not read source directory. Remove links and shortcuts and try again.', { - original: err, - exit: 1 - }); - }); + var ignore = options.config.get("functions.ignore", ["node_modules"]); + ignore.push("firebase-debug.log", CONFIG_DEST_FILE /* .runtimeconfig.json */); + return fsAsync + .readdirRecursive({ path: sourceDir, ignore: ignore }) + .then(function(files) { + _.forEach(files, function(file) { + archive.file(file.name, { + name: path.relative(sourceDir, file.name), + mode: file.mode, + }); + }); + archive.append(JSON.stringify(configValues, null, 2), { + name: CONFIG_DEST_FILE, + mode: 420 /* 0o644 */, + }); + archive.finalize(); + return archiveDone; + }) + .then( + function() { + utils.logBullet( + chalk.cyan.bold("functions:") + + " packaged " + + chalk.bold(options.config.get("functions.source")) + + " (" + + filesize(archive.pointer()) + + ") for uploading" + ); + return { + file: tmpFile, + stream: fs.createReadStream(tmpFile), + size: archive.pointer(), + }; + }, + function(err) { + throw new FirebaseError( + "Could not read source directory. Remove links and shortcuts and try again.", + { + original: err, + exit: 1, + } + ); + } + ); }; module.exports = function(context, options) { var configValues; - var sourceDir = options.config.path(options.config.get('functions.source')); - return _getFunctionsConfig(context).then(function(result) { - configValues = result; - return parseTriggers(getProjectId(options), sourceDir, configValues); - }).then(function(triggers) { - options.config.set('functions.triggers', triggers); - if (options.config.get('functions.triggers').length === 0) { - return RSVP.resolve(null); - } - return _packageSource(options, sourceDir, configValues); - }); + var sourceDir = options.config.path(options.config.get("functions.source")); + return _getFunctionsConfig(context) + .then(function(result) { + configValues = result; + return parseTriggers(getProjectId(options), sourceDir, configValues); + }) + .then(function(triggers) { + options.config.set("functions.triggers", triggers); + if (options.config.get("functions.triggers").length === 0) { + return RSVP.resolve(null); + } + return _packageSource(options, sourceDir, configValues); + }); }; diff --git a/lib/prepareUpload.js b/lib/prepareUpload.js index ce681d75..94d58e2c 100644 --- a/lib/prepareUpload.js +++ b/lib/prepareUpload.js @@ -1,44 +1,55 @@ -'use strict'; +"use strict"; -var fs = require('fs'); -var path = require('path'); -var RSVP = require('rsvp'); -var tar = require('tar'); -var tmp = require('tmp'); +var fs = require("fs"); +var path = require("path"); +var RSVP = require("rsvp"); +var tar = require("tar"); +var tmp = require("tmp"); -var FirebaseError = require('./error'); -var fsutils = require('./fsutils'); -var listFiles = require('./listFiles'); +var FirebaseError = require("./error"); +var fsutils = require("./fsutils"); +var listFiles = require("./listFiles"); module.exports = function(options) { - var hostingConfig = options.config.get('hosting'); + var hostingConfig = options.config.get("hosting"); var publicDir = options.config.path(hostingConfig.public); - var indexPath = path.join(publicDir, 'index.html'); + var indexPath = path.join(publicDir, "index.html"); - var tmpFile = tmp.fileSync({prefix: 'firebase-upload-', postfix: '.tar.gz'}); + var tmpFile = tmp.fileSync({ + prefix: "firebase-upload-", + postfix: ".tar.gz", + }); var manifest = listFiles(publicDir, hostingConfig.ignore); - return tar.c({ - gzip: true, - file: tmpFile.name, - cwd: publicDir, - prefix: 'public', - follow: true, - noDirRecurse: true, - portable: true - }, manifest.slice(0)).then(function() { - var stats = fs.statSync(tmpFile.name); - return { - file: tmpFile.name, - stream: fs.createReadStream(tmpFile.name), - manifest: manifest, - foundIndex: fsutils.fileExistsSync(indexPath), - size: stats.size - }; - }).catch(function(err) { - return RSVP.reject(new FirebaseError('There was an issue preparing Hosting files for upload.', { - original: err, - exit: 2 - })); - }); + return tar + .c( + { + gzip: true, + file: tmpFile.name, + cwd: publicDir, + prefix: "public", + follow: true, + noDirRecurse: true, + portable: true, + }, + manifest.slice(0) + ) + .then(function() { + var stats = fs.statSync(tmpFile.name); + return { + file: tmpFile.name, + stream: fs.createReadStream(tmpFile.name), + manifest: manifest, + foundIndex: fsutils.fileExistsSync(indexPath), + size: stats.size, + }; + }) + .catch(function(err) { + return RSVP.reject( + new FirebaseError("There was an issue preparing Hosting files for upload.", { + original: err, + exit: 2, + }) + ); + }); }; diff --git a/lib/previews.js b/lib/previews.js index 2f212187..6d3795ea 100644 --- a/lib/previews.js +++ b/lib/previews.js @@ -1,15 +1,18 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var configstore = require('./configstore'); +var _ = require("lodash"); +var configstore = require("./configstore"); -var previews = _.assign({ - // insert previews here... - 'kits': false -}, configstore.get('previews')); +var previews = _.assign( + { + // insert previews here... + kits: false, + }, + configstore.get("previews") +); if (process.env.FIREBASE_CLI_PREVIEWS) { - process.env.FIREBASE_CLI_PREVIEWS.split(',').forEach(function(feature) { + process.env.FIREBASE_CLI_PREVIEWS.split(",").forEach(function(feature) { if (_.has(previews, feature)) { _.set(previews, feature, true); } diff --git a/lib/profileReport.js b/lib/profileReport.js index 47d4932f..6f669141 100644 --- a/lib/profileReport.js +++ b/lib/profileReport.js @@ -1,27 +1,29 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var Table = require('cli-table'); -var Set = require('es6-set'); -var fs = require('fs'); -var _ = require('lodash'); -var readline = require('readline'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var Table = require("cli-table"); +var Set = require("es6-set"); +var fs = require("fs"); +var _ = require("lodash"); +var readline = require("readline"); +var RSVP = require("rsvp"); -var FirebaseError = require('./error'); -var logger = require('./logger'); -var utils = require('./utils'); +var FirebaseError = require("./error"); +var logger = require("./logger"); +var utils = require("./utils"); var DATA_LINE_REGEX = /^data: /; -var BANDWIDTH_NOTE = 'NOTE: The numbers reported here are only estimates of the data' + -' payloads from read operations. They are NOT a valid measure of your bandwidth bill.'; +var BANDWIDTH_NOTE = + "NOTE: The numbers reported here are only estimates of the data" + + " payloads from read operations. They are NOT a valid measure of your bandwidth bill."; -var SPEED_NOTE = 'NOTE: Speeds are reported at millisecond resolution and' + -' are not the latencies that clients will see.'; +var SPEED_NOTE = + "NOTE: Speeds are reported at millisecond resolution and" + + " are not the latencies that clients will see."; var COLLAPSE_THRESHOLD = 25; -var COLLAPSE_WILDCARD = ['$wildcard']; +var COLLAPSE_WILDCARD = ["$wildcard"]; var ProfileReport = function(tmpFile, outStream, options) { this.tempFile = tmpFile; @@ -36,7 +38,7 @@ var ProfileReport = function(tmpFile, outStream, options) { unindexed: {}, startTime: 0, endTime: 0, - opCount: 0 + opCount: 0, }; }; @@ -57,41 +59,41 @@ ProfileReport.extractJSON = function(line, input) { ProfileReport.pathString = function(path) { if (path) { - return '/' + path.join('/'); + return "/" + path.join("/"); } return null; }; ProfileReport.formatNumber = function(num) { - var parts = num.toFixed(2).split('.'); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + var parts = num.toFixed(2).split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); if (+parts[1] === 0) { return parts[0]; } - return parts.join('.'); + return parts.join("."); }; ProfileReport.formatBytes = function(bytes) { var threshold = 1000; if (Math.round(bytes) < threshold) { - return bytes + ' B'; + return bytes + " B"; } - var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + var units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; var u = -1; var formattedBytes = bytes; do { formattedBytes /= threshold; u++; } while (Math.abs(formattedBytes) >= threshold && u < units.length - 1); - return ProfileReport.formatNumber(formattedBytes) + ' ' + units[u]; + return ProfileReport.formatNumber(formattedBytes) + " " + units[u]; }; ProfileReport.extractReadableIndex = function(query) { - var indexPath = _.get(query, 'index.path'); + var indexPath = _.get(query, "index.path"); if (indexPath) { return ProfileReport.pathString(indexPath); } - return '.value'; + return ".value"; }; ProfileReport.prototype.collectUnindexed = function(data, path) { @@ -109,7 +111,7 @@ ProfileReport.prototype.collectUnindexed = function(data, path) { if (!_.has(pathNode, index)) { pathNode[index] = { times: 0, - query: query + query: query, }; } var indexNode = pathNode[index]; @@ -121,7 +123,7 @@ ProfileReport.prototype.collectSpeed = function(data, path, opType) { opType[path] = { times: 0, millis: 0, - rejected: 0 + rejected: 0, }; } var node = opType[path]; @@ -137,7 +139,7 @@ ProfileReport.prototype.collectBandwidth = function(bytes, path, direction) { if (!_.has(direction, path)) { direction[path] = { times: 0, - bytes: 0 + bytes: 0, }; } var node = direction[path]; @@ -168,42 +170,42 @@ ProfileReport.prototype.processOperation = function(data) { var path = ProfileReport.pathString(data.path); this.state.opCount++; switch (data.name) { - case 'concurrent-connect': - break; - case 'concurrent-disconnect': - break; - case 'realtime-read': - this.collectRead(data, path, data.bytes); - break; - case 'realtime-write': - this.collectWrite(data, path, data.bytes); - break; - case 'realtime-transaction': - this.collectWrite(data, path, data.bytes); - break; - case 'realtime-update': - this.collectWrite(data, path, data.bytes); - break; - case 'listener-listen': - this.collectRead(data, path, data.bytes); - this.collectUnindexed(data, path); - break; - case 'listener-broadcast': - this.collectBroadcast(data, path, data.bytes); - break; - case 'listener-unlisten': - break; - case 'rest-read': - this.collectRead(data, path, data.bytes); - break; - case 'rest-write': - this.collectWrite(data, path, data.bytes); - break; - case 'rest-update': - this.collectWrite(data, path, data.bytes); - break; - default: - break; + case "concurrent-connect": + break; + case "concurrent-disconnect": + break; + case "realtime-read": + this.collectRead(data, path, data.bytes); + break; + case "realtime-write": + this.collectWrite(data, path, data.bytes); + break; + case "realtime-transaction": + this.collectWrite(data, path, data.bytes); + break; + case "realtime-update": + this.collectWrite(data, path, data.bytes); + break; + case "listener-listen": + this.collectRead(data, path, data.bytes); + this.collectUnindexed(data, path); + break; + case "listener-broadcast": + this.collectBroadcast(data, path, data.bytes); + break; + case "listener-unlisten": + break; + case "rest-read": + this.collectRead(data, path, data.bytes); + break; + case "rest-write": + this.collectWrite(data, path, data.bytes); + break; + case "rest-update": + this.collectWrite(data, path, data.bytes); + break; + default: + break; } }; @@ -221,8 +223,8 @@ ProfileReport.prototype.collapsePaths = function(pathedObject, combiner, pathInd pathIndex = 1; } var allSegments = _.keys(pathedObject).map(function(path) { - return path.split('/').filter(function(s) { - return s !== ''; + return path.split("/").filter(function(s) { + return s !== ""; }); }); var pathSegments = allSegments.filter(function(segments) { @@ -270,17 +272,17 @@ ProfileReport.prototype.collapsePaths = function(pathedObject, combiner, pathInd ProfileReport.prototype.renderUnindexedData = function() { var table = new Table({ - head: ['Path', 'Index', 'Count'], + head: ["Path", "Index", "Count"], style: { - head: this.options.isFile ? [] : ['yellow'], - border: this.options.isFile ? [] : ['grey'] - } + head: this.options.isFile ? [] : ["yellow"], + border: this.options.isFile ? [] : ["grey"], + }, }); var unindexed = this.collapsePaths(this.state.unindexed, function(u1, u2) { _.mergeWith(u1, u2, function(p1, p2) { return { times: p1.times + p2.times, - query: p1.query + query: p1.query, }; }); }); @@ -292,7 +294,7 @@ ProfileReport.prototype.renderUnindexedData = function() { var row = [ path, ProfileReport.extractReadableIndex(data.query), - ProfileReport.formatNumber(data.times) + ProfileReport.formatNumber(data.times), ]; table.push(row); }); @@ -302,30 +304,34 @@ ProfileReport.prototype.renderUnindexedData = function() { ProfileReport.prototype.renderBandwidth = function(pureData) { var table = new Table({ - head: ['Path', 'Total', 'Count', 'Average'], + head: ["Path", "Total", "Count", "Average"], style: { - head: this.options.isFile ? [] : ['yellow'], - border: this.options.isFile ? [] : ['grey'] - } + head: this.options.isFile ? [] : ["yellow"], + border: this.options.isFile ? [] : ["grey"], + }, }); var data = this.collapsePaths(pureData, function(b1, b2) { return { bytes: b1.bytes + b2.bytes, - times: b1.times + b2.times + times: b1.times + b2.times, }; }); var paths = _.keys(data); - paths = _.orderBy(paths, function(path) { - var bandwidth = data[path]; - return bandwidth.bytes; - }, ['desc']); + paths = _.orderBy( + paths, + function(path) { + var bandwidth = data[path]; + return bandwidth.bytes; + }, + ["desc"] + ); paths.forEach(function(path) { var bandwidth = data[path]; var row = [ path, ProfileReport.formatBytes(bandwidth.bytes), ProfileReport.formatNumber(bandwidth.times), - ProfileReport.formatBytes(bandwidth.bytes / bandwidth.times) + ProfileReport.formatBytes(bandwidth.bytes / bandwidth.times), ]; table.push(row); }); @@ -341,36 +347,36 @@ ProfileReport.prototype.renderIncomingBandwidth = function() { }; ProfileReport.prototype.renderOperationSpeed = function(pureData, hasSecurity) { - var head = ['Path', 'Count', 'Average']; + var head = ["Path", "Count", "Average"]; if (hasSecurity) { - head.push('Permission Denied'); + head.push("Permission Denied"); } var table = new Table({ head: head, style: { - head: this.options.isFile ? [] : ['yellow'], - border: this.options.isFile ? [] : ['grey'] - } + head: this.options.isFile ? [] : ["yellow"], + border: this.options.isFile ? [] : ["grey"], + }, }); var data = this.collapsePaths(pureData, function(s1, s2) { return { times: s1.times + s2.times, millis: s1.millis + s2.millis, - rejected: s1.rejected + s2.rejected + rejected: s1.rejected + s2.rejected, }; }); var paths = _.keys(data); - paths = _.orderBy(paths, function(path) { - var speed = data[path]; - return speed.millis / speed.times; - }, ['desc']); + paths = _.orderBy( + paths, + function(path) { + var speed = data[path]; + return speed.millis / speed.times; + }, + ["desc"] + ); paths.forEach(function(path) { var speed = data[path]; - var row = [ - path, - speed.times, - ProfileReport.formatNumber(speed.millis / speed.times) + ' ms' - ]; + var row = [path, speed.times, ProfileReport.formatNumber(speed.millis / speed.times) + " ms"]; if (hasSecurity) { row.push(ProfileReport.formatNumber(speed.rejected)); } @@ -398,24 +404,24 @@ ProfileReport.prototype.parse = function(onLine, onClose) { var isInput = this.options.isInput; return new RSVP.Promise(function(resolve, reject) { var rl = readline.createInterface({ - input: fs.createReadStream(tmpFile) + input: fs.createReadStream(tmpFile), }); var errored = false; - rl.on('line', function(line) { + rl.on("line", function(line) { var data = ProfileReport.extractJSON(line, isInput); if (!data) { return; } onLine(data); }); - rl.on('close', function() { + rl.on("close", function() { if (errored) { - reject(new FirebaseError('There was an error creating the report.')); + reject(new FirebaseError("There was an error creating the report.")); } else { var result = onClose(); if (isFile) { // Only resolve once the data is flushed. - outStream.on('finish', function() { + outStream.on("finish", function() { resolve(result); }); outStream.end(); @@ -424,10 +430,10 @@ ProfileReport.prototype.parse = function(onLine, onClose) { } } }); - rl.on('error', function() { + rl.on("error", function() { reject(); }); - outStream.on('error', function() { + outStream.on("error", function() { errored = true; rl.close(); }); @@ -443,16 +449,18 @@ ProfileReport.prototype.write = function(data) { }; ProfileReport.prototype.generate = function() { - if (this.options.format === 'TXT') { + if (this.options.format === "TXT") { return this.generateText(); - } else if (this.options.format === 'RAW') { + } else if (this.options.format === "RAW") { return this.generateRaw(); - } else if (this.options.format === 'JSON') { + } else if (this.options.format === "JSON") { return this.generateJson(); } - return utils.reject(new FirebaseError('Invalid report format expected "TXT", "JSON", or "RAW"', { - exit: 1 - })); + return utils.reject( + new FirebaseError('Invalid report format expected "TXT", "JSON", or "RAW"', { + exit: 1, + }) + ); }; ProfileReport.prototype.generateRaw = function() { @@ -463,7 +471,7 @@ ProfileReport.prototype.generateRaw = function() { ProfileReport.prototype.writeRaw = function(data) { // Just write the json to the output - this.write(JSON.stringify(data) + '\n'); + this.write(JSON.stringify(data) + "\n"); }; ProfileReport.prototype.generateText = function() { @@ -476,26 +484,26 @@ ProfileReport.prototype.outputText = function() { var write = this.write.bind(this); var writeTitle = function(title) { if (isFile) { - write(title + '\n'); + write(title + "\n"); } else { - write(chalk.bold.yellow(title) + '\n'); + write(chalk.bold.yellow(title) + "\n"); } }; var writeTable = function(title, table) { writeTitle(title); - write(table.toString() + '\n'); + write(table.toString() + "\n"); }; - writeTitle('Report operations collected over ' + totalTime + ' ms.'); - writeTitle('Speed Report\n'); - write(SPEED_NOTE + '\n\n'); - writeTable('Read Speed', this.renderReadSpeed()); - writeTable('Write Speed', this.renderWriteSpeed()); - writeTable('Broadcast Speed', this.renderBroadcastSpeed()); - writeTitle('Bandwidth Report\n'); - write(BANDWIDTH_NOTE + '\n\n'); - writeTable('Downloaded Bytes', this.renderOutgoingBandwidth()); - writeTable('Uploaded Bytes', this.renderIncomingBandwidth()); - writeTable('Unindexed Queries', this.renderUnindexedData()); + writeTitle("Report operations collected over " + totalTime + " ms."); + writeTitle("Speed Report\n"); + write(SPEED_NOTE + "\n\n"); + writeTable("Read Speed", this.renderReadSpeed()); + writeTable("Write Speed", this.renderWriteSpeed()); + writeTable("Broadcast Speed", this.renderBroadcastSpeed()); + writeTitle("Bandwidth Report\n"); + write(BANDWIDTH_NOTE + "\n\n"); + writeTable("Downloaded Bytes", this.renderOutgoingBandwidth()); + writeTable("Uploaded Bytes", this.renderIncomingBandwidth()); + writeTable("Unindexed Queries", this.renderUnindexedData()); }; ProfileReport.prototype.generateJson = function() { @@ -507,7 +515,7 @@ ProfileReport.prototype.outputJson = function() { var tableToJson = function(table, note) { var json = { legend: table.options.head, - data: [] + data: [], }; if (note) { json.note = note; @@ -524,7 +532,7 @@ ProfileReport.prototype.outputJson = function() { broadcastSpeed: tableToJson(this.renderBroadcastSpeed(), SPEED_NOTE), downloadedBytes: tableToJson(this.renderOutgoingBandwidth(), BANDWIDTH_NOTE), uploadedBytes: tableToJson(this.renderIncomingBandwidth(), BANDWIDTH_NOTE), - unindexedQueries: tableToJson(this.renderUnindexedData()) + unindexedQueries: tableToJson(this.renderUnindexedData()), }; this.write(JSON.stringify(json, null, 2)); if (this.options.isFile) { diff --git a/lib/profiler.js b/lib/profiler.js index 341c0cec..ad9bd076 100644 --- a/lib/profiler.js +++ b/lib/profiler.js @@ -1,31 +1,31 @@ -'use strict'; +"use strict"; -var fs = require('fs'); -var _ = require('lodash'); -var ora = require('ora'); -var readline = require('readline'); -var request = require('request'); -var RSVP = require('rsvp'); -var tmp = require('tmp'); +var fs = require("fs"); +var _ = require("lodash"); +var ora = require("ora"); +var readline = require("readline"); +var request = require("request"); +var RSVP = require("rsvp"); +var tmp = require("tmp"); -var api = require('./api'); -var utils = require('./utils'); -var ProfileReport = require('./profileReport'); -var FirebaseError = require('./error'); -var responseToError = require('./responseToError'); +var api = require("./api"); +var utils = require("./utils"); +var ProfileReport = require("./profileReport"); +var FirebaseError = require("./error"); +var responseToError = require("./responseToError"); module.exports = function(options) { - var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + '/.settings/profile.json?'; + var url = utils.addSubdomain(api.realtimeOrigin, options.instance) + "/.settings/profile.json?"; var rl = readline.createInterface({ - input: process.stdin + input: process.stdin, }); var reqOptions = { url: url, headers: { - 'Accept': 'text/event-stream' - } + Accept: "text/event-stream", + }, }; return api.addRequestHeaders(reqOptions).then(function(reqOptionsWithToken) { @@ -36,12 +36,12 @@ module.exports = function(options) { var outStream = fileOut ? fs.createWriteStream(options.output) : process.stdout; var counter = 0; var spinner = ora({ - text: '0 operations recorded. Press [enter] to stop', - color: 'yellow' + text: "0 operations recorded. Press [enter] to stop", + color: "yellow", }); - var outputFormat = options.raw ? 'RAW' : options.parent.json ? 'JSON' : 'TXT'; // eslint-disable-line no-nested-ternary + var outputFormat = options.raw ? "RAW" : options.parent.json ? "JSON" : "TXT"; // eslint-disable-line no-nested-ternary var erroring; - var errorResponse = ''; + var errorResponse = ""; var response; var generateReport = _.once(function() { @@ -56,9 +56,11 @@ module.exports = function(options) { // If it wasn't JSON, then it was a text response, technically it should always // a text response because of the Accept header we set, but you never know. // Examples of errors here is the popular "Permission Denied". - return reject(new FirebaseError(errorResponse, { - exit: 2 - })); + return reject( + new FirebaseError(errorResponse, { + exit: 2, + }) + ); } } else if (response) { response.destroy(); @@ -69,15 +71,18 @@ module.exports = function(options) { format: outputFormat, isFile: fileOut, isInput: !!options.input, - collapse: options.collapse + collapse: options.collapse, }; var report = new ProfileReport(dataFile, outStream, reportOptions); - report.generate().then(function(result) { - fs.unlinkSync(tmpFile); - resolve(result); - }, function(e) { - reject(e); - }); + report.generate().then( + function(result) { + fs.unlinkSync(tmpFile); + resolve(result); + }, + function(e) { + reject(e); + } + ); }); if (options.input) { @@ -85,41 +90,42 @@ module.exports = function(options) { return generateReport(); } - request.get(reqOptionsWithToken) - .on('response', function(res) { + request + .get(reqOptionsWithToken) + .on("response", function(res) { response = res; if (response.statusCode >= 400) { erroring = true; - } else if (!_.has(options, 'duration')) { + } else if (!_.has(options, "duration")) { spinner.start(); } }) - .on('data', function(chunk) { + .on("data", function(chunk) { if (erroring) { errorResponse += chunk.toString(); return; } tmpStream.write(chunk); - if (chunk.toString().indexOf('event: log') >= 0) { + if (chunk.toString().indexOf("event: log") >= 0) { counter++; - spinner.text = counter + ' operations recorded. Press [enter] to stop'; + spinner.text = counter + " operations recorded. Press [enter] to stop"; } }) - .on('end', function() { - spinner.text = counter + ' operations recorded.\n'; + .on("end", function() { + spinner.text = counter + " operations recorded.\n"; generateReport(); }) - .on('error', function() { - spinner.text = counter + ' operations recorded.\n'; + .on("error", function() { + spinner.text = counter + " operations recorded.\n"; erroring = true; generateReport(); }); - if (_.has(options, 'duration')) { + if (_.has(options, "duration")) { setTimeout(generateReport, options.duration * 1000); } else { // On newline, generate the report. - rl.question('', generateReport); + rl.question("", generateReport); } }); }); diff --git a/lib/prompt.js b/lib/prompt.js index e4463d31..12092418 100644 --- a/lib/prompt.js +++ b/lib/prompt.js @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; -var inquirer = require('inquirer'); -var _ = require('lodash'); -var FirebaseError = require('./error'); -var RSVP = require('rsvp'); +var inquirer = require("inquirer"); +var _ = require("lodash"); +var FirebaseError = require("./error"); +var RSVP = require("rsvp"); var prompt = function(options, questions) { return new RSVP.Promise(function(resolve, reject) { @@ -15,10 +15,17 @@ var prompt = function(options, questions) { } if (prompts.length && options.nonInteractive) { - return reject(new FirebaseError('Missing required options (' + _.uniq(_.map(prompts, 'name')).join(', ') + ') while running in non-interactive mode', { - children: prompts, - exit: 1 - })); + return reject( + new FirebaseError( + "Missing required options (" + + _.uniq(_.map(prompts, "name")).join(", ") + + ") while running in non-interactive mode", + { + children: prompts, + exit: 1, + } + ) + ); } return inquirer.prompt(prompts, function(answers) { @@ -34,13 +41,15 @@ var prompt = 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]; }); + 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}; + return { checked: choice.checked, name: choice.label }; }); }; diff --git a/lib/rc.js b/lib/rc.js index b8e8dbe5..b0928cb8 100644 --- a/lib/rc.js +++ b/lib/rc.js @@ -1,20 +1,20 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var cjson = require('cjson'); -var fs = require('fs'); -var path = require('path'); +var _ = require("lodash"); +var chalk = require("chalk"); +var cjson = require("cjson"); +var fs = require("fs"); +var path = require("path"); -var detectProjectRoot = require('./detectProjectRoot'); -var FirebaseError = require('./error'); -var fsutils = require('./fsutils'); -var utils = require('./utils'); +var detectProjectRoot = require("./detectProjectRoot"); +var FirebaseError = require("./error"); +var fsutils = require("./fsutils"); +var utils = require("./utils"); // "exclusive" target implies that a resource can only be assigned a single target name var TARGET_TYPES = { - storage: {resource: 'bucket', exclusive: true}, - database: {resource: 'instance', exclusive: true} + storage: { resource: "bucket", exclusive: true }, + database: { resource: "instance", exclusive: true }, }; var RC = function(rcpath, data) { @@ -36,12 +36,12 @@ RC.prototype = { }, addProjectAlias: function(alias, project) { - this.set(['projects', alias], project); + this.set(["projects", alias], project); return this.save(); }, removeProjectAlias: function(alias) { - this.unset(['projects', alias]); + this.unset(["projects", alias]); return this.save(); }, @@ -50,23 +50,25 @@ RC.prototype = { }, get projects() { - return this.get('projects', {}); + return this.get("projects", {}); }, targets: function(project, type) { - return this.get(['targets', project, type], {}); + return this.get(["targets", project, type], {}); }, target: function(project, type, name) { - return this.get(['targets', project, type, name], []); + return this.get(["targets", project, type, name], []); }, applyTarget: function(project, type, targetName, resources) { if (!TARGET_TYPES[type]) { throw new FirebaseError( - 'Unrecognized target type ' + chalk.bold(type) + - '. Must be one of ' + _.keys(TARGET_TYPES).join(', '), - {code: 1} + "Unrecognized target type " + + chalk.bold(type) + + ". Must be one of " + + _.keys(TARGET_TYPES).join(", "), + { code: 1 } ); } @@ -77,18 +79,20 @@ RC.prototype = { var changed = []; // remove resources from existing targets - resources.forEach(function(resource) { - var cur = this.findTarget(project, type, resource); - if (cur && cur !== targetName) { - this.unsetTargetResource(project, type, cur, resource); - changed.push({resource: resource, target: cur}); - } - }.bind(this)); + resources.forEach( + function(resource) { + var cur = this.findTarget(project, type, resource); + if (cur && cur !== targetName) { + this.unsetTargetResource(project, type, cur, resource); + changed.push({ resource: resource, target: cur }); + } + }.bind(this) + ); // apply resources to new target - var existing = this.get(['targets', project, type, targetName], []); + var existing = this.get(["targets", project, type, targetName], []); var list = _.uniq(existing.concat(resources)).sort(); - this.set(['targets', project, type, targetName], list); + this.set(["targets", project, type, targetName], list); this.save(); return changed; @@ -110,7 +114,7 @@ RC.prototype = { if (!exists) { return false; } - this.unset(['targets', project, type, name]); + this.unset(["targets", project, type, name]); this.save(); return true; }, @@ -119,7 +123,7 @@ RC.prototype = { * Finds a target name for the specified type and resource. */ findTarget: function(project, type, resource) { - var targets = this.get(['targets', project, type]); + var targets = this.get(["targets", project, type]); for (var targetName in targets) { if (_.includes(targets[targetName], resource)) { return targetName; @@ -133,7 +137,7 @@ RC.prototype = { * not persist the result. */ unsetTargetResource: function(project, type, name, resource) { - var targetPath = ['targets', project, type, name]; + var targetPath = ["targets", project, type, name]; var updatedResources = this.get(targetPath, []).filter(function(r) { return r !== resource; }); @@ -151,7 +155,18 @@ RC.prototype = { */ requireTarget: function(project, type, name) { if (!this.target(project, type, name).length) { - throw new FirebaseError('Deploy target ' + chalk.bold(name) + ' not configured for project ' + chalk.bold(project) + '. Configure with:\n\n firebase target:apply ' + type + ' ' + name + ' ', {exit: 1}); + throw new FirebaseError( + "Deploy target " + + chalk.bold(name) + + " not configured for project " + + chalk.bold(project) + + ". Configure with:\n\n firebase target:apply " + + type + + " " + + name + + " ", + { exit: 1 } + ); } }, @@ -160,11 +175,13 @@ RC.prototype = { */ save: function() { if (this.path) { - fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2), {encoding: 'utf8'}); + fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2), { + encoding: "utf8", + }); return true; } return false; - } + }, }; RC.loadFile = function(rcpath) { @@ -174,7 +191,7 @@ RC.loadFile = function(rcpath) { data = cjson.load(rcpath); } catch (e) { // malformed rc file is a warning, not an error - utils.logWarning('JSON error trying to load ' + chalk.bold(rcpath)); + utils.logWarning("JSON error trying to load " + chalk.bold(rcpath)); } } return new RC(rcpath, data); @@ -183,7 +200,7 @@ RC.loadFile = function(rcpath) { RC.load = function(cwd) { cwd = cwd || process.cwd(); var dir = detectProjectRoot(cwd); - var potential = path.resolve(dir || cwd, './.firebaserc'); + var potential = path.resolve(dir || cwd, "./.firebaserc"); return RC.loadFile(potential); }; diff --git a/lib/requireAccess.js b/lib/requireAccess.js index 5ba7bdb5..1d1cf84c 100644 --- a/lib/requireAccess.js +++ b/lib/requireAccess.js @@ -1,54 +1,69 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); -var api = require('./api'); -var getInstanceId = require('./getInstanceId'); -var getProjectId = require('./getProjectId'); -var FirebaseError = require('./error'); -var identifierToProjectId = require('./identifierToProjectId'); -var requireAuth = require('./requireAuth'); +var api = require("./api"); +var getInstanceId = require("./getInstanceId"); +var getProjectId = require("./getProjectId"); +var FirebaseError = require("./error"); +var identifierToProjectId = require("./identifierToProjectId"); +var requireAuth = require("./requireAuth"); module.exports = function(options, authScopes) { var projectId = getProjectId(options); options.project = projectId; - if (process.env.FIREBASE_BYPASS_ADMIN_CALLS_FOR_TESTING === 'true') { + if (process.env.FIREBASE_BYPASS_ADMIN_CALLS_FOR_TESTING === "true") { return requireAuth(options, authScopes); } - return requireAuth(options, authScopes).then(function() { - return getInstanceId(options); - }).then(function(instance) { - options.instance = instance; - return api.request('GET', '/v1/database/' + options.instance + '/tokens', {auth: true}); - }).then(function(res) { - options.metadataToken = res.body.metadata; - return; - }).catch(function(err) { - if (err && err.exit && _.get(err, 'context.body.error.code') !== 'PROJECT_NOT_FOUND') { - return RSVP.reject(err); - } - - return identifierToProjectId(projectId).then(function(realProjectId) { - if (realProjectId) { - var fixCommand = 'firebase use ' + realProjectId; - if (options.projectAlias) { - fixCommand += ' --alias ' + options.projectAlias; - } - - return RSVP.reject(new FirebaseError( - 'Tried to access unrecognized project ' + chalk.bold(projectId) + ', but found matching instance for project ' + chalk.bold(realProjectId) + '.\n\n' + - 'To use ' + chalk.bold(realProjectId) + ' instead, run:\n\n ' + - chalk.bold(fixCommand) - ), {exit: 1}); + return requireAuth(options, authScopes) + .then(function() { + return getInstanceId(options); + }) + .then(function(instance) { + options.instance = instance; + return api.request("GET", "/v1/database/" + options.instance + "/tokens", { auth: true }); + }) + .then(function(res) { + options.metadataToken = res.body.metadata; + return; + }) + .catch(function(err) { + if (err && err.exit && _.get(err, "context.body.error.code") !== "PROJECT_NOT_FOUND") { + return RSVP.reject(err); } - return RSVP.reject(new FirebaseError( - 'Unable to authorize access to project ' + chalk.bold(projectId), {exit: 1} - )); + return identifierToProjectId(projectId).then(function(realProjectId) { + if (realProjectId) { + var fixCommand = "firebase use " + realProjectId; + if (options.projectAlias) { + fixCommand += " --alias " + options.projectAlias; + } + + return RSVP.reject( + new FirebaseError( + "Tried to access unrecognized project " + + chalk.bold(projectId) + + ", but found matching instance for project " + + chalk.bold(realProjectId) + + ".\n\n" + + "To use " + + chalk.bold(realProjectId) + + " instead, run:\n\n " + + chalk.bold(fixCommand) + ), + { exit: 1 } + ); + } + + return RSVP.reject( + new FirebaseError("Unable to authorize access to project " + chalk.bold(projectId), { + exit: 1, + }) + ); + }); }); - }); }; diff --git a/lib/requireAuth.js b/lib/requireAuth.js index ddc37ade..fd6a55a1 100644 --- a/lib/requireAuth.js +++ b/lib/requireAuth.js @@ -1,30 +1,32 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var RSVP = require('rsvp'); -var autoAuth = require('google-auto-auth'); +var _ = require("lodash"); +var chalk = require("chalk"); +var RSVP = require("rsvp"); +var autoAuth = require("google-auto-auth"); -var api = require('./api'); -var configstore = require('./configstore'); -var FirebaseError = require('./error'); -var logger = require('./logger'); -var utils = require('./utils'); +var api = require("./api"); +var configstore = require("./configstore"); +var FirebaseError = require("./error"); +var logger = require("./logger"); +var utils = require("./utils"); -var AUTH_ERROR = new FirebaseError('Command requires authentication, please run ' + chalk.bold('firebase login')); +var AUTH_ERROR = new FirebaseError( + "Command requires authentication, please run " + chalk.bold("firebase login") +); function _autoAuth(options, authScopes) { return new RSVP.Promise(function(resolve, reject) { - logger.debug('> attempting to authenticate via app default credentials'); - autoAuth({scopes: authScopes}).getToken(function(err, token) { + logger.debug("> attempting to authenticate via app default credentials"); + autoAuth({ scopes: authScopes }).getToken(function(err, token) { if (err) { - logger.debug('! auto-auth error:', err.message); - logger.debug('> no credentials could be found or automatically retrieved'); + logger.debug("! auto-auth error:", err.message); + logger.debug("> no credentials could be found or automatically retrieved"); return reject(AUTH_ERROR); } logger.debug(token); - logger.debug('> retrieved access token via default credentials'); + logger.debug("> retrieved access token via default credentials"); api.setAccessToken(token); resolve(); }); @@ -40,16 +42,16 @@ module.exports = function(options, authScopes) { api.setScopes(inScopes); options.authScopes = api.commandScopes; - var tokens = configstore.get('tokens'); - var user = configstore.get('user'); + var tokens = configstore.get("tokens"); + var user = configstore.get("user"); - var tokenOpt = utils.getInheritedOption(options, 'token'); + var tokenOpt = utils.getInheritedOption(options, "token"); if (tokenOpt) { - logger.debug('> authorizing via --token option'); + logger.debug("> authorizing via --token option"); } else if (process.env.FIREBASE_TOKEN) { - logger.debug('> authorizing via FIREBASE_TOKEN environment variable'); + logger.debug("> authorizing via FIREBASE_TOKEN environment variable"); } else if (user) { - logger.debug('> authorizing via signed-in user'); + logger.debug("> authorizing via signed-in user"); } else { return _autoAuth(options, authScopes); } @@ -61,11 +63,16 @@ module.exports = function(options, authScopes) { return RSVP.resolve(); } - if (!user || !tokens) { return new RSVP.Promise(function(resolve, reject) { - if (configstore.get('session')) { - return reject(new FirebaseError('This version of Firebase CLI requires reauthentication.\n\nPlease run ' + chalk.bold('firebase login') + ' to regain access.')); + if (configstore.get("session")) { + return reject( + new FirebaseError( + "This version of Firebase CLI requires reauthentication.\n\nPlease run " + + chalk.bold("firebase login") + + " to regain access." + ) + ); } return reject(AUTH_ERROR); }); diff --git a/lib/requireConfig.js b/lib/requireConfig.js index 4ad13ca7..80accad4 100644 --- a/lib/requireConfig.js +++ b/lib/requireConfig.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -var RSVP = require('rsvp'); -var FirebaseError = require('./error'); +var RSVP = require("rsvp"); +var FirebaseError = require("./error"); module.exports = function(options) { if (options.config) { @@ -9,6 +9,8 @@ module.exports = function(options) { } return RSVP.reject( options.configError || - new FirebaseError('Not in a Firebase project directory (could not locate firebase.json)', {exit: 1}) + new FirebaseError("Not in a Firebase project directory (could not locate firebase.json)", { + exit: 1, + }) ); }; diff --git a/lib/resolveProjectPath.js b/lib/resolveProjectPath.js index 4739b923..0de7dabe 100644 --- a/lib/resolveProjectPath.js +++ b/lib/resolveProjectPath.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -var path = require('path'); -var detectProjectRoot = require('./detectProjectRoot'); +var path = require("path"); +var detectProjectRoot = require("./detectProjectRoot"); module.exports = function(cwd, filePath) { return path.resolve(detectProjectRoot(cwd), filePath); diff --git a/lib/responseToError.js b/lib/responseToError.js index 275e1401..779d6e89 100644 --- a/lib/responseToError.js +++ b/lib/responseToError.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var FirebaseError = require('./error'); +var _ = require("lodash"); +var FirebaseError = require("./error"); module.exports = function(response, body) { if (response.statusCode < 400) { @@ -9,11 +9,11 @@ module.exports = function(response, body) { } if (!body.error) { body.error = { - message: 'Unknown Error' + message: "Unknown Error", }; } - var message = 'HTTP Error: ' + response.statusCode + ', ' + (body.error.message || body.error); + var message = "HTTP Error: " + response.statusCode + ", " + (body.error.message || body.error); var exitCode; if (response.statusCode >= 500) { @@ -24,12 +24,12 @@ module.exports = function(response, body) { exitCode = 1; } - _.unset(response, 'request.headers'); + _.unset(response, "request.headers"); return new FirebaseError(message, { context: { body: body, - response: response + response: response, }, - exit: exitCode + exit: exitCode, }); }; diff --git a/lib/rtdb.js b/lib/rtdb.js index 6e9195d1..42e00472 100644 --- a/lib/rtdb.js +++ b/lib/rtdb.js @@ -1,27 +1,31 @@ -'use strict'; +"use strict"; -var api = require('./api'); -var FirebaseError = require('./error'); -var utils = require('./utils'); +var api = require("./api"); +var FirebaseError = require("./error"); +var utils = require("./utils"); exports.updateRules = function(instance, src, options) { options = options || {}; - var url = '/.settings/rules.json'; + var url = "/.settings/rules.json"; if (options.dryRun) { - url += '?dryRun=true'; + url += "?dryRun=true"; } - return api.request('PUT', url, { - origin: utils.addSubdomain(api.realtimeOrigin, instance), - auth: true, - data: src, - json: false, - resolveOnHTTPError: true - }).then(function(response) { - if (response.status === 400) { - throw new FirebaseError('Syntax error in database rules:\n\n' + JSON.parse(response.body).error); - } else if (response.status > 400) { - throw new FirebaseError('Unexpected error while deploying database rules.', {exit: 2}); - } - }); + return api + .request("PUT", url, { + origin: utils.addSubdomain(api.realtimeOrigin, instance), + auth: true, + data: src, + json: false, + resolveOnHTTPError: true, + }) + .then(function(response) { + if (response.status === 400) { + throw new FirebaseError( + "Syntax error in database rules:\n\n" + JSON.parse(response.body).error + ); + } else if (response.status > 400) { + throw new FirebaseError("Unexpected error while deploying database rules.", { exit: 2 }); + } + }); }; diff --git a/lib/scopes.js b/lib/scopes.js index 1d1ef56a..2877c445 100644 --- a/lib/scopes.js +++ b/lib/scopes.js @@ -1,14 +1,14 @@ -'use strict'; +"use strict"; module.exports = { // default scopes - OPENID: 'openid', - EMAIL: 'email', - CLOUD_PROJECTS_READONLY: 'https://www.googleapis.com/auth/cloudplatformprojects.readonly', - FIREBASE_PLATFORM: 'https://www.googleapis.com/auth/firebase', + OPENID: "openid", + EMAIL: "email", + CLOUD_PROJECTS_READONLY: "https://www.googleapis.com/auth/cloudplatformprojects.readonly", + FIREBASE_PLATFORM: "https://www.googleapis.com/auth/firebase", // incremental scopes - CLOUD_PLATFORM: 'https://www.googleapis.com/auth/cloud-platform', - CLOUD_STORAGE: 'https://www.googleapis.com/auth/devstorage.read_write', - CLOUD_PUBSUB: 'https://www.googleapis.com/auth/pubsub' + CLOUD_PLATFORM: "https://www.googleapis.com/auth/cloud-platform", + CLOUD_STORAGE: "https://www.googleapis.com/auth/devstorage.read_write", + CLOUD_PUBSUB: "https://www.googleapis.com/auth/pubsub", }; diff --git a/lib/serve/functions.js b/lib/serve/functions.js index dd11ee07..24151877 100644 --- a/lib/serve/functions.js +++ b/lib/serve/functions.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; -var FunctionsEmulator = require('../functionsEmulator'); +var FunctionsEmulator = require("../functionsEmulator"); var emulatorInstance; module.exports = { @@ -10,5 +10,5 @@ module.exports = { }, stop: function() { return emulatorInstance.stop(); - } + }, }; diff --git a/lib/serve/hosting.js b/lib/serve/hosting.js index b8e7fea7..7a4d8c81 100644 --- a/lib/serve/hosting.js +++ b/lib/serve/hosting.js @@ -1,20 +1,20 @@ -'use strict'; +"use strict"; -var chalk = require('chalk'); -var FirebaseError = require('../error'); -var RSVP = require('rsvp'); -var superstatic = require('superstatic').server; -var utils = require('../utils'); -var detectProjectRoot = require('../detectProjectRoot'); -var implicitInit = require('../hosting/implicitInit'); -var initMiddleware = require('../hosting/initMiddleware'); -var functionsProxy = require('../hosting/functionsProxy'); +var chalk = require("chalk"); +var FirebaseError = require("../error"); +var RSVP = require("rsvp"); +var superstatic = require("superstatic").server; +var utils = require("../utils"); +var detectProjectRoot = require("../detectProjectRoot"); +var implicitInit = require("../hosting/implicitInit"); +var initMiddleware = require("../hosting/initMiddleware"); +var functionsProxy = require("../hosting/functionsProxy"); var MAX_PORT_ATTEMPTS = 10; var _attempts = 0; function _startServer(options) { - var config = options.config ? options.config.get('hosting') : {public: '.'}; + var config = options.config ? options.config.get("hosting") : { public: "." }; return implicitInit(options).then(function(init) { var server = superstatic({ @@ -23,36 +23,47 @@ function _startServer(options) { host: options.host, config: config, cwd: detectProjectRoot(options.cwd), - stack: 'strict', + stack: "strict", before: { - files: initMiddleware(init) + files: initMiddleware(init), }, rewriters: { - function: functionsProxy(options) - } + function: functionsProxy(options), + }, }).listen(function() { - if (config.public && config.public !== '.') { - utils.logBullet(chalk.cyan.bold('hosting:') + ' Serving hosting files from: ' + chalk.bold(config.public)); + if (config.public && config.public !== ".") { + utils.logBullet( + chalk.cyan.bold("hosting:") + " Serving hosting files from: " + chalk.bold(config.public) + ); } - utils.logSuccess(chalk.green.bold('hosting:') + ' Local server: ' + - chalk.underline(chalk.bold('http://' + options.host + ':' + options.port)) + '\n'); + utils.logSuccess( + chalk.green.bold("hosting:") + + " Local server: " + + chalk.underline(chalk.bold("http://" + options.host + ":" + options.port)) + + "\n" + ); }); - server.on('error', function(err) { - if (err.code === 'EADDRINUSE') { - var message = 'Port ' + options.port + ' is not available.'; + server.on("error", function(err) { + if (err.code === "EADDRINUSE") { + var message = "Port " + options.port + " is not available."; if (_attempts < MAX_PORT_ATTEMPTS) { - utils.logWarning(chalk.yellow('hosting: ') + message + ' Trying another port...'); + utils.logWarning(chalk.yellow("hosting: ") + message + " Trying another port..."); // Another project that's running takes up to 4 ports: 1 hosting port and 3 functions ports options.port = options.port + 4; _attempts++; _startServer(options); } else { utils.logWarning(message); - throw new FirebaseError('Could not find an open port for hosting development server.', {exit: 1}); + throw new FirebaseError("Could not find an open port for hosting development server.", { + exit: 1, + }); } } else { - throw new FirebaseError('An error occurred while starting the hosting development server:\n\n' + err.toString(), {exit: 1}); + throw new FirebaseError( + "An error occurred while starting the hosting development server:\n\n" + err.toString(), + { exit: 1 } + ); } }); }); @@ -64,5 +75,5 @@ function _stopServer() { module.exports = { start: _startServer, - stop: _stopServer + stop: _stopServer, }; diff --git a/lib/serve/index.js b/lib/serve/index.js index 9c8d81d5..a51fba1a 100644 --- a/lib/serve/index.js +++ b/lib/serve/index.js @@ -1,27 +1,33 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var RSVP = require('rsvp'); -var logger = require('../logger'); +var _ = require("lodash"); +var RSVP = require("rsvp"); +var logger = require("../logger"); var TARGETS = { - hosting: require('./hosting'), - functions: require('./functions') + hosting: require("./hosting"), + functions: require("./functions"), }; var _serve = function(options) { var targetNames = options.targets; options.port = parseInt(options.port, 10); - return RSVP.all(_.map(targetNames, function(targetName) { - var target = TARGETS[targetName]; - return target.start(options); - })).then(function() { + return RSVP.all( + _.map(targetNames, function(targetName) { + var target = TARGETS[targetName]; + return target.start(options); + }) + ).then(function() { return new RSVP.Promise(function(resolve) { - process.on('SIGINT', function() { - logger.info('Shutting down...'); - return RSVP.all(_.forEach(targetNames, function(targetName) { - var target = TARGETS[targetName]; - return target.stop(options); - })).then(resolve).catch(resolve); + process.on("SIGINT", function() { + logger.info("Shutting down..."); + return RSVP.all( + _.forEach(targetNames, function(targetName) { + var target = TARGETS[targetName]; + return target.stop(options); + }) + ) + .then(resolve) + .catch(resolve); }); }); }); diff --git a/lib/track.js b/lib/track.js index c20a6ca0..7335dce7 100644 --- a/lib/track.js +++ b/lib/track.js @@ -1,35 +1,35 @@ -'use strict'; +"use strict"; -var ua = require('universal-analytics'); -var RSVP = require('rsvp'); +var ua = require("universal-analytics"); +var RSVP = require("rsvp"); -var _ = require('lodash'); -var configstore = require('./configstore'); -var pkg = require('../package.json'); -var uuid = require('uuid'); -var logger = require('./logger'); +var _ = require("lodash"); +var configstore = require("./configstore"); +var pkg = require("../package.json"); +var uuid = require("uuid"); +var logger = require("./logger"); -var anonId = configstore.get('analytics-uuid'); +var anonId = configstore.get("analytics-uuid"); if (!anonId) { anonId = uuid.v4(); - configstore.set('analytics-uuid', anonId); + configstore.set("analytics-uuid", anonId); } -var visitor = ua(process.env.FIREBASE_ANALYTICS_UA || 'UA-29174744-3', anonId, { +var visitor = ua(process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3", anonId, { strictCidFormat: false, - https: true + https: true, }); module.exports = function(action, label, duration) { return new RSVP.Promise(function(resolve) { if (!_.isString(action) || !_.isString(label)) { - logger.debug('track received non-string arguments:', action, label); + logger.debug("track received non-string arguments:", action, label); resolve(); } duration = duration || 0; - if (configstore.get('tokens') && configstore.get('usage')) { - visitor.event('Firebase CLI ' + pkg.version, action, label, duration).send(function() { + if (configstore.get("tokens") && configstore.get("usage")) { + visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(function() { // we could handle errors here, but we won't resolve(); }); diff --git a/lib/triggerParser.js b/lib/triggerParser.js index ecfa313f..6bc4802e 100644 --- a/lib/triggerParser.js +++ b/lib/triggerParser.js @@ -1,14 +1,17 @@ // This is an indepedently executed script that parses triggers // from a functions package directory. -'use strict'; +"use strict"; -var extractTriggers = require('./extractTriggers'); -var EXIT = function() { process.exit(0); }; +var extractTriggers = require("./extractTriggers"); +var EXIT = function() { + process.exit(0); +}; -(function() { // wrap in function to allow return without exiting process +(function() { + // wrap in function to allow return without exiting process var packageDir = process.argv[2]; if (!packageDir) { - process.send({error: 'Must supply package directory for functions trigger parsing.'}, EXIT); + process.send({ error: "Must supply package directory for functions trigger parsing." }, EXIT); return; } @@ -17,22 +20,42 @@ var EXIT = function() { process.exit(0); }; try { mod = require(packageDir); } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - process.send({error: 'Error parsing triggers: ' + e.message + '\n\nTry running "npm install" in your functions directory before deploying.'}, EXIT); + if (e.code === "MODULE_NOT_FOUND") { + process.send( + { + error: + "Error parsing triggers: " + + e.message + + '\n\nTry running "npm install" in your functions directory before deploying.', + }, + EXIT + ); return; } else if (/Firebase config variables are not available/.test(e.message)) { - process.send({error: 'Error occurred while parsing your function triggers. Please ensure you have the latest firebase-functions SDK by running "npm i --save firebase-functions@latest" inside your functions folder.\n\n' + e.stack}, EXIT); + process.send( + { + error: + 'Error occurred while parsing your function triggers. Please ensure you have the latest firebase-functions SDK by running "npm i --save firebase-functions@latest" inside your functions folder.\n\n' + + e.stack, + }, + EXIT + ); } - process.send({error: 'Error occurred while parsing your function triggers.\n\n' + e.stack}, EXIT); + process.send( + { + error: "Error occurred while parsing your function triggers.\n\n" + e.stack, + }, + EXIT + ); return; } try { extractTriggers(mod, triggers); } catch (err) { - process.send({error: err.message}, EXIT); + process.send({ error: err.message }, EXIT); } - process.send({triggers: triggers}, EXIT); + process.send({ triggers: triggers }, EXIT); })(); diff --git a/lib/utils.js b/lib/utils.js index 31ab9b5f..cc16abc5 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,15 +1,15 @@ -'use strict'; +"use strict"; -var _ = require('lodash'); -var chalk = require('chalk'); -var Readable = require('stream').Readable; -var RSVP = require('rsvp'); +var _ = require("lodash"); +var chalk = require("chalk"); +var Readable = require("stream").Readable; +var RSVP = require("rsvp"); -var configstore = require('./configstore'); -var FirebaseError = require('./error'); -var logger = require('./logger'); +var configstore = require("./configstore"); +var FirebaseError = require("./error"); +var logger = require("./logger"); -var isWindows = process.platform === 'win32'; +var isWindows = process.platform === "win32"; var ENV_OVERRIDES = []; @@ -20,8 +20,8 @@ module.exports = { * @param {String} path The console path for the URL. */ consoleUrl: function(project, path) { - var api = require('./api'); - return api.consoleOrigin + '/project/' + project + path; + var api = require("./api"); + return api.consoleOrigin + "/project/" + project + path; }, /** * Trace up the ancestry of objects that have a `parent` key, finding the @@ -75,7 +75,7 @@ module.exports = { * @returns {String} The origin for the domain with a subdomain */ addSubdomain: function(origin, subdomain) { - return origin.replace('//', '//' + subdomain + '.'); + return origin.replace("//", "//" + subdomain + "."); }, /** * Log an info statement with a green checkmark at the start of the line. @@ -83,9 +83,9 @@ module.exports = { * @param {String} The log type, defaults to 'info' */ logSuccess: function(message, type) { - type = type || 'info'; - var chr = isWindows ? '+' : '✔'; - logger[type](chalk.green(chr + ' '), message); + type = type || "info"; + var chr = isWindows ? "+" : "✔"; + logger[type](chalk.green(chr + " "), message); }, /** * Log an info statement with a gray bullet at the start of the line. @@ -93,8 +93,8 @@ module.exports = { * @param {String} The log type, defaults to 'info' */ logBullet: function(message, type) { - type = type || 'info'; - logger[type](chalk.cyan.bold('i '), message); + type = type || "info"; + logger[type](chalk.cyan.bold("i "), message); }, /** * Log an info statement with a gray bullet at the start of the line. @@ -102,9 +102,9 @@ module.exports = { * @param {String} The log type, defaults to 'info' */ logWarning: function(message, type) { - type = type || 'warn'; - var chr = isWindows ? '!' : '⚠'; - logger[type](chalk.yellow.bold(chr + ' '), message); + type = type || "warn"; + var chr = isWindows ? "!" : "⚠"; + logger[type](chalk.yellow.bold(chr + " "), message); }, /** * Return a promise that rejects with a FirebaseError. @@ -119,10 +119,12 @@ module.exports = { */ explainStdin: function() { if (isWindows) { - throw new FirebaseError('STDIN input is not available on Windows.', {exit: 1}); + throw new FirebaseError("STDIN input is not available on Windows.", { + exit: 1, + }); } if (process.stdin.isTTY) { - logger.info(chalk.bold('Note:'), 'Reading STDIN. Type JSON data and then press Ctrl-D'); + logger.info(chalk.bold("Note:"), "Reading STDIN. Type JSON data and then press Ctrl-D"); } }, /** @@ -146,14 +148,14 @@ module.exports = { * @param {String} newActive the new active project alias or id */ makeActiveProject: function(projectDir, newActive) { - var activeProjects = configstore.get('activeProjects') || {}; + var activeProjects = configstore.get("activeProjects") || {}; if (newActive) { activeProjects[projectDir] = newActive; } else { _.unset(activeProjects, projectDir); } - configstore.set('activeProjects', activeProjects); + configstore.set("activeProjects", activeProjects); }, /** @@ -161,7 +163,7 @@ module.exports = { * @param {Array} array of parts to be connected together with slashes */ endpoint: function(parts) { - return '/' + _.join(parts, '/'); + return "/" + _.join(parts, "/"); }, /** @@ -170,27 +172,27 @@ module.exports = { */ getFunctionsEventProvider: function(eventType) { // Legacy event types: - var parts = eventType.split('/'); + var parts = eventType.split("/"); if (parts.length > 1) { - var provider = _.last(parts[1].split('.')); + var provider = _.last(parts[1].split(".")); return _.capitalize(provider); } // New event types: if (eventType.match(/google.pubsub/)) { - return 'PubSub'; + return "PubSub"; } else if (eventType.match(/google.storage/)) { - return 'Storage'; + return "Storage"; } else if (eventType.match(/google.analytics/)) { - return 'Analytics'; + return "Analytics"; } else if (eventType.match(/google.firebase.database/)) { - return 'Database'; + return "Database"; } else if (eventType.match(/google.firebase.auth/)) { - return 'Auth'; + return "Auth"; } else if (eventType.match(/google.firebase.crashlytics/)) { - return 'Crashlytics'; + return "Crashlytics"; } else if (eventType.match(/google.firestore/)) { - return 'Firestore'; + return "Firestore"; } - return _.capitalize(eventType.split('.')[1]); - } + return _.capitalize(eventType.split(".")[1]); + }, }; diff --git a/lib/validateJsonRules.js b/lib/validateJsonRules.js index 76a61905..99692515 100644 --- a/lib/validateJsonRules.js +++ b/lib/validateJsonRules.js @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; -var cjson = require('cjson'); -var _ = require('lodash'); +var cjson = require("cjson"); +var _ = require("lodash"); module.exports = function(rules) { var parsed = cjson.parse(rules); - return _.has(parsed, 'rules'); + return _.has(parsed, "rules"); }; diff --git a/lib/validator.js b/lib/validator.js index a5d253ad..c6892471 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -1,13 +1,14 @@ -'use strict'; +"use strict"; -var JSONSchema = require('jsonschema'); +var JSONSchema = require("jsonschema"); var jsonschema = new JSONSchema.Validator(); -var request = require('request'); -var RSVP = require('rsvp'); -var FirebaseError = require('./error'); +var request = require("request"); +var RSVP = require("rsvp"); +var FirebaseError = require("./error"); var NAMED_SCHEMAS = { - firebase: 'https://gist.githubusercontent.com/mbleigh/6040df46f12f349889b2/raw/1c11a6e00a7295c84508dca80f2c92b00ba44006/firebase-schema.json' + firebase: + "https://gist.githubusercontent.com/mbleigh/6040df46f12f349889b2/raw/1c11a6e00a7295c84508dca80f2c92b00ba44006/firebase-schema.json", }; var Validator = function(url) { @@ -28,21 +29,23 @@ Validator.prototype.validate = function(data) { self._validateQueue.push({ data: data, resolve: resolve, - reject: reject + reject: reject, }); self._process(); }); }; Validator.prototype._process = function() { - if (!this.schema) { return; } + if (!this.schema) { + return; + } while (this._validateQueue.length) { var item = this._validateQueue.shift(); var result = jsonschema.validate(item.data, this.schema); - var err = new FirebaseError('Your document has validation errors', { + var err = new FirebaseError("Your document has validation errors", { children: this._decorateErrors(result.errors), - exit: 2 + exit: 2, }); if (result.valid) { @@ -55,7 +58,7 @@ Validator.prototype._process = function() { Validator.prototype._decorateErrors = function(errors) { errors.forEach(function(error) { - error.name = error.property.replace(/^instance/, 'root'); + error.name = error.property.replace(/^instance/, "root"); }); return errors; }; diff --git a/package.json b/package.json index 6a0bb945..12b3933c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "firebase": "./bin/firebase" }, "scripts": { - "test": "./node_modules/.bin/gulp" + "format": "prettier --write '**/*.js' 'bin/*'", + "lint": "eslint '**/*.js'", + "mocha": "nyc mocha test/**/*.spec.js --timeout=1000", + "test": "npm run lint && npm run mocha" }, "repository": { "type": "git", @@ -87,14 +90,12 @@ "chai": "^3.0.0", "chai-as-promised": "^5.1.0", "coveralls": "2.11.2", - "eslint": "^1.4.1", - "gulp": "^3.9.0", - "gulp-eslint": "^1.0.0", - "gulp-exit": "0.0.2", - "gulp-istanbul": "^0.10.0", - "gulp-mocha": "^2.1.2", - "jshint-stylish": "^2.0.1", + "eslint": "^4.19.1", + "eslint-plugin-prettier": "^2.6.0", + "mocha": "^5.0.5", "nock": "^2.10.0", + "nyc": "^11.6.0", + "prettier": "^1.11.1", "sinon": "^1.17.4", "sinon-as-promised": "^4.0.0", "sinon-chai": "^2.8.0" diff --git a/scripts/assets/functions_to_test.js b/scripts/assets/functions_to_test.js index ccd1d1ca..c361cf1d 100644 --- a/scripts/assets/functions_to_test.js +++ b/scripts/assets/functions_to_test.js @@ -1,31 +1,37 @@ -var functions = require('firebase-functions'); -var admin = require('firebase-admin'); +var functions = require("firebase-functions"); +var admin = require("firebase-admin"); admin.initializeApp(functions.config().firebase); -exports.dbAction = functions.database.ref('/input/{uuid}').onWrite(function(event) { - console.log('Received event:', event); - return event.data.ref.root.child('output/' + event.params.uuid).set(event.data.val()); +exports.dbAction = functions.database.ref("/input/{uuid}").onWrite(function(event) { + console.log("Received event:", event); + return event.data.ref.root.child("output/" + event.params.uuid).set(event.data.val()); }); exports.nested = { - dbAction: functions.database.ref('/inputNested/{uuid}').onWrite(function(event) { - console.log('Received event:', event); - return event.data.ref.root.child('output/' + event.params.uuid).set(event.data.val()); - }) + dbAction: functions.database.ref("/inputNested/{uuid}").onWrite(function(event) { + console.log("Received event:", event); + return event.data.ref.root.child("output/" + event.params.uuid).set(event.data.val()); + }), }; exports.httpsAction = functions.https.onRequest(function(req, res) { res.send(req.body); }); -exports.pubsubAction = functions.pubsub.topic('topic1').onPublish(function(event) { - console.log('Received event:', event); +exports.pubsubAction = functions.pubsub.topic("topic1").onPublish(function(event) { + console.log("Received event:", event); var uuid = event.data.json; - return admin.database().ref('output/' + uuid).set(uuid); + return admin + .database() + .ref("output/" + uuid) + .set(uuid); }); exports.gcsAction = functions.storage.object().onChange(function(event) { - console.log('Received event:', event); + console.log("Received event:", event); var uuid = event.data.name; - return admin.database().ref('output/' + uuid).set(uuid); + return admin + .database() + .ref("output/" + uuid) + .set(uuid); }); diff --git a/scripts/test-functions-config.js b/scripts/test-functions-config.js index 52439bb3..088641ca 100644 --- a/scripts/test-functions-config.js +++ b/scripts/test-functions-config.js @@ -1,38 +1,38 @@ #!/usr/bin/env node -'use strict'; +"use strict"; -var chalk = require('chalk'); -var exec = require('child_process').exec; -var expect = require('chai').expect; -var fs = require('fs-extra'); -var tmp = require('tmp'); -var RSVP = require('rsvp'); +var chalk = require("chalk"); +var exec = require("child_process").exec; +var expect = require("chai").expect; +var fs = require("fs-extra"); +var tmp = require("tmp"); +var RSVP = require("rsvp"); -var api = require('../lib/api'); -var scopes = require('../lib/scopes'); -var configstore = require('../lib/configstore'); +var api = require("../lib/api"); +var scopes = require("../lib/scopes"); +var configstore = require("../lib/configstore"); -var localFirebase = __dirname + '/../bin/firebase'; -var projectDir = __dirname + '/test-project'; +var localFirebase = __dirname + "/../bin/firebase"; +var projectDir = __dirname + "/test-project"; var tmpDir; var preTest = function() { - var dir = tmp.dirSync({prefix: 'cfgtest_'}); + var dir = tmp.dirSync({ prefix: "cfgtest_" }); tmpDir = dir.name; fs.copySync(projectDir, tmpDir); - api.setRefreshToken(configstore.get('tokens').refresh_token); + api.setRefreshToken(configstore.get("tokens").refresh_token); api.setScopes(scopes.CLOUD_PLATFORM); - console.log('Done pretest prep.'); + console.log("Done pretest prep."); }; var postTest = function() { fs.remove(tmpDir); - console.log('Done post-test cleanup.'); + console.log("Done post-test cleanup."); }; var set = function(expression) { return new RSVP.Promise(function(resolve) { - exec(localFirebase + ' functions:config:set ' + expression, {'cwd': tmpDir}, function(err) { + exec(localFirebase + " functions:config:set " + expression, { cwd: tmpDir }, function(err) { expect(err).to.be.null; resolve(); }); @@ -41,7 +41,7 @@ var set = function(expression) { var unset = function(key) { return new RSVP.Promise(function(resolve) { - exec(localFirebase + ' functions:config:unset ' + key, {'cwd': tmpDir}, function(err) { + exec(localFirebase + " functions:config:unset " + key, { cwd: tmpDir }, function(err) { expect(err).to.be.null; resolve(); }); @@ -50,7 +50,7 @@ var unset = function(key) { var getAndCompare = function(expected) { return new RSVP.Promise(function(resolve) { - exec(localFirebase + ' functions:config:get', {'cwd': tmpDir}, function(err, stdout) { + exec(localFirebase + " functions:config:get", { cwd: tmpDir }, function(err, stdout) { expect(JSON.parse(stdout)).to.deep.equal(expected); resolve(); }); @@ -59,36 +59,60 @@ var getAndCompare = function(expected) { var runTest = function(description, expression, key, expected) { return set(expression) - .then(function() { - return getAndCompare(expected); - }).then(function() { - return unset(key); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: ') + description); - }); + .then(function() { + return getAndCompare(expected); + }) + .then(function() { + return unset(key); + }) + .then(function() { + console.log(chalk.green("\u2713 Test passed: ") + description); + }); }; var main = function() { preTest(); - runTest('string value', 'foo.bar=faz', 'foo', {foo: {bar: 'faz'}}) - .then(function() { - return runTest('string value in quotes', 'foo.bar="faz"', 'foo', {foo: {bar: 'faz'}}); - }).then(function() { - return runTest('string value with quotes', 'foo.bar=\'"faz"\'', 'foo', {foo: {bar: '\"faz\"'}}); - }).then(function() { - return runTest('single-part key and JSON value', 'foo=\'{"bar":"faz"}\'', 'foo', {foo: {bar: 'faz'}}); - }).then(function() { - return runTest('multi-part key and JSON value', 'foo.too=\'{"bar":"faz"}\'', 'foo', {foo: {too: {bar: 'faz'}}}); - }).then(function() { - return runTest('numeric value', 'foo.bar=123', 'foo', {foo: {bar: '123'}}); - }).then(function() { - return runTest('numeric value in quotes', 'foo.bar="123"', 'foo', {foo: {bar: '123'}}); - }).then(function() { - return runTest('null value', 'foo.bar=null', 'foo', {foo: {bar: 'null'}}); - }).catch(function(err) { - console.log(chalk.red('Error while running tests: '), err); - return RSVP.resolve(); - }).then(postTest); + runTest("string value", "foo.bar=faz", "foo", { foo: { bar: "faz" } }) + .then(function() { + return runTest("string value in quotes", 'foo.bar="faz"', "foo", { + foo: { bar: "faz" }, + }); + }) + .then(function() { + return runTest("string value with quotes", "foo.bar='\"faz\"'", "foo", { + foo: { bar: '"faz"' }, + }); + }) + .then(function() { + return runTest("single-part key and JSON value", 'foo=\'{"bar":"faz"}\'', "foo", { + foo: { bar: "faz" }, + }); + }) + .then(function() { + return runTest("multi-part key and JSON value", 'foo.too=\'{"bar":"faz"}\'', "foo", { + foo: { too: { bar: "faz" } }, + }); + }) + .then(function() { + return runTest("numeric value", "foo.bar=123", "foo", { + foo: { bar: "123" }, + }); + }) + .then(function() { + return runTest("numeric value in quotes", 'foo.bar="123"', "foo", { + foo: { bar: "123" }, + }); + }) + .then(function() { + return runTest("null value", "foo.bar=null", "foo", { + foo: { bar: "null" }, + }); + }) + .catch(function(err) { + console.log(chalk.red("Error while running tests: "), err); + return RSVP.resolve(); + }) + .then(postTest); }; main(); diff --git a/scripts/test-functions-deploy.js b/scripts/test-functions-deploy.js index af40a3f9..3cd73b5e 100644 --- a/scripts/test-functions-deploy.js +++ b/scripts/test-functions-deploy.js @@ -1,47 +1,47 @@ #!/usr/bin/env node -'use strict'; +"use strict"; -var expect = require('chai').expect; -var execSync = require('child_process').execSync; -var exec = require('child_process').exec; -var tmp = require('tmp'); -var _ = require('lodash'); -var fs = require('fs-extra'); -var cloudfunctions = require('../lib/gcp/cloudfunctions'); -var api = require('../lib/api'); -var scopes = require('../lib/scopes'); -var configstore = require('../lib/configstore'); -var extractTriggers = require('../lib/extractTriggers'); -var RSVP = require('rsvp'); -var chalk = require('chalk'); -var firebase = require('firebase'); -var functions = require('firebase-functions'); -var admin = require('firebase-admin'); -var sinon = require('sinon'); +var expect = require("chai").expect; +var execSync = require("child_process").execSync; +var exec = require("child_process").exec; +var tmp = require("tmp"); +var _ = require("lodash"); +var fs = require("fs-extra"); +var cloudfunctions = require("../lib/gcp/cloudfunctions"); +var api = require("../lib/api"); +var scopes = require("../lib/scopes"); +var configstore = require("../lib/configstore"); +var extractTriggers = require("../lib/extractTriggers"); +var RSVP = require("rsvp"); +var chalk = require("chalk"); +var firebase = require("firebase"); +var functions = require("firebase-functions"); +var admin = require("firebase-admin"); +var sinon = require("sinon"); -var functionsSource = __dirname + '/assets/functions_to_test.js'; -var projectDir = __dirname + '/test-project'; -var projectId = 'functions-integration-test'; -var httpsTrigger = 'https://us-central1-functions-integration-test.cloudfunctions.net/httpsAction'; -var region = 'us-central1'; -var localFirebase = __dirname + '/../bin/firebase'; +var functionsSource = __dirname + "/assets/functions_to_test.js"; +var projectDir = __dirname + "/test-project"; +var projectId = "functions-integration-test"; +var httpsTrigger = "https://us-central1-functions-integration-test.cloudfunctions.net/httpsAction"; +var region = "us-central1"; +var localFirebase = __dirname + "/../bin/firebase"; var TIMEOUT = 40000; var tmpDir; var app; var parseFunctionsList = function() { - var configStub = sinon.stub(functions, 'config').returns({ + var configStub = sinon.stub(functions, "config").returns({ firebase: { - databaseURL: 'https://not-a-project.firebaseio.com', - storageBucket: 'not-a-project.appspot.com' - } + databaseURL: "https://not-a-project.firebaseio.com", + storageBucket: "not-a-project.appspot.com", + }, }); - var adminStub = sinon.stub(admin, 'initializeApp'); + var adminStub = sinon.stub(admin, "initializeApp"); var triggers = []; extractTriggers(require(functionsSource), triggers); configStub.restore(); adminStub.restore(); - return _.map(triggers, 'name'); + return _.map(triggers, "name"); }; var getUuid = function() { @@ -49,43 +49,46 @@ var getUuid = function() { }; var preTest = function() { - var dir = tmp.dirSync({prefix: 'fntest_'}); + var dir = tmp.dirSync({ prefix: "fntest_" }); tmpDir = dir.name; fs.copySync(projectDir, tmpDir); - execSync('npm install', {'cwd': tmpDir + '/functions'}); - api.setRefreshToken(configstore.get('tokens').refresh_token); + execSync("npm install", { cwd: tmpDir + "/functions" }); + api.setRefreshToken(configstore.get("tokens").refresh_token); api.setScopes(scopes.CLOUD_PLATFORM); var config = { - apiKey: 'AIzaSyCLgng7Qgzf-2UKRPLz--LtLLxUsMK8oco', - authDomain: 'functions-integration-test.firebaseapp.com', - databaseURL: 'https://functions-integration-test.firebaseio.com', - storageBucket: 'functions-integration-test.appspot.com' + apiKey: "AIzaSyCLgng7Qgzf-2UKRPLz--LtLLxUsMK8oco", + authDomain: "functions-integration-test.firebaseapp.com", + databaseURL: "https://functions-integration-test.firebaseio.com", + storageBucket: "functions-integration-test.appspot.com", }; app = firebase.initializeApp(config); - console.log('Done pretest prep.'); + console.log("Done pretest prep."); }; var postTest = function() { fs.remove(tmpDir); - execSync(localFirebase + ' database:remove / -y', {'cwd': tmpDir}); - console.log('Done post-test cleanup.'); + execSync(localFirebase + " database:remove / -y", { cwd: tmpDir }); + console.log("Done post-test cleanup."); process.exit(); }; var checkFunctionsListMatch = function(expectedFunctions) { - return cloudfunctions.list(projectId, region).then(function(result) { - var deployedFunctions = _.map(result, 'functionName'); - expect(_.isEmpty(_.xor(expectedFunctions, deployedFunctions))).to.be.true; - return true; - }).catch(function(err) { - expect(err).to.be.null; - }); + return cloudfunctions + .list(projectId, region) + .then(function(result) { + var deployedFunctions = _.map(result, "functionName"); + expect(_.isEmpty(_.xor(expectedFunctions, deployedFunctions))).to.be.true; + return true; + }) + .catch(function(err) { + expect(err).to.be.null; + }); }; var testCreateUpdate = function() { - fs.copySync(functionsSource, tmpDir + '/functions/index.js'); + fs.copySync(functionsSource, tmpDir + "/functions/index.js"); return new RSVP.Promise(function(resolve) { - exec(localFirebase + ' deploy', {'cwd': tmpDir}, function(err, stdout) { + exec(localFirebase + " deploy", { cwd: tmpDir }, function(err, stdout) { console.log(stdout); expect(err).to.be.null; resolve(checkFunctionsListMatch(parseFunctionsList())); @@ -94,19 +97,26 @@ var testCreateUpdate = function() { }; var testCreateUpdateWithFilter = function() { - fs.copySync(functionsSource, tmpDir + '/functions/index.js'); + fs.copySync(functionsSource, tmpDir + "/functions/index.js"); return new RSVP.Promise(function(resolve) { - exec(localFirebase + ' deploy --only functions:dbAction,functions:httpsAction', {'cwd': tmpDir}, function(err, stdout) { - console.log(stdout); - expect(err).to.be.null; - resolve(checkFunctionsListMatch(['dbAction', 'httpsAction'])); - }); + exec( + localFirebase + " deploy --only functions:dbAction,functions:httpsAction", + { cwd: tmpDir }, + function(err, stdout) { + console.log(stdout); + expect(err).to.be.null; + resolve(checkFunctionsListMatch(["dbAction", "httpsAction"])); + } + ); }); }; var testDelete = function() { return new RSVP.Promise(function(resolve) { - exec('> functions/index.js &&' + localFirebase + ' deploy', {'cwd': tmpDir}, function(err, stdout) { + exec("> functions/index.js &&" + localFirebase + " deploy", { cwd: tmpDir }, function( + err, + stdout + ) { console.log(stdout); expect(err).to.be.null; resolve(checkFunctionsListMatch([])); @@ -116,141 +126,180 @@ var testDelete = function() { var testDeleteWithFilter = function() { return new RSVP.Promise(function(resolve) { - exec('> functions/index.js &&' + localFirebase + ' deploy --only functions:dbAction', {'cwd': tmpDir}, function(err, stdout) { - console.log(stdout); - expect(err).to.be.null; - resolve(checkFunctionsListMatch(['httpsAction'])); - }); + exec( + "> functions/index.js &&" + localFirebase + " deploy --only functions:dbAction", + { cwd: tmpDir }, + function(err, stdout) { + console.log(stdout); + expect(err).to.be.null; + resolve(checkFunctionsListMatch(["httpsAction"])); + } + ); }); }; var testUnknownFilter = function() { return new RSVP.Promise(function(resolve) { - exec('> functions/index.js &&' + localFirebase + ' deploy --only functions:unknownFilter', {'cwd': tmpDir}, function(err, stdout) { - console.log(stdout); - expect(stdout).to.contain('the following filters were specified but do not match any functions in the project: unknownFilter'); - expect(err).to.be.null; - resolve(checkFunctionsListMatch(['httpsAction'])); - }); + exec( + "> functions/index.js &&" + localFirebase + " deploy --only functions:unknownFilter", + { cwd: tmpDir }, + function(err, stdout) { + console.log(stdout); + expect(stdout).to.contain( + "the following filters were specified but do not match any functions in the project: unknownFilter" + ); + expect(err).to.be.null; + resolve(checkFunctionsListMatch(["httpsAction"])); + } + ); }); }; var waitForAck = function(uuid, testDescription) { return Promise.race([ new Promise(function(resolve) { - var ref = firebase.database().ref('output').child(uuid); - var listener = ref.on('value', function(snap) { + var ref = firebase + .database() + .ref("output") + .child(uuid); + var listener = ref.on("value", function(snap) { if (snap.exists()) { - ref.off('value', listener); + ref.off("value", listener); resolve(); } }); }), new Promise(function(resolve, reject) { setTimeout(function() { - reject('Timed out while waiting for output from ' + testDescription); + reject("Timed out while waiting for output from " + testDescription); }, TIMEOUT); - }) + }), ]); }; var writeToDB = function(path) { var uuid = getUuid(); - return app.database().ref(path).child(uuid).set({'foo': 'bar'}).then(function() { - return RSVP.resolve(uuid); - }); + return app + .database() + .ref(path) + .child(uuid) + .set({ foo: "bar" }) + .then(function() { + return RSVP.resolve(uuid); + }); }; var sendHttpRequest = function(message) { - return api.request('POST', httpsTrigger, { - data: message, - origin: '' - }).then(function(resp) { - expect(resp.status).to.equal(200); - expect(resp.body).to.deep.equal(message); - }); + return api + .request("POST", httpsTrigger, { + data: message, + origin: "", + }) + .then(function(resp) { + expect(resp.status).to.equal(200); + expect(resp.body).to.deep.equal(message); + }); }; var publishPubsub = function(topic) { var uuid = getUuid(); - var message = new Buffer(uuid).toString('base64'); - return api.request('POST', '/v1/projects/functions-integration-test/topics/' + topic + ':publish', { - auth: true, - data: {'messages': [ - {'data': message} - ]}, - origin: 'https://pubsub.googleapis.com' - }).then(function(resp) { - expect(resp.status).to.equal(200); - return RSVP.resolve(uuid); - }); + var message = new Buffer(uuid).toString("base64"); + return api + .request("POST", "/v1/projects/functions-integration-test/topics/" + topic + ":publish", { + auth: true, + data: { + messages: [{ data: message }], + }, + origin: "https://pubsub.googleapis.com", + }) + .then(function(resp) { + expect(resp.status).to.equal(200); + return RSVP.resolve(uuid); + }); }; var saveToStorage = function() { var uuid = getUuid(); - var contentLength = Buffer.byteLength(uuid, 'utf8'); - var resource = ['b', projectId + '.appspot.com', 'o'].join('/'); - var endpoint = '/upload/storage/v1/' + resource + '?uploadType=media&name=' + uuid; - return api.request('POST', endpoint, { - auth: true, - headers: { - 'Content-Type': 'text/plain', - 'Content-Length': contentLength - }, - data: uuid, - json: false, - origin: api.googleOrigin - }).then(function(resp) { - expect(resp.status).to.equal(200); - return RSVP.resolve(uuid); - }); + var contentLength = Buffer.byteLength(uuid, "utf8"); + var resource = ["b", projectId + ".appspot.com", "o"].join("/"); + var endpoint = "/upload/storage/v1/" + resource + "?uploadType=media&name=" + uuid; + return api + .request("POST", endpoint, { + auth: true, + headers: { + "Content-Type": "text/plain", + "Content-Length": contentLength, + }, + data: uuid, + json: false, + origin: api.googleOrigin, + }) + .then(function(resp) { + expect(resp.status).to.equal(200); + return RSVP.resolve(uuid); + }); }; var testFunctionsTrigger = function() { - var checkDbAction = writeToDB('input').then(function(uuid) { - return waitForAck(uuid, 'database triggered function'); + var checkDbAction = writeToDB("input").then(function(uuid) { + return waitForAck(uuid, "database triggered function"); }); - var checkNestedDbAction = writeToDB('inputNested').then(function(uuid) { - return waitForAck(uuid, 'nested database triggered function'); + var checkNestedDbAction = writeToDB("inputNested").then(function(uuid) { + return waitForAck(uuid, "nested database triggered function"); }); - var checkHttpsAction = sendHttpRequest({'message': 'hello'}); - var checkPubsubAction = publishPubsub('topic1').then(function(uuid) { - return waitForAck(uuid, 'pubsub triggered function'); + var checkHttpsAction = sendHttpRequest({ message: "hello" }); + var checkPubsubAction = publishPubsub("topic1").then(function(uuid) { + return waitForAck(uuid, "pubsub triggered function"); }); var checkGcsAction = saveToStorage().then(function(uuid) { - return waitForAck(uuid, 'storage triggered function'); + return waitForAck(uuid, "storage triggered function"); }); - return RSVP.all([checkDbAction, checkNestedDbAction, checkHttpsAction, checkPubsubAction, checkGcsAction]); + return RSVP.all([ + checkDbAction, + checkNestedDbAction, + checkHttpsAction, + checkPubsubAction, + checkGcsAction, + ]); }; var main = function() { preTest(); - testCreateUpdate().then(function() { - console.log(chalk.green('\u2713 Test passed: creating functions')); - return testCreateUpdate(); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: updating functions')); - return testFunctionsTrigger(); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: triggering functions')); - return testDelete(); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: deleting functions')); - return testCreateUpdateWithFilter(); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: creating functions with filters')); - return testDeleteWithFilter(); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: deleting functions with filters')); - return testUnknownFilter(); - }).then(function() { - console.log(chalk.green('\u2713 Test passed: threw warning when passing filter with unknown identifier')); - }).catch(function(err) { - console.log(chalk.red('Error while running tests: '), err); - return RSVP.resolve(); - }).then(postTest); + testCreateUpdate() + .then(function() { + console.log(chalk.green("\u2713 Test passed: creating functions")); + return testCreateUpdate(); + }) + .then(function() { + console.log(chalk.green("\u2713 Test passed: updating functions")); + return testFunctionsTrigger(); + }) + .then(function() { + console.log(chalk.green("\u2713 Test passed: triggering functions")); + return testDelete(); + }) + .then(function() { + console.log(chalk.green("\u2713 Test passed: deleting functions")); + return testCreateUpdateWithFilter(); + }) + .then(function() { + console.log(chalk.green("\u2713 Test passed: creating functions with filters")); + return testDeleteWithFilter(); + }) + .then(function() { + console.log(chalk.green("\u2713 Test passed: deleting functions with filters")); + return testUnknownFilter(); + }) + .then(function() { + console.log( + chalk.green("\u2713 Test passed: threw warning when passing filter with unknown identifier") + ); + }) + .catch(function(err) { + console.log(chalk.red("Error while running tests: "), err); + return RSVP.resolve(); + }) + .then(postTest); }; main(); - - diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 19e6180f..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "env": { - "mocha": true - }, - "rules": { - "no-unused-expressions": 0 - } -} diff --git a/test/helpers/index.js b/test/helpers/index.js index bd3da774..797df9a9 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -1,10 +1,13 @@ -'use strict'; +"use strict"; -require('sinon-as-promised'); +require("sinon-as-promised"); -var auth = require('../../lib/auth'); +var auth = require("../../lib/auth"); exports.mockAuth = function(sandbox) { var authMock = sandbox.mock(auth); - authMock.expects('getAccessToken').atLeast(1).resolves({access_token: 'an_access_token'}); + authMock + .expects("getAccessToken") + .atLeast(1) + .resolves({ access_token: "an_access_token" }); }; diff --git a/test/lib/accountExporter.spec.js b/test/lib/accountExporter.spec.js index 975d2ac6..c0344f51 100644 --- a/test/lib/accountExporter.spec.js +++ b/test/lib/accountExporter.spec.js @@ -1,55 +1,55 @@ -'use strict'; +"use strict"; -var chai = require('chai'); -var nock = require('nock'); -var os = require('os'); -var sinon = require('sinon'); +var chai = require("chai"); +var nock = require("nock"); +var os = require("os"); +var sinon = require("sinon"); -var accountExporter = require('../../lib/accountExporter'); -var helpers = require('../helpers'); +var accountExporter = require("../../lib/accountExporter"); +var helpers = require("../helpers"); var expect = chai.expect; -describe('accountExporter', function() { +describe("accountExporter", function() { var validateOptions = accountExporter.validateOptions; var serialExportUsers = accountExporter.serialExportUsers; - describe('validateOptions', function() { - it('should reject when no format provided', function() { - return expect(validateOptions({}, 'output_file')).to.be.rejected; + describe("validateOptions", function() { + it("should reject when no format provided", function() { + return expect(validateOptions({}, "output_file")).to.be.rejected; }); - it('should reject when format is not csv or json', function() { - return expect(validateOptions({format: 'txt'}, 'output_file')).to.be.rejected; + it("should reject when format is not csv or json", function() { + return expect(validateOptions({ format: "txt" }, "output_file")).to.be.rejected; }); - it('should ignore format param when implicitly specified in file name', function() { - var ret = validateOptions({format: 'JSON'}, 'output_file.csv'); - expect(ret.format).to.eq('csv'); + it("should ignore format param when implicitly specified in file name", function() { + var ret = validateOptions({ format: "JSON" }, "output_file.csv"); + expect(ret.format).to.eq("csv"); }); - it('should use format param when not implicitly specified in file name', function() { - var ret = validateOptions({format: 'JSON'}, 'output_file'); - expect(ret.format).to.eq('json'); + it("should use format param when not implicitly specified in file name", function() { + var ret = validateOptions({ format: "JSON" }, "output_file"); + expect(ret.format).to.eq("json"); }); }); - describe('serialExportUsers', function() { + describe("serialExportUsers", function() { var sandbox; var userList = []; var writeStream = { write: function() {}, - end: function() {} + end: function() {}, }; var spyWrite; beforeEach(function() { sandbox = sinon.sandbox.create(); helpers.mockAuth(sandbox); - spyWrite = sandbox.spy(writeStream, 'write'); + spyWrite = sandbox.spy(writeStream, "write"); for (var i = 1; i <= 7; i++) { userList.push({ localId: i.toString(), - email: 'test' + i + '@test.org' + email: "test" + i + "@test.org", }); } }); @@ -60,54 +60,56 @@ describe('accountExporter', function() { userList = []; }); - it('should call api.request multiple times', function() { - nock('https://www.googleapis.com') - .post('/identitytoolkit/v3/relyingparty/downloadAccount', { - maxResults: 3, - targetProjectId: 'test-project-id' - }) - .reply(200, { - users: userList.slice(0, 3), - nextPageToken: '3' - }) - .post('/identitytoolkit/v3/relyingparty/downloadAccount', { - maxResults: 3, - nextPageToken: '3', - targetProjectId: 'test-project-id' - }) - .reply(200, { - users: userList.slice(3, 6), - nextPageToken: '6' - }) - .post('/identitytoolkit/v3/relyingparty/downloadAccount', { - maxResults: 3, - nextPageToken: '6', - targetProjectId: 'test-project-id' - }) - .reply(200, { - users: userList.slice(6, 7), - nextPageToken: '7' - }) - .post('/identitytoolkit/v3/relyingparty/downloadAccount', { - maxResults: 3, - nextPageToken: '7', - targetProjectId: 'test-project-id' - }) - .reply(200, { - users: [], - nextPageToken: '7' - }); + it("should call api.request multiple times", function() { + nock("https://www.googleapis.com") + .post("/identitytoolkit/v3/relyingparty/downloadAccount", { + maxResults: 3, + targetProjectId: "test-project-id", + }) + .reply(200, { + users: userList.slice(0, 3), + nextPageToken: "3", + }) + .post("/identitytoolkit/v3/relyingparty/downloadAccount", { + maxResults: 3, + nextPageToken: "3", + targetProjectId: "test-project-id", + }) + .reply(200, { + users: userList.slice(3, 6), + nextPageToken: "6", + }) + .post("/identitytoolkit/v3/relyingparty/downloadAccount", { + maxResults: 3, + nextPageToken: "6", + targetProjectId: "test-project-id", + }) + .reply(200, { + users: userList.slice(6, 7), + nextPageToken: "7", + }) + .post("/identitytoolkit/v3/relyingparty/downloadAccount", { + maxResults: 3, + nextPageToken: "7", + targetProjectId: "test-project-id", + }) + .reply(200, { + users: [], + nextPageToken: "7", + }); - var result = serialExportUsers('test-project-id', { - format: 'JSON', + var result = serialExportUsers("test-project-id", { + format: "JSON", batchSize: 3, - writeStream: writeStream + writeStream: writeStream, }); return result.then(function() { expect(spyWrite.callCount).to.eq(7); expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2)); for (var j = 1; j < 7; j++) { - expect(spyWrite.getCall(j).args[0]).to.eq(',' + os.EOL + JSON.stringify(userList[j], null, 2)); + expect(spyWrite.getCall(j).args[0]).to.eq( + "," + os.EOL + JSON.stringify(userList[j], null, 2) + ); } }); }); diff --git a/test/lib/accountImporter.spec.js b/test/lib/accountImporter.spec.js index 6f1142ce..fee341d5 100644 --- a/test/lib/accountImporter.spec.js +++ b/test/lib/accountImporter.spec.js @@ -1,67 +1,77 @@ -'use strict'; +"use strict"; -var chai = require('chai'); -var sinon = require('sinon'); -var api = require('../../lib/api'); -var accountImporter = require('../../lib/accountImporter'); -var helpers = require('../helpers'); +var chai = require("chai"); +var sinon = require("sinon"); +var api = require("../../lib/api"); +var accountImporter = require("../../lib/accountImporter"); +var helpers = require("../helpers"); var expect = chai.expect; -describe('accountImporter', function() { +describe("accountImporter", function() { var validateOptions = accountImporter.validateOptions; var validateUserJson = accountImporter.validateUserJson; var serialImportUsers = accountImporter.serialImportUsers; - describe('validateOptions', function() { - it('should reject when unsupported hash algorithm provided', function() { - return expect(validateOptions({hashAlgo: 'MD2'})).to.be.rejected; + describe("validateOptions", function() { + it("should reject when unsupported hash algorithm provided", function() { + return expect(validateOptions({ hashAlgo: "MD2" })).to.be.rejected; }); - it('should reject when missing parameters', function() { - return expect(validateOptions({hashAlgo: 'HMAC_SHA1'})).to.be.rejected; + it("should reject when missing parameters", function() { + return expect(validateOptions({ hashAlgo: "HMAC_SHA1" })).to.be.rejected; }); }); - describe('validateUserJson', function() { - it('should reject when unknown fields in user json', function() { - return expect(validateUserJson({ - uid: '123', - email: 'test@test.org' - })).to.have.property('error'); + describe("validateUserJson", function() { + it("should reject when unknown fields in user json", function() { + return expect( + validateUserJson({ + uid: "123", + email: "test@test.org", + }) + ).to.have.property("error"); }); - it('should reject when unknown fields in providerUserInfo of user json', function() { - return expect(validateUserJson({ - localId: '123', - email: 'test@test.org', - providerUserInfo: [{ - providerId: 'google.com', - googleId: 'abc', - email: 'test@test.org' - }] - })).to.have.property('error'); + it("should reject when unknown fields in providerUserInfo of user json", function() { + return expect( + validateUserJson({ + localId: "123", + email: "test@test.org", + providerUserInfo: [ + { + providerId: "google.com", + googleId: "abc", + email: "test@test.org", + }, + ], + }) + ).to.have.property("error"); }); - it('should reject when unknown providerUserInfo of user json', function() { - return expect(validateUserJson({ - localId: '123', - email: 'test@test.org', - providerUserInfo: [{ - providerId: 'otheridp.com', - rawId: 'abc', - email: 'test@test.org' - }] - })).to.have.property('error'); + it("should reject when unknown providerUserInfo of user json", function() { + return expect( + validateUserJson({ + localId: "123", + email: "test@test.org", + providerUserInfo: [ + { + providerId: "otheridp.com", + rawId: "abc", + email: "test@test.org", + }, + ], + }) + ).to.have.property("error"); }); }); - describe('serialImportUsers', function() { + describe("serialImportUsers", function() { var sandbox; var mockApi; var batches = []; var hashOptions = { - hashAlgo: 'HMAC_SHA1', - hashKey: 'a2V5MTIz' + hashAlgo: "HMAC_SHA1", + hashKey: "a2V5MTIz", }; var expectedResponse = []; @@ -70,14 +80,16 @@ describe('accountImporter', function() { helpers.mockAuth(sandbox); mockApi = sandbox.mock(api); for (var i = 0; i < 10; i++) { - batches.push([{ - localId: i.toString(), - email: 'test' + i + '@test.org' - }]); + batches.push([ + { + localId: i.toString(), + email: "test" + i + "@test.org", + }, + ]); expectedResponse.push({ status: 200, - response: '', - body: '' + response: "", + body: "", }); } }); @@ -89,48 +101,62 @@ describe('accountImporter', function() { expectedResponse = []; }); - it('should call api.request multiple times', function(done) { + it("should call api.request multiple times", function(done) { for (var i = 0; i < batches.length; i++) { - mockApi.expects('request').withArgs('POST', '/identitytoolkit/v3/relyingparty/uploadAccount', { - auth: true, - data: { - hashAlgorithm: 'HMAC_SHA1', - signerKey: 'a2V5MTIz', - targetProjectId: 'test-project-id', - users: [{ email: 'test' + i + '@test.org', localId: i.toString() }] - }, - json: true, - origin: 'https://www.googleapis.com' - }).once().resolves(expectedResponse[i]); + mockApi + .expects("request") + .withArgs("POST", "/identitytoolkit/v3/relyingparty/uploadAccount", { + auth: true, + data: { + hashAlgorithm: "HMAC_SHA1", + signerKey: "a2V5MTIz", + targetProjectId: "test-project-id", + users: [{ email: "test" + i + "@test.org", localId: i.toString() }], + }, + json: true, + origin: "https://www.googleapis.com", + }) + .once() + .resolves(expectedResponse[i]); } - return expect(serialImportUsers('test-project-id', hashOptions, batches, 0)).to.eventually.notify(done); + return expect( + serialImportUsers("test-project-id", hashOptions, batches, 0) + ).to.eventually.notify(done); }); - it('should continue when some request\'s response is 200 but has `error` in response', function(done) { + it("should continue when some request's response is 200 but has `error` in response", function(done) { expectedResponse[5] = { status: 200, - response: '', + response: "", body: { - error: [{ - index: 0, - message: 'some error message' - }] - } + error: [ + { + index: 0, + message: "some error message", + }, + ], + }, }; for (var i = 0; i < batches.length; i++) { - mockApi.expects('request').withArgs('POST', '/identitytoolkit/v3/relyingparty/uploadAccount', { - auth: true, - data: { - hashAlgorithm: 'HMAC_SHA1', - signerKey: 'a2V5MTIz', - targetProjectId: 'test-project-id', - users: [{ email: 'test' + i + '@test.org', localId: i.toString() }] - }, - json: true, - origin: 'https://www.googleapis.com' - }).once().resolves(expectedResponse[i]); + mockApi + .expects("request") + .withArgs("POST", "/identitytoolkit/v3/relyingparty/uploadAccount", { + auth: true, + data: { + hashAlgorithm: "HMAC_SHA1", + signerKey: "a2V5MTIz", + targetProjectId: "test-project-id", + users: [{ email: "test" + i + "@test.org", localId: i.toString() }], + }, + json: true, + origin: "https://www.googleapis.com", + }) + .once() + .resolves(expectedResponse[i]); } - return expect(serialImportUsers('test-project-id', hashOptions, batches, 0)).to.eventually.notify(done); + return expect( + serialImportUsers("test-project-id", hashOptions, batches, 0) + ).to.eventually.notify(done); }); }); }); diff --git a/test/lib/command.spec.js b/test/lib/command.spec.js index c3aa2522..cef7795f 100644 --- a/test/lib/command.spec.js +++ b/test/lib/command.spec.js @@ -1,86 +1,92 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -chai.use(require('chai-as-promised')); +chai.use(require("chai-as-promised")); -var RSVP = require('rsvp'); -var Command = require('../../lib/command'); +var RSVP = require("rsvp"); +var Command = require("../../lib/command"); -describe('Command', function() { +describe("Command", function() { var command; beforeEach(function() { - command = new Command('example'); + command = new Command("example"); }); - it('should initialize the name from the argument', function() { - expect(new Command('test')._name).to.equal('test'); + it("should initialize the name from the argument", function() { + expect(new Command("test")._name).to.equal("test"); }); - it('should set description with a command', function() { - expect(command.description('test')._description).to.equal('test'); + it("should set description with a command", function() { + expect(command.description("test")._description).to.equal("test"); }); - it('should add an option with a command', function() { - expect(command - .option('-f', 'first option') - .option('-s', 'second option') - ._options + it("should add an option with a command", function() { + expect( + command.option("-f", "first option").option("-s", "second option")._options ).to.have.length(2); }); - it('should add a before with a command', function() { - expect(command - .before(function() { }) - .before(function() { }) - ._befores - ).to.have.length(2); + it("should add a before with a command", function() { + expect(command.before(function() {}).before(function() {})._befores).to.have.length(2); }); - it('should set the action with a command', function() { - var action = function() { }; + it("should set the action with a command", function() { + var action = function() {}; expect(command.action(action)._action).to.equal(action); }); - describe('.runner()', function() { - it('should work when no arguments are passed and options', function() { - var run = command.action(function(options) { - options.foo = 'bar'; - return RSVP.resolve(options); - }).runner(); + describe(".runner()", function() { + it("should work when no arguments are passed and options", function() { + var run = command + .action(function(options) { + options.foo = "bar"; + return RSVP.resolve(options); + }) + .runner(); - return expect(run({foo: 'baz'})).to.eventually.have.property('foo', 'bar'); + return expect(run({ foo: "baz" })).to.eventually.have.property("foo", "bar"); }); - it('should execute befores before the action', function() { - var run = command.before(function(options) { - options.foo = true; - return RSVP.resolve(); - }).action(function(options) { - if (options.foo) { options.bar = 'baz'; } - return options; - }).runner(); + it("should execute befores before the action", function() { + var run = command + .before(function(options) { + options.foo = true; + return RSVP.resolve(); + }) + .action(function(options) { + if (options.foo) { + options.bar = "baz"; + } + return options; + }) + .runner(); - return expect(run({})).to.eventually.have.property('bar'); + return expect(run({})).to.eventually.have.property("bar"); }); - it('should terminate execution if a before errors', function() { - var run = command.before(function() { - throw new Error('foo'); - }).action(function(options, resolve) { - resolve(true); - }).runner(); + it("should terminate execution if a before errors", function() { + var run = command + .before(function() { + throw new Error("foo"); + }) + .action(function(options, resolve) { + resolve(true); + }) + .runner(); - return expect(run()).to.be.rejectedWith('foo'); + return expect(run()).to.be.rejectedWith("foo"); }); - it('should reject the promise if an error is thrown', function() { - var run = command.action(function() { - throw new Error('foo'); - }).runner(); + it("should reject the promise if an error is thrown", function() { + var run = command + .action(function() { + throw new Error("foo"); + }) + .runner(); - return expect(run()).to.be.rejectedWith('foo'); + return expect(run()).to.be.rejectedWith("foo"); }); }); }); diff --git a/test/lib/config.spec.js b/test/lib/config.spec.js index beca1b6c..c637cd2b 100644 --- a/test/lib/config.spec.js +++ b/test/lib/config.spec.js @@ -1,93 +1,102 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var Config = require('../../lib/config'); -var path = require('path'); +var Config = require("../../lib/config"); +var path = require("path"); var _fixtureDir = function(name) { - return path.resolve(__dirname, '../fixtures/' + name); + return path.resolve(__dirname, "../fixtures/" + name); }; -describe('Config', function() { - describe('#new', function() { - it('should hoist legacy hosting keys', function() { - var config = new Config({ - name: 'throwaway', - public: 'public', - ignore: ['**/.*'], - rewrites: [], - redirects: [], - headers: [] - }, {}); +describe("Config", function() { + describe("#new", function() { + it("should hoist legacy hosting keys", function() { + var config = new Config( + { + name: "throwaway", + public: "public", + ignore: ["**/.*"], + rewrites: [], + redirects: [], + headers: [], + }, + {} + ); - expect(config.data.hosting).to.have.property('public', 'public'); - expect(config.data.hosting).to.have.property('ignore'); - expect(config.data.hosting).to.have.property('rewrites'); - expect(config.data.hosting).to.have.property('redirects'); - expect(config.data.hosting).to.have.property('headers'); + expect(config.data.hosting).to.have.property("public", "public"); + expect(config.data.hosting).to.have.property("ignore"); + expect(config.data.hosting).to.have.property("rewrites"); + expect(config.data.hosting).to.have.property("redirects"); + expect(config.data.hosting).to.have.property("headers"); }); - it('should not hoist if hosting key is present', function() { - var config = new Config({ - name: 'throwaway', - hosting: {public: '.'}, - rewrites: [] - }, {}); + it("should not hoist if hosting key is present", function() { + var config = new Config( + { + name: "throwaway", + hosting: { public: "." }, + rewrites: [], + }, + {} + ); - expect(config.data.hosting).to.have.property('public', '.'); - expect(config.data.hosting).not.to.have.property('rewrites'); + expect(config.data.hosting).to.have.property("public", "."); + expect(config.data.hosting).not.to.have.property("rewrites"); }); }); - describe('#importLegacyHostingKeys', function() { - it('should respect non-overlapping keys in hosting', function() { - var redirects = [{source: '/foo', destination: '/bar.html', type: 301}]; - var rewrites = [{source: '**', destination: '/index.html'}]; - var config = new Config({ - rewrites: rewrites, - hosting: { - redirects: redirects - } - }, {}); + describe("#importLegacyHostingKeys", function() { + it("should respect non-overlapping keys in hosting", function() { + var redirects = [{ source: "/foo", destination: "/bar.html", type: 301 }]; + var rewrites = [{ source: "**", destination: "/index.html" }]; + var config = new Config( + { + rewrites: rewrites, + hosting: { + redirects: redirects, + }, + }, + {} + ); config.importLegacyHostingKeys(); - expect(config.get('hosting.redirects')).to.eq(redirects); - expect(config.get('hosting.rewrites')).to.eq(rewrites); + expect(config.get("hosting.redirects")).to.eq(redirects); + expect(config.get("hosting.rewrites")).to.eq(rewrites); }); }); - describe('#_parseFile', function() { - it('should load a cjson file', function() { - var config = new Config({}, {cwd: _fixtureDir('config-imports')}); - expect(config._parseFile('hosting', 'hosting.json').public).to.equal('.'); + describe("#_parseFile", function() { + it("should load a cjson file", function() { + var config = new Config({}, { cwd: _fixtureDir("config-imports") }); + expect(config._parseFile("hosting", "hosting.json").public).to.equal("."); }); - it('should error out for an unknown file', function() { - var config = new Config({}, {cwd: _fixtureDir('config-imports')}); + it("should error out for an unknown file", function() { + var config = new Config({}, { cwd: _fixtureDir("config-imports") }); expect(function() { - config._parseFile('hosting', 'i-dont-exist.json'); - }).to.throw('Imported file i-dont-exist.json does not exist'); + config._parseFile("hosting", "i-dont-exist.json"); + }).to.throw("Imported file i-dont-exist.json does not exist"); }); - it('should error out for an unrecognized extension', function() { - var config = new Config({}, {cwd: _fixtureDir('config-imports')}); + it("should error out for an unrecognized extension", function() { + var config = new Config({}, { cwd: _fixtureDir("config-imports") }); expect(function() { - config._parseFile('hosting', 'unsupported.txt'); - }).to.throw('unsupported.txt is not of a supported config file type'); + config._parseFile("hosting", "unsupported.txt"); + }).to.throw("unsupported.txt is not of a supported config file type"); }); }); - describe('#_materialize', function() { - it('should assign unaltered if an object is found', function() { - var config = new Config({example: {foo: 'bar'}}, {}); - expect(config._materialize('example').foo).to.equal('bar'); + describe("#_materialize", function() { + it("should assign unaltered if an object is found", function() { + var config = new Config({ example: { foo: "bar" } }, {}); + expect(config._materialize("example").foo).to.equal("bar"); }); - it('should prevent top-level key duplication', function() { - var config = new Config({rules: 'rules.json'}, {cwd: _fixtureDir('dup-top-level')}); - expect(config._materialize('rules')).to.deep.equal({'.read': true}); + it("should prevent top-level key duplication", function() { + var config = new Config({ rules: "rules.json" }, { cwd: _fixtureDir("dup-top-level") }); + expect(config._materialize("rules")).to.deep.equal({ ".read": true }); }); }); }); diff --git a/test/lib/extractTriggers.spec.js b/test/lib/extractTriggers.spec.js index 1fcc19a3..4589391c 100644 --- a/test/lib/extractTriggers.spec.js +++ b/test/lib/extractTriggers.spec.js @@ -1,13 +1,13 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var extractTriggers = require('../../lib/extractTriggers'); +var extractTriggers = require("../../lib/extractTriggers"); -describe('extractTriggers', function() { +describe("extractTriggers", function() { var fnWithTrigger = function() {}; - fnWithTrigger.__trigger = {service: 'function.with.trigger'}; + fnWithTrigger.__trigger = { service: "function.with.trigger" }; var fnWithoutTrigger = function() {}; var triggers; @@ -15,38 +15,47 @@ describe('extractTriggers', function() { triggers = []; }); - it('should find exported functions with __trigger', function() { - extractTriggers({ - foo: fnWithTrigger, - bar: fnWithoutTrigger, - baz: fnWithTrigger - }, triggers); + it("should find exported functions with __trigger", function() { + extractTriggers( + { + foo: fnWithTrigger, + bar: fnWithoutTrigger, + baz: fnWithTrigger, + }, + triggers + ); expect(triggers.length).to.eq(2); }); - it('should attach name and entryPoint to exported triggers', function() { - extractTriggers({ - foo: fnWithTrigger - }, triggers); - expect(triggers[0].name).to.eq('foo'); - expect(triggers[0].entryPoint).to.eq('foo'); + it("should attach name and entryPoint to exported triggers", function() { + extractTriggers( + { + foo: fnWithTrigger, + }, + triggers + ); + expect(triggers[0].name).to.eq("foo"); + expect(triggers[0].entryPoint).to.eq("foo"); }); - it('should find nested functions and set name and entryPoint', function() { - extractTriggers({ - foo: { - bar: fnWithTrigger, - baz: { - qux: fnWithTrigger, - not: fnWithoutTrigger - } + it("should find nested functions and set name and entryPoint", function() { + extractTriggers( + { + foo: { + bar: fnWithTrigger, + baz: { + qux: fnWithTrigger, + not: fnWithoutTrigger, + }, + }, + baz: fnWithTrigger, }, - baz: fnWithTrigger - }, triggers); + triggers + ); - expect(triggers[0].name).to.eq('foo-bar'); - expect(triggers[0].entryPoint).to.eq('foo.bar'); + expect(triggers[0].name).to.eq("foo-bar"); + expect(triggers[0].entryPoint).to.eq("foo.bar"); expect(triggers.length).to.eq(3); }); }); diff --git a/test/lib/fsAsync.spec.js b/test/lib/fsAsync.spec.js index ba139fde..c4aa066c 100644 --- a/test/lib/fsAsync.spec.js +++ b/test/lib/fsAsync.spec.js @@ -1,15 +1,15 @@ -'use strict'; +"use strict"; -var crypto = require('crypto'); -var fs = require('fs'); -var os = require('os'); -var path = require('path'); +var crypto = require("crypto"); +var fs = require("fs"); +var os = require("os"); +var path = require("path"); -var fsAsync = require('../../lib/fsAsync'); +var fsAsync = require("../../lib/fsAsync"); -var chai = require('chai'); -var chaiAsPromised = require('chai-as-promised'); -var _ = require('lodash'); +var chai = require("chai"); +var chaiAsPromised = require("chai-as-promised"); +var _ = require("lodash"); var expect = chai.expect; chai.use(chaiAsPromised); @@ -26,24 +26,24 @@ chai.use(chaiAsPromised); // nestednodemodules // node_modules // subfile -describe('fsAsync', function() { +describe("fsAsync", function() { var baseDir; var files = [ - '.hidden', - 'visible', - 'subdir/subfile', - 'subdir/nesteddir/nestedfile', - 'subdir/node_modules/nestednodemodules', - 'node_modules/subfile' + ".hidden", + "visible", + "subdir/subfile", + "subdir/nesteddir/nestedfile", + "subdir/node_modules/nestednodemodules", + "node_modules/subfile", ]; before(function() { - baseDir = path.join(os.tmpdir(), crypto.randomBytes(10).toString('hex')); + baseDir = path.join(os.tmpdir(), crypto.randomBytes(10).toString("hex")); fs.mkdirSync(baseDir); - fs.mkdirSync(path.join(baseDir, 'subdir')); - fs.mkdirSync(path.join(baseDir, 'subdir', 'nesteddir')); - fs.mkdirSync(path.join(baseDir, 'subdir', 'node_modules')); - fs.mkdirSync(path.join(baseDir, 'node_modules')); + fs.mkdirSync(path.join(baseDir, "subdir")); + fs.mkdirSync(path.join(baseDir, "subdir", "nesteddir")); + fs.mkdirSync(path.join(baseDir, "subdir", "node_modules")); + fs.mkdirSync(path.join(baseDir, "node_modules")); _.each(files, function(file) { fs.writeFileSync(path.join(baseDir, file), file); }); @@ -55,52 +55,65 @@ describe('fsAsync', function() { }); }); - it('can recurse directories', function() { - var foundFiles = fsAsync.readdirRecursive({path: baseDir}).then(function(results) { + it("can recurse directories", function() { + var foundFiles = fsAsync.readdirRecursive({ path: baseDir }).then(function(results) { return _.map(results, function(result) { return result.name; }).sort(); }); - var expectFiles = _.map(files, function(file) { return path.join(baseDir, file); }).sort(); + var expectFiles = _.map(files, function(file) { + return path.join(baseDir, file); + }).sort(); return expect(foundFiles).to.eventually.deep.equal(expectFiles); }); - it('can ignore directories', function() { - var expected = _ - .chain(files) + it("can ignore directories", function() { + var expected = _.chain(files) .reject(function(file) { - return file.indexOf('node_modules') !== -1; - }).map(function(file) { + return file.indexOf("node_modules") !== -1; + }) + .map(function(file) { return path.join(baseDir, file); - }).value().sort(); + }) + .value() + .sort(); - var promise = fsAsync.readdirRecursive({ - path: baseDir, - ignore: ['node_modules'] - }).then(function(results) { - return _.map(results, function(result) { return result.name; }).sort(); - }); + var promise = fsAsync + .readdirRecursive({ + path: baseDir, + ignore: ["node_modules"], + }) + .then(function(results) { + return _.map(results, function(result) { + return result.name; + }).sort(); + }); return expect(promise).to.eventually.deep.equal(expected); }); - it('supports blob rules', function() { - var expected = _ - .chain(files) + it("supports blob rules", function() { + var expected = _.chain(files) .reject(function(file) { - return file.indexOf('node_modules') !== -1; - }).map(function(file) { + return file.indexOf("node_modules") !== -1; + }) + .map(function(file) { return path.join(baseDir, file); - }).value().sort(); + }) + .value() + .sort(); - var promise = fsAsync.readdirRecursive({ - path: baseDir, - ignore: ['**/node_modules/**'] - }).then(function(results) { - return _.map(results, function(result) { return result.name; }).sort(); - }); + var promise = fsAsync + .readdirRecursive({ + path: baseDir, + ignore: ["**/node_modules/**"], + }) + .then(function(results) { + return _.map(results, function(result) { + return result.name; + }).sort(); + }); return expect(promise).to.eventually.deep.equal(expected); }); }); - diff --git a/test/lib/functionsConfig.spec.js b/test/lib/functionsConfig.spec.js index 9ba2d0b8..3c5aabff 100644 --- a/test/lib/functionsConfig.spec.js +++ b/test/lib/functionsConfig.spec.js @@ -1,45 +1,47 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var functionsConfig = require('../../lib/functionsConfig'); +var functionsConfig = require("../../lib/functionsConfig"); -describe('config.parseSetArgs', function() { - it('should throw if a reserved namespace is used', function() { +describe("config.parseSetArgs", function() { + it("should throw if a reserved namespace is used", function() { expect(function() { - functionsConfig.parseSetArgs(['firebase.something=else']); - }).to.throw('reserved namespace'); + functionsConfig.parseSetArgs(["firebase.something=else"]); + }).to.throw("reserved namespace"); }); - it('should throw if a malformed arg is used', function() { + it("should throw if a malformed arg is used", function() { expect(function() { - functionsConfig.parseSetArgs(['foo.bar=baz', 'qux']); - }).to.throw('must be in key=val format'); + functionsConfig.parseSetArgs(["foo.bar=baz", "qux"]); + }).to.throw("must be in key=val format"); }); - it('should parse args into correct config and variable IDs', function() { - expect(functionsConfig.parseSetArgs(['foo.bar.faz=val'])) - .to.deep.eq([{ - configId: 'foo', - varId: 'bar/faz', - val: 'val' - }]); + it("should parse args into correct config and variable IDs", function() { + expect(functionsConfig.parseSetArgs(["foo.bar.faz=val"])).to.deep.eq([ + { + configId: "foo", + varId: "bar/faz", + val: "val", + }, + ]); }); }); -describe('config.parseUnsetArgs', function() { - it('should throw if a reserved namespace is used', function() { +describe("config.parseUnsetArgs", function() { + it("should throw if a reserved namespace is used", function() { expect(function() { - functionsConfig.parseUnsetArgs(['firebase.something']); - }).to.throw('reserved namespace'); + functionsConfig.parseUnsetArgs(["firebase.something"]); + }).to.throw("reserved namespace"); }); - it('should parse args into correct config and variable IDs', function() { - expect(functionsConfig.parseUnsetArgs(['foo.bar.faz'])) - .to.deep.eq([{ - configId: 'foo', - varId: 'bar/faz' - }]); + it("should parse args into correct config and variable IDs", function() { + expect(functionsConfig.parseUnsetArgs(["foo.bar.faz"])).to.deep.eq([ + { + configId: "foo", + varId: "bar/faz", + }, + ]); }); }); diff --git a/test/lib/identifierToProjectId.spec.js b/test/lib/identifierToProjectId.spec.js index bd7bf69a..be2f95fa 100644 --- a/test/lib/identifierToProjectId.spec.js +++ b/test/lib/identifierToProjectId.spec.js @@ -1,15 +1,15 @@ -'use strict'; +"use strict"; -var chai = require('chai'); -chai.use(require('chai-as-promised')); +var chai = require("chai"); +chai.use(require("chai-as-promised")); var expect = chai.expect; -var sinon = require('sinon'); +var sinon = require("sinon"); -var helpers = require('../helpers'); -var identifierToProjectId = require('../../lib/identifierToProjectId'); -var api = require('../../lib/api'); +var helpers = require("../helpers"); +var identifierToProjectId = require("../../lib/identifierToProjectId"); +var api = require("../../lib/api"); -describe('identifierToProjectId', function() { +describe("identifierToProjectId", function() { var sandbox; var mockApi; @@ -23,22 +23,22 @@ describe('identifierToProjectId', function() { sandbox.restore(); }); - it('should return a project id if there is an exact match', function() { - mockApi.expects('getProjects').resolves({foobar: {}}); - return expect(identifierToProjectId('foobar')).to.eventually.equal('foobar'); + it("should return a project id if there is an exact match", function() { + mockApi.expects("getProjects").resolves({ foobar: {} }); + return expect(identifierToProjectId("foobar")).to.eventually.equal("foobar"); }); - it('should return an instance if one is a match', function() { - mockApi.expects('getProjects').resolves({ - foo: {instances: {database: ['bar']}} + it("should return an instance if one is a match", function() { + mockApi.expects("getProjects").resolves({ + foo: { instances: { database: ["bar"] } }, }); - return expect(identifierToProjectId('bar')).to.eventually.equal('foo'); + return expect(identifierToProjectId("bar")).to.eventually.equal("foo"); }); - it('should return null if no match is found', function() { - mockApi.expects('getProjects').resolves({ - foo: {instances: {database: ['bar']}} + it("should return null if no match is found", function() { + mockApi.expects("getProjects").resolves({ + foo: { instances: { database: ["bar"] } }, }); - return expect(identifierToProjectId('nope')).to.eventually.be.null; + return expect(identifierToProjectId("nope")).to.eventually.be.null; }); }); diff --git a/test/lib/listFiles.spec.js b/test/lib/listFiles.spec.js index 95a72e62..0cf4c3ae 100644 --- a/test/lib/listFiles.spec.js +++ b/test/lib/listFiles.spec.js @@ -1,24 +1,22 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var path = require('path'); +var path = require("path"); -var listFiles = require('../../lib/listFiles'); +var listFiles = require("../../lib/listFiles"); -describe('listFiles', function() { +describe("listFiles", function() { // for details, see the file structure and firebase.json in test/fixtures/ignores - it('should ignore firebase-debug.log, specified ignores, and nothing else', function() { - expect(listFiles(path.resolve(__dirname, '../fixtures/ignores'), [ - '**/.*', - 'firebase.json', - 'ignored.txt', - 'ignored/**/*.txt' - ])).to.deep.equal([ - 'index.html', - 'ignored/index.html', - 'present/index.html' - ]); + it("should ignore firebase-debug.log, specified ignores, and nothing else", function() { + expect( + listFiles(path.resolve(__dirname, "../fixtures/ignores"), [ + "**/.*", + "firebase.json", + "ignored.txt", + "ignored/**/*.txt", + ]) + ).to.deep.equal(["index.html", "ignored/index.html", "present/index.html"]); }); }); diff --git a/test/lib/localFunction.spec.js b/test/lib/localFunction.spec.js index c817570c..674518ad 100644 --- a/test/lib/localFunction.spec.js +++ b/test/lib/localFunction.spec.js @@ -1,40 +1,45 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var LocalFunction = require('../../lib/localFunction'); +var LocalFunction = require("../../lib/localFunction"); -describe('localFunction._constructAuth', function() { +describe("localFunction._constructAuth", function() { var lf = new LocalFunction({}); var constructAuth = lf._constructAuth; - it('warn if opts.auth and opts.authType are conflicting', function() { + it("warn if opts.auth and opts.authType are conflicting", function() { expect(function() { - return constructAuth({ uid: 'something' }, 'UNAUTHENTICATED'); - }).to.throw('incompatible'); + return constructAuth({ uid: "something" }, "UNAUTHENTICATED"); + }).to.throw("incompatible"); expect(function() { - return constructAuth({ uid: 'something' }, 'ADMIN'); - }).to.throw('incompatible'); + return constructAuth({ uid: "something" }, "ADMIN"); + }).to.throw("incompatible"); }); - it('construct the correct auth for admin users', function() { - expect(constructAuth(undefined, 'ADMIN')).to.deep.equal({ admin: true }); + it("construct the correct auth for admin users", function() { + expect(constructAuth(undefined, "ADMIN")).to.deep.equal({ admin: true }); }); - it('construct the correct auth for unauthenticated users', function() { - expect(constructAuth(undefined, 'UNAUTHENTICATED')).to.deep.equal({ - admin: false }); + it("construct the correct auth for unauthenticated users", function() { + expect(constructAuth(undefined, "UNAUTHENTICATED")).to.deep.equal({ + admin: false, + }); }); - it('construct the correct auth for authenticated users', function() { - expect(constructAuth(undefined, 'USER')).to.deep.equal({ variable: { uid: '', token: {} } }); - expect(constructAuth({ uid: '11' }, 'USER')).to.deep.equal({ variable: { uid: '11', token: {} } }); + it("construct the correct auth for authenticated users", function() { + expect(constructAuth(undefined, "USER")).to.deep.equal({ + variable: { uid: "", token: {} }, + }); + expect(constructAuth({ uid: "11" }, "USER")).to.deep.equal({ + variable: { uid: "11", token: {} }, + }); }); - it('leaves auth untouched if it already follows wire format', function() { - var auth = { variable: { uid: 'something' } }; + it("leaves auth untouched if it already follows wire format", function() { + var auth = { variable: { uid: "something" } }; expect(constructAuth(auth)).to.deep.equal(auth); }); }); diff --git a/test/lib/profilerReport.spec.js b/test/lib/profilerReport.spec.js index 487e10d4..82f6ae2d 100644 --- a/test/lib/profilerReport.spec.js +++ b/test/lib/profilerReport.spec.js @@ -1,107 +1,108 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var path = require('path'); -var stream = require('stream'); -var ProfileReport = require('../../lib/profileReport'); +var path = require("path"); +var stream = require("stream"); +var ProfileReport = require("../../lib/profileReport"); var combinerFunc = function(obj1, obj2) { - return {count: obj1.count + obj2.count}; + return { count: obj1.count + obj2.count }; }; -var fixturesDir = path.resolve(__dirname, '../fixtures'); +var fixturesDir = path.resolve(__dirname, "../fixtures"); var newReport = function() { - var input = path.resolve(fixturesDir, 'profiler-data/sample.json'); + var input = path.resolve(fixturesDir, "profiler-data/sample.json"); var throwAwayStream = new stream.PassThrough(); return new ProfileReport(input, throwAwayStream, { - format: 'JSON', + format: "JSON", isFile: false, - collapse: true + collapse: true, }); }; -describe('profilerReport', function() { - it('should correctly generate a report', function() { +describe("profilerReport", function() { + it("should correctly generate a report", function() { var report = newReport(); - var output = require(path.resolve(fixturesDir, 'profiler-data/sample-output.json')); + var output = require(path.resolve(fixturesDir, "profiler-data/sample-output.json")); expect(report.generate()).to.eventually.deep.equal(output); }); - it('should format numbers correctly', function() { + it("should format numbers correctly", function() { var result = ProfileReport.formatNumber(5); - expect(result).to.eq('5'); - result = ProfileReport.formatNumber(5.00); - expect(result).to.eq('5'); + expect(result).to.eq("5"); + result = ProfileReport.formatNumber(5.0); + expect(result).to.eq("5"); result = ProfileReport.formatNumber(3.33); - expect(result).to.eq('3.33'); + expect(result).to.eq("3.33"); result = ProfileReport.formatNumber(3.123423); - expect(result).to.eq('3.12'); + expect(result).to.eq("3.12"); result = ProfileReport.formatNumber(3.129); - expect(result).to.eq('3.13'); + expect(result).to.eq("3.13"); result = ProfileReport.formatNumber(3123423232); - expect(result).to.eq('3,123,423,232'); + expect(result).to.eq("3,123,423,232"); result = ProfileReport.formatNumber(3123423232.4242); - expect(result).to.eq('3,123,423,232.42'); + expect(result).to.eq("3,123,423,232.42"); }); - it('should not collapse paths if not needed', function() { + it("should not collapse paths if not needed", function() { var report = newReport(); var data = {}; for (var i = 0; i < 20; i++) { - data['/path/num' + i] = {count: 1}; + data["/path/num" + i] = { count: 1 }; } var result = report.collapsePaths(data, combinerFunc); expect(result).to.deep.eq(data); }); - it('should collapse paths to $wildcard', function() { + it("should collapse paths to $wildcard", function() { var report = newReport(); var data = {}; for (var i = 0; i < 30; i++) { - data['/path/num' + i] = {count: 1}; + data["/path/num" + i] = { count: 1 }; } var result = report.collapsePaths(data, combinerFunc); - expect(result).to.deep.eq({'/path/$wildcard': {count: 30}}); + expect(result).to.deep.eq({ "/path/$wildcard": { count: 30 } }); }); - it('should not collapse paths with --no-collapse', function() { + it("should not collapse paths with --no-collapse", function() { var report = newReport(); report.options.collapse = false; var data = {}; for (var i = 0; i < 30; i++) { - data['/path/num' + i] = {count: 1}; + data["/path/num" + i] = { count: 1 }; } var result = report.collapsePaths(data, combinerFunc); expect(result).to.deep.eq(data); }); - it('should collapse paths recursively', function() { + it("should collapse paths recursively", function() { var report = newReport(); var data = {}; for (var i = 0; i < 30; i++) { - data['/path/num' + i + '/next' + i] = {count: 1}; + data["/path/num" + i + "/next" + i] = { count: 1 }; } - data['/path/num1/bar/test'] = {count: 1}; - data['/foo'] = {count: 1}; + data["/path/num1/bar/test"] = { count: 1 }; + data["/foo"] = { count: 1 }; var result = report.collapsePaths(data, combinerFunc); expect(result).to.deep.eq({ - '/path/$wildcard/$wildcard': {count: 30}, - '/path/$wildcard/$wildcard/test': {count: 1}, - '/foo': {count: 1}}); + "/path/$wildcard/$wildcard": { count: 30 }, + "/path/$wildcard/$wildcard/test": { count: 1 }, + "/foo": { count: 1 }, + }); }); - it('should extract the correct path index', function() { - var query = {index: {path: ['foo', 'bar']}}; + it("should extract the correct path index", function() { + var query = { index: { path: ["foo", "bar"] } }; var result = ProfileReport.extractReadableIndex(query); - expect(result).to.eq('/foo/bar'); + expect(result).to.eq("/foo/bar"); }); - it('should extract the correct value index', function() { - var query = {index: {}}; + it("should extract the correct value index", function() { + var query = { index: {} }; var result = ProfileReport.extractReadableIndex(query); - expect(result).to.eq('.value'); + expect(result).to.eq(".value"); }); }); diff --git a/test/lib/rc.spec.js b/test/lib/rc.spec.js index a342211b..56490d80 100644 --- a/test/lib/rc.spec.js +++ b/test/lib/rc.spec.js @@ -1,151 +1,161 @@ -'use strict'; +"use strict"; -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var path = require('path'); -var RC = require('../../lib/rc'); +var path = require("path"); +var RC = require("../../lib/rc"); -var fixturesDir = path.resolve(__dirname, '../fixtures'); +var fixturesDir = path.resolve(__dirname, "../fixtures"); -describe('RC', function() { - describe('.load', function() { - it('should load from nearest project directory', function() { - var result = RC.load(path.resolve(fixturesDir, 'fbrc/conflict')); - expect(result.projects.default).to.eq('top'); +describe("RC", function() { + describe(".load", function() { + it("should load from nearest project directory", function() { + var result = RC.load(path.resolve(fixturesDir, "fbrc/conflict")); + expect(result.projects.default).to.eq("top"); }); - it('should be an empty object when not in project dir', function() { + it("should be an empty object when not in project dir", function() { var result = RC.load(__dirname); return expect(result.data).to.deep.eq({}); }); - it('should not throw up on invalid json', function() { - var result = RC.load(path.resolve(fixturesDir, 'fbrc/invalid')); + it("should not throw up on invalid json", function() { + var result = RC.load(path.resolve(fixturesDir, "fbrc/invalid")); return expect(result.data).to.deep.eq({}); }); }); - describe('instance methods', function() { + describe("instance methods", function() { var subject; beforeEach(function() { subject = new RC(); }); - describe('#addProjectAlias', function() { - it('should set a value in projects.', function() { - expect(subject.addProjectAlias('foo', 'bar')).to.be.false; - expect(subject.projects.foo).to.eq('bar'); + describe("#addProjectAlias", function() { + it("should set a value in projects.", function() { + expect(subject.addProjectAlias("foo", "bar")).to.be.false; + expect(subject.projects.foo).to.eq("bar"); }); }); - describe('#removeProjectAlias', function() { - it('should remove an already set value in projects.', function() { - subject.addProjectAlias('foo', 'bar'); - expect(subject.projects.foo).to.eq('bar'); - expect(subject.removeProjectAlias('foo')).to.be.false; + describe("#removeProjectAlias", function() { + it("should remove an already set value in projects.", function() { + subject.addProjectAlias("foo", "bar"); + expect(subject.projects.foo).to.eq("bar"); + expect(subject.removeProjectAlias("foo")).to.be.false; expect(subject.projects).to.deep.eq({}); }); }); - describe('#hasProjects', function() { - it('should be true if project aliases are set, false if not', function() { + describe("#hasProjects", function() { + it("should be true if project aliases are set, false if not", function() { expect(subject.hasProjects).to.be.false; - subject.addProjectAlias('foo', 'bar'); + subject.addProjectAlias("foo", "bar"); expect(subject.hasProjects).to.be.true; }); }); - describe('#targets', function() { - it('should return all targets for specified project and type', function() { - var data = {foo: ['bar']}; - subject.set('targets', {myproject: {storage: data}}); - expect(subject.targets('myproject', 'storage')).to.deep.eq(data); + describe("#targets", function() { + it("should return all targets for specified project and type", function() { + var data = { foo: ["bar"] }; + subject.set("targets", { myproject: { storage: data } }); + expect(subject.targets("myproject", "storage")).to.deep.eq(data); }); - it('should return an empty object for missing data', function() { - expect(subject.targets('foo', 'storage')).to.deep.eq({}); + it("should return an empty object for missing data", function() { + expect(subject.targets("foo", "storage")).to.deep.eq({}); }); }); - describe('#target', function() { - it('should return all resources for a specified target', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar', 'baz']}}}); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq(['bar', 'baz']); + describe("#target", function() { + it("should return all resources for a specified target", function() { + subject.set("targets", { + myproject: { storage: { foo: ["bar", "baz"] } }, + }); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq(["bar", "baz"]); }); - it('should return an empty array if nothing is found', function() { - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq([]); + it("should return an empty array if nothing is found", function() { + expect(subject.target("myproject", "storage", "foo")).to.deep.eq([]); }); }); - describe('#unsetTargetResource', function() { - it('should remove a resource from a target', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar', 'baz', 'qux']}}}); - subject.unsetTargetResource('myproject', 'storage', 'foo', 'baz'); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq(['bar', 'qux']); + describe("#unsetTargetResource", function() { + it("should remove a resource from a target", function() { + subject.set("targets", { + myproject: { storage: { foo: ["bar", "baz", "qux"] } }, + }); + subject.unsetTargetResource("myproject", "storage", "foo", "baz"); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq(["bar", "qux"]); }); - it('should no-op if the resource is not in the target', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar', 'baz', 'qux']}}}); - subject.unsetTargetResource('myproject', 'storage', 'foo', 'derp'); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq(['bar', 'baz', 'qux']); + it("should no-op if the resource is not in the target", function() { + subject.set("targets", { + myproject: { storage: { foo: ["bar", "baz", "qux"] } }, + }); + subject.unsetTargetResource("myproject", "storage", "foo", "derp"); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq(["bar", "baz", "qux"]); }); }); - describe('#applyTarget', function() { - it('should error for an unrecognized target type', function() { + describe("#applyTarget", function() { + it("should error for an unrecognized target type", function() { expect(function() { - subject.applyTarget('myproject', 'fake', 'foo', ['bar']); - }).to.throw('Unrecognized target type'); + subject.applyTarget("myproject", "fake", "foo", ["bar"]); + }).to.throw("Unrecognized target type"); }); - it('should coerce a string argument into an array', function() { - subject.applyTarget('myproject', 'storage', 'foo', 'bar'); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq(['bar']); + it("should coerce a string argument into an array", function() { + subject.applyTarget("myproject", "storage", "foo", "bar"); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq(["bar"]); }); - it('should add all resources to the specified target', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar']}}}); - subject.applyTarget('myproject', 'storage', 'foo', ['baz', 'qux']); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq(['bar', 'baz', 'qux']); + it("should add all resources to the specified target", function() { + subject.set("targets", { myproject: { storage: { foo: ["bar"] } } }); + subject.applyTarget("myproject", "storage", "foo", ["baz", "qux"]); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq(["bar", "baz", "qux"]); }); - it('should remove a resource from a different target', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar']}}}); - subject.applyTarget('myproject', 'storage', 'baz', ['bar', 'qux']); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq([]); - expect(subject.target('myproject', 'storage', 'baz')).to.deep.eq(['bar', 'qux']); + it("should remove a resource from a different target", function() { + subject.set("targets", { myproject: { storage: { foo: ["bar"] } } }); + subject.applyTarget("myproject", "storage", "baz", ["bar", "qux"]); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq([]); + expect(subject.target("myproject", "storage", "baz")).to.deep.eq(["bar", "qux"]); }); - it('should return a list of resources that changed targets', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar']}}}); - var result = subject.applyTarget('myproject', 'storage', 'baz', ['bar', 'qux']); - expect(result).to.deep.eq([{resource: 'bar', target: 'foo'}]); + it("should return a list of resources that changed targets", function() { + subject.set("targets", { myproject: { storage: { foo: ["bar"] } } }); + var result = subject.applyTarget("myproject", "storage", "baz", ["bar", "qux"]); + expect(result).to.deep.eq([{ resource: "bar", target: "foo" }]); }); }); - describe('#removeTarget', function() { - it('should remove a the target for a specific resource and return its name', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar', 'baz']}}}); - expect(subject.removeTarget('myproject', 'storage', 'bar')).to.eq('foo'); - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq(['baz']); + describe("#removeTarget", function() { + it("should remove a the target for a specific resource and return its name", function() { + subject.set("targets", { + myproject: { storage: { foo: ["bar", "baz"] } }, + }); + expect(subject.removeTarget("myproject", "storage", "bar")).to.eq("foo"); + expect(subject.target("myproject", "storage", "foo")).to.deep.eq(["baz"]); }); - it('should return null if not present', function() { - expect(subject.removeTarget('myproject', 'storage', 'fake')).to.be.null; + it("should return null if not present", function() { + expect(subject.removeTarget("myproject", "storage", "fake")).to.be.null; }); }); - describe('#clearTarget', function() { - it('should clear an existing target by name and return true', function() { - subject.set('targets', {myproject: {storage: {foo: ['bar', 'baz']}}}); - expect(subject.clearTarget('myproject', 'storage', 'foo')).to.be.true; - expect(subject.target('myproject', 'storage', 'foo')).to.deep.eq([]); + describe("#clearTarget", function() { + it("should clear an existing target by name and return true", function() { + subject.set("targets", { + myproject: { storage: { foo: ["bar", "baz"] } }, + }); + expect(subject.clearTarget("myproject", "storage", "foo")).to.be.true; + expect(subject.target("myproject", "storage", "foo")).to.deep.eq([]); }); - it('should return false for a non-existent target', function() { - expect(subject.clearTarget('myproject', 'storage', 'foo')).to.be.false; + it("should return false for a non-existent target", function() { + expect(subject.clearTarget("myproject", "storage", "foo")).to.be.false; }); }); });