firebase-tools 10.1.2 → 10.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +92 -48
  3. package/lib/archiveDirectory.js +63 -73
  4. package/lib/auth.js +62 -25
  5. package/lib/commands/ext-configure.js +1 -0
  6. package/lib/commands/ext-dev-usage.js +3 -8
  7. package/lib/commands/ext-install.js +1 -0
  8. package/lib/commands/ext-uninstall.js +1 -0
  9. package/lib/commands/ext-update.js +1 -0
  10. package/lib/commands/functions-secrets-access.js +17 -0
  11. package/lib/commands/functions-secrets-destroy.js +40 -0
  12. package/lib/commands/functions-secrets-get.js +21 -0
  13. package/lib/commands/functions-secrets-prune.js +50 -0
  14. package/lib/commands/functions-secrets-set.js +46 -0
  15. package/lib/commands/index.js +7 -3
  16. package/lib/commands/login.js +1 -1
  17. package/lib/database/metadata.js +16 -24
  18. package/lib/deploy/functions/backend.js +11 -1
  19. package/lib/deploy/functions/ensure.js +112 -0
  20. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  21. package/lib/deploy/functions/prepare.js +13 -19
  22. package/lib/deploy/functions/release/fabricator.js +4 -1
  23. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
  24. package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
  25. package/lib/deploy/functions/validate.js +83 -1
  26. package/lib/deploy/hosting/convertConfig.js +45 -24
  27. package/lib/deploy/hosting/prepare.js +1 -1
  28. package/lib/emulator/controller.js +3 -1
  29. package/lib/emulator/emulatorLogger.js +7 -0
  30. package/lib/emulator/functionsEmulator.js +113 -79
  31. package/lib/emulator/functionsEmulatorRuntime.js +100 -83
  32. package/lib/emulator/functionsEmulatorShared.js +51 -1
  33. package/lib/emulator/functionsEmulatorShell.js +1 -2
  34. package/lib/emulator/functionsRuntimeWorker.js +1 -1
  35. package/lib/emulator/storage/apis/gcloud.js +2 -2
  36. package/lib/emulator/storage/files.js +8 -3
  37. package/lib/extensions/askUserForParam.js +1 -1
  38. package/lib/extensions/diagnose.js +56 -0
  39. package/lib/extensions/extensionsApi.js +0 -1
  40. package/lib/extensions/extensionsHelper.js +10 -17
  41. package/lib/extensions/resolveSource.js +1 -53
  42. package/lib/extensions/secretsUtils.js +1 -1
  43. package/lib/extensions/updateHelper.js +0 -14
  44. package/lib/extensions/utils.js +4 -2
  45. package/lib/functions/env.js +5 -7
  46. package/lib/functions/secrets.js +112 -0
  47. package/lib/gcp/cloudbilling.js +8 -19
  48. package/lib/gcp/cloudfunctions.js +24 -48
  49. package/lib/gcp/cloudlogging.js +8 -11
  50. package/lib/gcp/cloudmonitoring.js +8 -5
  51. package/lib/gcp/cloudscheduler.js +7 -18
  52. package/lib/gcp/firedata.js +5 -4
  53. package/lib/gcp/firestore.js +5 -5
  54. package/lib/gcp/iam.js +18 -33
  55. package/lib/gcp/resourceManager.js +8 -13
  56. package/lib/gcp/runtimeconfig.js +31 -53
  57. package/lib/gcp/secretManager.js +137 -77
  58. package/lib/gcp/storage.js +25 -29
  59. package/lib/previews.js +1 -1
  60. package/lib/serve/functions.js +2 -2
  61. package/lib/utils.js +6 -1
  62. package/npm-shrinkwrap.json +962 -987
  63. package/package.json +5 -3
  64. package/schema/firebase-config.json +387 -12
  65. package/templates/init/hosting/index.html +1 -1
package/lib/api.js CHANGED
@@ -67,6 +67,7 @@ var _appendQueryData = function (path, data) {
67
67
  return path;
68
68
  };
69
69
  var api = {
70
+ authProxyOrigin: utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools"),
70
71
  clientId: utils.envOverride("FIREBASE_CLIENT_ID", "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com"),
71
72
  clientSecret: utils.envOverride("FIREBASE_CLIENT_SECRET", "j9iVZfS8kkCEFUPaAeJV0sAi"),
72
73
  cloudbillingOrigin: utils.envOverride("FIREBASE_CLOUDBILLING_URL", "https://cloudbilling.googleapis.com"),
package/lib/apiv2.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Client = exports.setAccessToken = exports.setRefreshToken = void 0;
4
- const stream_1 = require("stream");
5
4
  const url_1 = require("url");
5
+ const stream_1 = require("stream");
6
6
  const ProxyAgent = require("proxy-agent");
7
+ const retry = require("retry");
7
8
  const abort_controller_1 = require("abort-controller");
8
9
  const node_fetch_1 = require("node-fetch");
9
10
  const util_1 = require("util");
@@ -151,6 +152,7 @@ class Client {
151
152
  return `${this.opts.urlPrefix}${versionPath}${options.path}`;
152
153
  }
153
154
  async doRequest(options) {
155
+ var _a;
154
156
  if (!options.path.startsWith("/")) {
155
157
  options.path = "/" + options.path;
156
158
  }
@@ -199,53 +201,95 @@ class Client {
199
201
  else if (options.body !== undefined) {
200
202
  fetchOptions.body = JSON.stringify(options.body);
201
203
  }
202
- this.logRequest(options);
203
- let res;
204
- try {
205
- res = await (0, node_fetch_1.default)(fetchURL, fetchOptions);
206
- }
207
- catch (thrown) {
208
- const err = thrown instanceof Error ? thrown : new Error(thrown);
209
- const isAbortError = err.name.includes("AbortError");
210
- if (isAbortError) {
211
- throw new error_1.FirebaseError(`Timeout reached making request to ${fetchURL}`, { original: err });
212
- }
213
- throw new error_1.FirebaseError(`Failed to make request to ${fetchURL}`, { original: err });
214
- }
215
- finally {
216
- if (reqTimeout) {
217
- clearTimeout(reqTimeout);
218
- }
219
- }
220
- let body;
221
- if (options.responseType === "json") {
222
- const text = await res.text();
223
- if (!text.length) {
224
- body = undefined;
225
- }
226
- else {
227
- body = JSON.parse(text);
228
- }
229
- }
230
- else if (options.responseType === "stream") {
231
- body = res.body;
232
- }
233
- else {
234
- throw new error_1.FirebaseError(`Unable to interpret response. Please set responseType.`, {
235
- exit: 2,
236
- });
237
- }
238
- this.logResponse(res, body, options);
239
- if (res.status >= 400) {
240
- if (!options.resolveOnHTTPError) {
241
- throw responseToError({ statusCode: res.status }, body);
242
- }
243
- }
244
- return {
245
- status: res.status,
246
- response: res,
247
- body,
204
+ const operationOptions = {
205
+ retries: ((_a = options.retryCodes) === null || _a === void 0 ? void 0 : _a.length) ? 1 : 2,
206
+ minTimeout: 1 * 1000,
207
+ maxTimeout: 5 * 1000,
248
208
  };
209
+ if (typeof options.retries === "number") {
210
+ operationOptions.retries = options.retries;
211
+ }
212
+ if (typeof options.retryMinTimeout === "number") {
213
+ operationOptions.minTimeout = options.retryMinTimeout;
214
+ }
215
+ if (typeof options.retryMaxTimeout === "number") {
216
+ operationOptions.maxTimeout = options.retryMaxTimeout;
217
+ }
218
+ const operation = retry.operation(operationOptions);
219
+ return await new Promise((resolve, reject) => {
220
+ operation.attempt(async (currentAttempt) => {
221
+ var _a;
222
+ let res;
223
+ let body;
224
+ try {
225
+ if (currentAttempt > 1) {
226
+ logger_1.logger.debug(`*** [apiv2] Attempting the request again. Attempt number ${currentAttempt}`);
227
+ }
228
+ this.logRequest(options);
229
+ try {
230
+ res = await (0, node_fetch_1.default)(fetchURL, fetchOptions);
231
+ }
232
+ catch (thrown) {
233
+ const err = thrown instanceof Error ? thrown : new Error(thrown);
234
+ const isAbortError = err.name.includes("AbortError");
235
+ if (isAbortError) {
236
+ throw new error_1.FirebaseError(`Timeout reached making request to ${fetchURL}`, {
237
+ original: err,
238
+ });
239
+ }
240
+ throw new error_1.FirebaseError(`Failed to make request to ${fetchURL}`, { original: err });
241
+ }
242
+ finally {
243
+ if (reqTimeout) {
244
+ clearTimeout(reqTimeout);
245
+ }
246
+ }
247
+ if (options.responseType === "json") {
248
+ const text = await res.text();
249
+ if (!text.length) {
250
+ body = undefined;
251
+ }
252
+ else {
253
+ try {
254
+ body = JSON.parse(text);
255
+ }
256
+ catch (err) {
257
+ this.logResponse(res, text, options);
258
+ throw new error_1.FirebaseError(`Unable to parse JSON: ${err}`);
259
+ }
260
+ }
261
+ }
262
+ else if (options.responseType === "stream") {
263
+ body = res.body;
264
+ }
265
+ else {
266
+ throw new error_1.FirebaseError(`Unable to interpret response. Please set responseType.`, {
267
+ exit: 2,
268
+ });
269
+ }
270
+ }
271
+ catch (err) {
272
+ return err instanceof error_1.FirebaseError ? reject(err) : reject(new error_1.FirebaseError(`${err}`));
273
+ }
274
+ this.logResponse(res, body, options);
275
+ if (res.status >= 400) {
276
+ if ((_a = options.retryCodes) === null || _a === void 0 ? void 0 : _a.includes(res.status)) {
277
+ const err = responseToError({ statusCode: res.status }, body) || undefined;
278
+ if (operation.retry(err)) {
279
+ return;
280
+ }
281
+ }
282
+ if (!options.resolveOnHTTPError) {
283
+ return reject(responseToError({ statusCode: res.status }, body));
284
+ }
285
+ }
286
+ resolve({
287
+ status: res.status,
288
+ response: res,
289
+ body,
290
+ });
291
+ });
292
+ });
249
293
  }
250
294
  logRequest(options) {
251
295
  var _a, _b;
@@ -282,7 +326,7 @@ class Client {
282
326
  }
283
327
  exports.Client = Client;
284
328
  function isLocalInsecureRequest(urlPrefix) {
285
- const u = (0, url_1.parse)(urlPrefix);
329
+ const u = new url_1.URL(urlPrefix);
286
330
  return u.protocol === "http:";
287
331
  }
288
332
  function bodyToString(body) {
@@ -1,18 +1,17 @@
1
1
  "use strict";
2
- const _ = require("lodash");
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.archiveDirectory = void 0;
3
4
  const archiver = require("archiver");
4
5
  const filesize = require("filesize");
5
6
  const fs = require("fs");
6
7
  const path = require("path");
7
8
  const tar = require("tar");
8
9
  const tmp = require("tmp");
9
- const { listFiles } = require("./listFiles");
10
- const { FirebaseError } = require("./error");
10
+ const error_1 = require("./error");
11
+ const listFiles_1 = require("./listFiles");
12
+ const logger_1 = require("./logger");
11
13
  const fsAsync = require("./fsAsync");
12
- const { logger } = require("./logger");
13
- const utils = require("./utils");
14
- const archiveDirectory = (sourceDirectory, options) => {
15
- options = options || {};
14
+ async function archiveDirectory(sourceDirectory, options = {}) {
16
15
  let postfix = ".tar.gz";
17
16
  if (options.type === "zip") {
18
17
  postfix = ".zip";
@@ -26,105 +25,96 @@ const archiveDirectory = (sourceDirectory, options) => {
26
25
  }
27
26
  let makeArchive;
28
27
  if (options.type === "zip") {
29
- makeArchive = _zipDirectory(sourceDirectory, tempFile, options);
28
+ makeArchive = zipDirectory(sourceDirectory, tempFile, options);
30
29
  }
31
30
  else {
32
- makeArchive = _tarDirectory(sourceDirectory, tempFile, options);
31
+ makeArchive = tarDirectory(sourceDirectory, tempFile, options);
33
32
  }
34
- return makeArchive
35
- .then((archive) => {
36
- logger.debug(`Archived ${filesize(archive.size)} in ${sourceDirectory}.`);
33
+ try {
34
+ const archive = await makeArchive;
35
+ logger_1.logger.debug(`Archived ${filesize(archive.size)} in ${sourceDirectory}.`);
37
36
  return archive;
38
- })
39
- .catch((err) => {
40
- if (err instanceof FirebaseError) {
37
+ }
38
+ catch (err) {
39
+ if (err instanceof error_1.FirebaseError) {
41
40
  throw err;
42
41
  }
43
- return utils.reject("Failed to create archive.", {
44
- original: err,
45
- });
46
- });
47
- };
48
- const _tarDirectory = (sourceDirectory, tempFile, options) => {
49
- const allFiles = listFiles(sourceDirectory, options.ignore);
42
+ throw new error_1.FirebaseError("Failed to create archive.", { original: err });
43
+ }
44
+ }
45
+ exports.archiveDirectory = archiveDirectory;
46
+ async function tarDirectory(sourceDirectory, tempFile, options) {
47
+ const allFiles = (0, listFiles_1.listFiles)(sourceDirectory, options.ignore);
50
48
  try {
51
49
  fs.statSync(sourceDirectory);
52
50
  }
53
51
  catch (err) {
54
52
  if (err.code === "ENOENT") {
55
- return utils.reject(`Could not read directory "${sourceDirectory}"`);
53
+ throw new error_1.FirebaseError(`Could not read directory "${sourceDirectory}"`);
56
54
  }
57
55
  throw err;
58
56
  }
59
57
  if (!allFiles.length) {
60
- return utils.reject(`Cannot create a tar archive with 0 files from directory "${sourceDirectory}"`);
58
+ throw new error_1.FirebaseError(`Cannot create a tar archive with 0 files from directory "${sourceDirectory}"`);
61
59
  }
62
- return tar
63
- .create({
60
+ await tar.create({
64
61
  gzip: true,
65
62
  file: tempFile.name,
66
63
  cwd: sourceDirectory,
67
64
  follow: true,
68
65
  noDirRecurse: true,
69
66
  portable: true,
70
- }, allFiles)
71
- .then(() => {
72
- const stats = fs.statSync(tempFile.name);
73
- return {
74
- file: tempFile.name,
75
- stream: fs.createReadStream(tempFile.name),
76
- manifest: allFiles,
77
- size: stats.size,
78
- source: sourceDirectory,
79
- };
80
- });
81
- };
82
- const _zipDirectory = (sourceDirectory, tempFile, options) => {
67
+ }, allFiles);
68
+ const stats = fs.statSync(tempFile.name);
69
+ return {
70
+ file: tempFile.name,
71
+ stream: fs.createReadStream(tempFile.name),
72
+ manifest: allFiles,
73
+ size: stats.size,
74
+ source: sourceDirectory,
75
+ };
76
+ }
77
+ async function zipDirectory(sourceDirectory, tempFile, options) {
83
78
  const archiveFileStream = fs.createWriteStream(tempFile.name, {
84
79
  flags: "w",
85
80
  encoding: "binary",
86
81
  });
87
82
  const archive = archiver("zip");
88
- const archiveDone = _pipeAsync(archive, archiveFileStream);
83
+ const archiveDone = pipeAsync(archive, archiveFileStream);
89
84
  const allFiles = [];
90
- return fsAsync
91
- .readdirRecursive({ path: sourceDirectory, ignore: options.ignore })
92
- .catch((err) => {
85
+ let files;
86
+ try {
87
+ files = await fsAsync.readdirRecursive({ path: sourceDirectory, ignore: options.ignore });
88
+ }
89
+ catch (err) {
93
90
  if (err.code === "ENOENT") {
94
- return utils.reject(`Could not read directory "${sourceDirectory}"`, { original: err });
91
+ throw new error_1.FirebaseError(`Could not read directory "${sourceDirectory}"`, { original: err });
95
92
  }
96
93
  throw err;
97
- })
98
- .then(function (files) {
99
- _.forEach(files, function (file) {
100
- const name = path.relative(sourceDirectory, file.name);
101
- allFiles.push(name);
102
- archive.file(file.name, {
103
- name,
104
- mode: file.mode,
105
- });
94
+ }
95
+ for (const file of files) {
96
+ const name = path.relative(sourceDirectory, file.name);
97
+ allFiles.push(name);
98
+ archive.file(file.name, {
99
+ name,
100
+ mode: file.mode,
106
101
  });
107
- archive.finalize();
108
- return archiveDone;
109
- })
110
- .then(() => {
111
- const stats = fs.statSync(tempFile.name);
112
- return {
113
- file: tempFile.name,
114
- stream: fs.createReadStream(tempFile.name),
115
- manifest: allFiles,
116
- size: stats.size,
117
- source: sourceDirectory,
118
- };
119
- });
120
- };
121
- const _pipeAsync = function (from, to) {
122
- return new Promise(function (resolve, reject) {
102
+ }
103
+ void archive.finalize();
104
+ await archiveDone;
105
+ const stats = fs.statSync(tempFile.name);
106
+ return {
107
+ file: tempFile.name,
108
+ stream: fs.createReadStream(tempFile.name),
109
+ manifest: allFiles,
110
+ size: stats.size,
111
+ source: sourceDirectory,
112
+ };
113
+ }
114
+ async function pipeAsync(from, to) {
115
+ return new Promise((resolve, reject) => {
123
116
  to.on("finish", resolve);
124
117
  to.on("error", reject);
125
118
  from.pipe(to);
126
119
  });
127
- };
128
- module.exports = {
129
- archiveDirectory,
130
- };
120
+ }
package/lib/auth.js CHANGED
@@ -19,6 +19,10 @@ const logger_1 = require("./logger");
19
19
  const prompt_1 = require("./prompt");
20
20
  const scopes = require("./scopes");
21
21
  const defaultCredentials_1 = require("./defaultCredentials");
22
+ const uuid_1 = require("uuid");
23
+ const crypto_1 = require("crypto");
24
+ const cli_color_1 = require("cli-color");
25
+ const track_1 = require("./track");
22
26
  portfinder.basePort = 9005;
23
27
  function getGlobalDefaultAccount() {
24
28
  const user = configstore_1.configstore.get("user");
@@ -182,19 +186,23 @@ function getLoginUrl(callbackUrl, userHint) {
182
186
  login_hint: userHint,
183
187
  }));
184
188
  }
185
- async function getTokensFromAuthorizationCode(code, callbackUrl) {
189
+ async function getTokensFromAuthorizationCode(code, callbackUrl, verifier) {
186
190
  var _a, _b;
187
191
  let res;
192
+ const params = {
193
+ code: code,
194
+ client_id: api.clientId,
195
+ client_secret: api.clientSecret,
196
+ redirect_uri: callbackUrl,
197
+ grant_type: "authorization_code",
198
+ };
199
+ if (verifier) {
200
+ params["code_verifier"] = verifier;
201
+ }
188
202
  try {
189
203
  res = await api.request("POST", "/o/oauth2/token", {
190
204
  origin: api.authOrigin,
191
- form: {
192
- code: code,
193
- client_id: api.clientId,
194
- client_secret: api.clientSecret,
195
- redirect_uri: callbackUrl,
196
- grant_type: "authorization_code",
197
- },
205
+ form: params,
198
206
  });
199
207
  }
200
208
  catch (err) {
@@ -248,31 +256,58 @@ async function respondWithFile(req, res, statusCode, filename) {
248
256
  res.end(response);
249
257
  req.socket.destroy();
250
258
  }
251
- async function loginWithoutLocalhost(userHint) {
252
- const callbackUrl = getCallbackUrl();
253
- const authUrl = getLoginUrl(callbackUrl, userHint);
259
+ function urlsafeBase64(base64string) {
260
+ return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_");
261
+ }
262
+ async function loginRemotely(userHint) {
263
+ var _a;
264
+ const authProxyClient = new apiv2.Client({
265
+ urlPrefix: api.authProxyOrigin,
266
+ auth: false,
267
+ });
268
+ const sessionId = (0, uuid_1.v4)();
269
+ const codeVerifier = (0, crypto_1.randomBytes)(32).toString("hex");
270
+ const codeChallenge = urlsafeBase64((0, crypto_1.createHash)("sha256").update(codeVerifier).digest("base64"));
271
+ const attestToken = (_a = (await authProxyClient.post("/attest", {
272
+ session_id: sessionId,
273
+ })).body) === null || _a === void 0 ? void 0 : _a.token;
274
+ const loginUrl = `${api.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`;
275
+ logger_1.logger.info();
276
+ logger_1.logger.info("To sign in to the Firebase CLI:");
254
277
  logger_1.logger.info();
255
- logger_1.logger.info("Visit this URL on any device to log in:");
256
- logger_1.logger.info(clc.bold.underline(authUrl));
278
+ logger_1.logger.info("1. Take note of your session ID:");
279
+ logger_1.logger.info();
280
+ logger_1.logger.info(` ${(0, cli_color_1.bold)(sessionId.substring(0, 5).toUpperCase())}`);
281
+ logger_1.logger.info();
282
+ logger_1.logger.info("2. Visit the URL below on any device and follow the instructions to get your code:");
283
+ logger_1.logger.info();
284
+ logger_1.logger.info(` ${loginUrl}`);
285
+ logger_1.logger.info();
286
+ logger_1.logger.info("3. Paste or enter the authorization code below once you have it:");
257
287
  logger_1.logger.info();
258
- open(authUrl);
259
288
  const code = await (0, prompt_1.promptOnce)({
260
289
  type: "input",
261
- name: "code",
262
- message: "Paste authorization code here:",
290
+ message: "Enter authorization code:",
263
291
  });
264
- const tokens = await getTokensFromAuthorizationCode(code, callbackUrl);
265
- return {
266
- user: jwt.decode(tokens.id_token),
267
- tokens: tokens,
268
- scopes: SCOPES,
269
- };
292
+ try {
293
+ const tokens = await getTokensFromAuthorizationCode(code, `${api.authProxyOrigin}/complete`, codeVerifier);
294
+ (0, track_1.track)("login", "google_remote");
295
+ return {
296
+ user: jwt.decode(tokens.id_token),
297
+ tokens: tokens,
298
+ scopes: SCOPES,
299
+ };
300
+ }
301
+ catch (e) {
302
+ throw new error_1.FirebaseError("Unable to authenticate using the provided code. Please try again.");
303
+ }
270
304
  }
271
305
  async function loginWithLocalhostGoogle(port, userHint) {
272
306
  const callbackUrl = getCallbackUrl(port);
273
307
  const authUrl = getLoginUrl(callbackUrl, userHint);
274
308
  const successTemplate = "../templates/loginSuccess.html";
275
309
  const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokensFromAuthorizationCode);
310
+ (0, track_1.track)("login", "google_localhost");
276
311
  return {
277
312
  user: jwt.decode(tokens.id_token),
278
313
  tokens: tokens,
@@ -283,7 +318,9 @@ async function loginWithLocalhostGitHub(port) {
283
318
  const callbackUrl = getCallbackUrl(port);
284
319
  const authUrl = getGithubLoginUrl(callbackUrl);
285
320
  const successTemplate = "../templates/loginSuccessGithub.html";
286
- return loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
321
+ const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
322
+ (0, track_1.track)("login", "google_localhost");
323
+ return tokens;
287
324
  }
288
325
  async function loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokens) {
289
326
  return new Promise((resolve, reject) => {
@@ -331,10 +368,10 @@ async function loginGoogle(localhost, userHint) {
331
368
  return await loginWithLocalhostGoogle(port, userHint);
332
369
  }
333
370
  catch (_a) {
334
- return await loginWithoutLocalhost(userHint);
371
+ return await loginRemotely(userHint);
335
372
  }
336
373
  }
337
- return await loginWithoutLocalhost(userHint);
374
+ return await loginRemotely(userHint);
338
375
  }
339
376
  exports.loginGoogle = loginGoogle;
340
377
  async function loginGithub() {
@@ -27,6 +27,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
27
27
  "firebaseextensions.instances.get",
28
28
  ])
29
29
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
30
+ .before(extensionsHelper_1.diagnoseAndFixProject)
30
31
  .action(async (instanceId, options) => {
31
32
  const spinner = ora(`Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
32
33
  try {
@@ -58,14 +58,14 @@ module.exports = new command_1.Command("ext:dev:usage <publisherId>")
58
58
  }
59
59
  const profile = await (0, extensionsApi_1.getPublisherProfile)("-", publisherId);
60
60
  const projectNumber = (0, extensionsHelper_1.getPublisherProjectFromName)(profile.name);
61
- const past30d = new Date();
62
- past30d.setDate(past30d.getDate() - 30);
61
+ const past45d = new Date();
62
+ past45d.setDate(past45d.getDate() - 45);
63
63
  const query = {
64
64
  filter: `metric.type="firebaseextensions.googleapis.com/extension/version/active_instances" ` +
65
65
  `resource.type="firebaseextensions.googleapis.com/ExtensionVersion" ` +
66
66
  `resource.labels.extension="${extensionName}"`,
67
67
  "interval.endTime": new Date().toJSON(),
68
- "interval.startTime": past30d.toJSON(),
68
+ "interval.startTime": past45d.toJSON(),
69
69
  view: cloudmonitoring_1.TimeSeriesView.FULL,
70
70
  "aggregation.alignmentPeriod": (60 * 60 * 24).toString() + "s",
71
71
  "aggregation.perSeriesAligner": cloudmonitoring_1.Aligner.ALIGN_MAX,
@@ -95,15 +95,10 @@ module.exports = new command_1.Command("ext:dev:usage <publisherId>")
95
95
  });
96
96
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `showing usage stats for ${clc.bold(extensionName)}:`);
97
97
  logger_1.logger.info(table.toString());
98
- const link = await buildCloudMonitoringLink({
99
- projectNumber: projectNumber,
100
- extensionName,
101
- });
102
98
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `How to read this table:`);
103
99
  logger_1.logger.info(`* Due to privacy considerations, numbers are reported as ranges.`);
104
100
  logger_1.logger.info(`* In the absence of significant changes, we will render a '-' symbol.`);
105
101
  logger_1.logger.info(`* You will need more than 10 installs over a period of more than 28 days to render sufficient data.`);
106
- logger_1.logger.info(`For more detail, visit: ${link}`);
107
102
  });
108
103
  async function buildCloudMonitoringLink(args) {
109
104
  const pageState = {
@@ -201,6 +201,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
201
201
  .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.create"])
202
202
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
203
203
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
204
+ .before(extensionsHelper_1.diagnoseAndFixProject)
204
205
  .action(async (extensionName, options) => {
205
206
  const projectId = (0, projectUtils_1.needProjectId)(options);
206
207
  const paramsEnvPath = options.params;
@@ -33,6 +33,7 @@ exports.default = new command_1.Command("ext:uninstall <extensionInstanceId>")
33
33
  .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.delete"])
34
34
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
35
35
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
36
+ .before(extensionsHelper_1.diagnoseAndFixProject)
36
37
  .action(async (instanceId, options) => {
37
38
  const projectId = (0, projectUtils_1.needProjectId)(options);
38
39
  let instance;
@@ -44,6 +44,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
44
44
  ])
45
45
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
46
46
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
47
+ .before(extensionsHelper_1.diagnoseAndFixProject)
47
48
  .withForce()
48
49
  .option("--params <paramsFile>", "name of params variables file with .env format.")
49
50
  .action(async (instanceId, updateSource, options) => {
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("../command");
4
+ const logger_1 = require("../logger");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const secretManager_1 = require("../gcp/secretManager");
7
+ exports.default = new command_1.Command("functions:secrets:access <KEY>[@version]")
8
+ .description("Access secret value given secret and its version. Defaults to accessing the latest version.")
9
+ .action(async (key, options) => {
10
+ const projectId = (0, projectUtils_1.needProjectId)(options);
11
+ let [name, version] = key.split("@");
12
+ if (!version) {
13
+ version = "latest";
14
+ }
15
+ const value = await (0, secretManager_1.accessSecretVersion)(projectId, name, version);
16
+ logger_1.logger.info(value);
17
+ });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("../command");
4
+ const logger_1 = require("../logger");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const secretManager_1 = require("../gcp/secretManager");
7
+ const prompt_1 = require("../prompt");
8
+ const secrets = require("../functions/secrets");
9
+ exports.default = new command_1.Command("functions:secrets:destroy <KEY>[@version]")
10
+ .description("Destroy a secret. Defaults to destroying the latest version.")
11
+ .withForce("Destroys a secret without confirmation.")
12
+ .action(async (key, options) => {
13
+ const projectId = (0, projectUtils_1.needProjectId)(options);
14
+ let [name, version] = key.split("@");
15
+ if (!version) {
16
+ version = "latest";
17
+ }
18
+ const sv = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
19
+ if (!options.force) {
20
+ const confirm = await (0, prompt_1.promptOnce)({
21
+ name: "destroy",
22
+ type: "confirm",
23
+ default: true,
24
+ message: `Are you sure you want to destroy ${sv.secret.name}@${sv.versionId}`,
25
+ }, options);
26
+ if (!confirm) {
27
+ return;
28
+ }
29
+ }
30
+ await (0, secretManager_1.destroySecretVersion)(projectId, name, version);
31
+ logger_1.logger.info(`Destroyed secret version ${name}@${sv.versionId}`);
32
+ const secret = await (0, secretManager_1.getSecret)(projectId, name);
33
+ if (secrets.isFirebaseManaged(secret)) {
34
+ const versions = await (0, secretManager_1.listSecretVersions)(projectId, name);
35
+ if (versions.filter((v) => v.state === "ENABLED").length === 0) {
36
+ logger_1.logger.info(`No active secret versions left. Destroying secret ${name}`);
37
+ await (0, secretManager_1.deleteSecret)(projectId, name);
38
+ }
39
+ }
40
+ });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Table = require("cli-table");
4
+ const command_1 = require("../command");
5
+ const logger_1 = require("../logger");
6
+ const projectUtils_1 = require("../projectUtils");
7
+ const secretManager_1 = require("../gcp/secretManager");
8
+ exports.default = new command_1.Command("functions:secrets:get <KEY>")
9
+ .description("Get metadata for secret and its versions")
10
+ .action(async (key, options) => {
11
+ const projectId = (0, projectUtils_1.needProjectId)(options);
12
+ const versions = await (0, secretManager_1.listSecretVersions)(projectId, key);
13
+ const table = new Table({
14
+ head: ["Version", "State"],
15
+ style: { head: ["yellow"] },
16
+ });
17
+ for (const version of versions) {
18
+ table.push([version.versionId, version.state]);
19
+ }
20
+ logger_1.logger.info(table.toString());
21
+ });