firebase-tools 10.2.2 → 10.4.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 (76) hide show
  1. package/lib/commands/deploy.js +1 -1
  2. package/lib/commands/experimental-functions-shell.js +1 -1
  3. package/lib/commands/ext-configure.js +68 -7
  4. package/lib/commands/ext-export.js +10 -9
  5. package/lib/commands/ext-install.js +73 -9
  6. package/lib/commands/ext-uninstall.js +9 -0
  7. package/lib/commands/ext-update.js +58 -3
  8. package/lib/commands/functions-config-export.js +2 -2
  9. package/lib/commands/functions-shell.js +1 -1
  10. package/lib/commands/hosting-channel-create.js +2 -2
  11. package/lib/commands/hosting-channel-delete.js +2 -2
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/commands/hosting-channel-list.js +2 -2
  14. package/lib/commands/hosting-channel-open.js +2 -2
  15. package/lib/commands/hosting-sites-delete.js +2 -2
  16. package/lib/commands/serve.js +1 -1
  17. package/lib/commands/target-apply.js +2 -2
  18. package/lib/commands/target-clear.js +2 -2
  19. package/lib/commands/target-remove.js +2 -2
  20. package/lib/commands/target.js +2 -2
  21. package/lib/config.js +9 -3
  22. package/lib/deploy/extensions/planner.js +15 -9
  23. package/lib/deploy/functions/backend.js +10 -1
  24. package/lib/deploy/functions/checkIam.js +4 -4
  25. package/lib/deploy/functions/prepare.js +2 -1
  26. package/lib/deploy/functions/release/fabricator.js +4 -4
  27. package/lib/deploy/functions/release/planner.js +34 -20
  28. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
  29. package/lib/deploy/functions/runtimes/node/index.js +27 -0
  30. package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
  31. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  32. package/lib/deploy/functions/services/index.js +9 -1
  33. package/lib/deploy/functions/services/storage.js +10 -4
  34. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  35. package/lib/emulator/auth/apiSpec.js +37 -0
  36. package/lib/emulator/commandUtils.js +2 -2
  37. package/lib/emulator/constants.js +1 -0
  38. package/lib/emulator/controller.js +9 -7
  39. package/lib/emulator/extensions/validation.js +37 -2
  40. package/lib/emulator/extensionsEmulator.js +47 -9
  41. package/lib/emulator/functionsEmulator.js +17 -12
  42. package/lib/emulator/functionsEmulatorShared.js +34 -11
  43. package/lib/emulator/storage/apis/firebase.js +316 -341
  44. package/lib/emulator/storage/apis/gcloud.js +238 -113
  45. package/lib/emulator/storage/crc.js +5 -1
  46. package/lib/emulator/storage/errors.js +9 -0
  47. package/lib/emulator/storage/files.js +161 -304
  48. package/lib/emulator/storage/index.js +25 -74
  49. package/lib/emulator/storage/metadata.js +63 -49
  50. package/lib/emulator/storage/multipart.js +62 -0
  51. package/lib/emulator/storage/persistence.js +78 -0
  52. package/lib/emulator/storage/rules/config.js +34 -0
  53. package/lib/emulator/storage/rules/manager.js +98 -0
  54. package/lib/emulator/storage/rules/runtime.js +4 -0
  55. package/lib/emulator/storage/rules/utils.js +48 -0
  56. package/lib/emulator/storage/server.js +2 -2
  57. package/lib/emulator/storage/upload.js +106 -0
  58. package/lib/extensions/askUserForParam.js +77 -28
  59. package/lib/extensions/emulator/optionsHelper.js +35 -3
  60. package/lib/extensions/extensionsHelper.js +19 -10
  61. package/lib/extensions/manifest.js +142 -14
  62. package/lib/extensions/paramHelper.js +32 -9
  63. package/lib/fsutils.js +14 -1
  64. package/lib/functions/env.js +4 -6
  65. package/lib/functions/events/v2.js +11 -0
  66. package/lib/gcp/cloudfunctions.js +20 -7
  67. package/lib/gcp/cloudfunctionsv2.js +30 -12
  68. package/lib/gcp/resourceManager.js +4 -4
  69. package/lib/requireConfig.js +11 -9
  70. package/lib/serve/functions.js +2 -1
  71. package/lib/utils.js +14 -1
  72. package/npm-shrinkwrap.json +2 -2
  73. package/package.json +1 -1
  74. package/lib/deploy/extensions/params.js +0 -42
  75. package/lib/deploy/functions/eventTypes.js +0 -10
  76. package/lib/prepareUpload.js +0 -44
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadService = exports.NotCancellableError = exports.UploadNotActiveError = exports.UploadStatus = exports.UploadType = void 0;
4
+ const uuid_1 = require("uuid");
5
+ const errors_1 = require("./errors");
6
+ var UploadType;
7
+ (function (UploadType) {
8
+ UploadType[UploadType["MULTIPART"] = 0] = "MULTIPART";
9
+ UploadType[UploadType["RESUMABLE"] = 1] = "RESUMABLE";
10
+ })(UploadType = exports.UploadType || (exports.UploadType = {}));
11
+ var UploadStatus;
12
+ (function (UploadStatus) {
13
+ UploadStatus[UploadStatus["ACTIVE"] = 0] = "ACTIVE";
14
+ UploadStatus[UploadStatus["CANCELLED"] = 1] = "CANCELLED";
15
+ UploadStatus[UploadStatus["FINISHED"] = 2] = "FINISHED";
16
+ })(UploadStatus = exports.UploadStatus || (exports.UploadStatus = {}));
17
+ class UploadNotActiveError extends Error {
18
+ }
19
+ exports.UploadNotActiveError = UploadNotActiveError;
20
+ class NotCancellableError extends Error {
21
+ }
22
+ exports.NotCancellableError = NotCancellableError;
23
+ class UploadService {
24
+ constructor(_persistence) {
25
+ this._persistence = _persistence;
26
+ this.reset();
27
+ }
28
+ reset() {
29
+ this._uploads = new Map();
30
+ }
31
+ multipartUpload(request) {
32
+ const upload = this.startMultipartUpload(request, request.dataRaw.byteLength);
33
+ this._persistence.deleteFile(upload.path, true);
34
+ this._persistence.appendBytes(upload.path, request.dataRaw);
35
+ return upload;
36
+ }
37
+ startMultipartUpload(request, sizeInBytes) {
38
+ const id = (0, uuid_1.v4)();
39
+ const upload = {
40
+ id: (0, uuid_1.v4)(),
41
+ bucketId: request.bucketId,
42
+ objectId: request.objectId,
43
+ type: UploadType.MULTIPART,
44
+ path: this.getStagingFileName(id, request.bucketId, request.objectId),
45
+ status: UploadStatus.FINISHED,
46
+ metadata: JSON.parse(request.metadataRaw),
47
+ size: sizeInBytes,
48
+ authorization: request.authorization,
49
+ };
50
+ this._uploads.set(upload.id, upload);
51
+ return upload;
52
+ }
53
+ startResumableUpload(request) {
54
+ const id = (0, uuid_1.v4)();
55
+ const upload = {
56
+ id: id,
57
+ bucketId: request.bucketId,
58
+ objectId: request.objectId,
59
+ type: UploadType.RESUMABLE,
60
+ path: this.getStagingFileName(id, request.bucketId, request.objectId),
61
+ status: UploadStatus.ACTIVE,
62
+ metadata: JSON.parse(request.metadataRaw),
63
+ size: 0,
64
+ authorization: request.authorization,
65
+ };
66
+ this._uploads.set(upload.id, upload);
67
+ this._persistence.deleteFile(upload.path, true);
68
+ return upload;
69
+ }
70
+ continueResumableUpload(uploadId, dataRaw) {
71
+ const upload = this.getResumableUpload(uploadId);
72
+ if (upload.status !== UploadStatus.ACTIVE) {
73
+ throw new UploadNotActiveError();
74
+ }
75
+ this._persistence.appendBytes(upload.path, dataRaw);
76
+ upload.size += dataRaw.byteLength;
77
+ return upload;
78
+ }
79
+ getResumableUpload(uploadId) {
80
+ const upload = this._uploads.get(uploadId);
81
+ if (!upload || upload.type !== UploadType.RESUMABLE) {
82
+ throw new errors_1.NotFoundError();
83
+ }
84
+ return upload;
85
+ }
86
+ cancelResumableUpload(uploadId) {
87
+ const upload = this.getResumableUpload(uploadId);
88
+ if (upload.status === UploadStatus.FINISHED) {
89
+ throw new NotCancellableError();
90
+ }
91
+ upload.status = UploadStatus.CANCELLED;
92
+ return upload;
93
+ }
94
+ finalizeResumableUpload(uploadId) {
95
+ const upload = this.getResumableUpload(uploadId);
96
+ if (upload.status === UploadStatus.CANCELLED) {
97
+ throw new UploadNotActiveError();
98
+ }
99
+ upload.status = UploadStatus.FINISHED;
100
+ return upload;
101
+ }
102
+ getStagingFileName(uploadId, bucketId, objectId) {
103
+ return encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`);
104
+ }
105
+ }
106
+ exports.UploadService = UploadService;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ask = exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.checkResponse = void 0;
3
+ exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.ask = exports.checkResponse = exports.SecretLocation = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const { marked } = require("marked");
@@ -12,10 +12,15 @@ const utils_1 = require("./utils");
12
12
  const logger_1 = require("../logger");
13
13
  const prompt_1 = require("../prompt");
14
14
  const utils = require("../utils");
15
+ var SecretLocation;
16
+ (function (SecretLocation) {
17
+ SecretLocation[SecretLocation["CLOUD"] = 1] = "CLOUD";
18
+ SecretLocation[SecretLocation["LOCAL"] = 2] = "LOCAL";
19
+ })(SecretLocation = exports.SecretLocation || (exports.SecretLocation = {}));
15
20
  var SecretUpdateAction;
16
21
  (function (SecretUpdateAction) {
17
- SecretUpdateAction[SecretUpdateAction["LEAVE"] = 0] = "LEAVE";
18
- SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 1] = "SET_NEW";
22
+ SecretUpdateAction[SecretUpdateAction["LEAVE"] = 1] = "LEAVE";
23
+ SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 2] = "SET_NEW";
19
24
  })(SecretUpdateAction || (SecretUpdateAction = {}));
20
25
  function checkResponse(response, spec) {
21
26
  let valid = true;
@@ -55,9 +60,35 @@ function checkResponse(response, spec) {
55
60
  return valid;
56
61
  }
57
62
  exports.checkResponse = checkResponse;
58
- async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
63
+ async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
64
+ if (_.isEmpty(paramSpecs)) {
65
+ logger_1.logger.debug("No params were specified for this extension.");
66
+ return {};
67
+ }
68
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
69
+ const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
70
+ const result = {};
71
+ const promises = _.map(substituted, (paramSpec) => {
72
+ return async () => {
73
+ result[paramSpec.param] = await askForParam({
74
+ projectId,
75
+ instanceId,
76
+ paramSpec,
77
+ reconfiguring,
78
+ });
79
+ };
80
+ });
81
+ await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
82
+ logger_1.logger.info();
83
+ return result;
84
+ }
85
+ exports.ask = ask;
86
+ async function askForParam(args) {
87
+ const paramSpec = args.paramSpec;
59
88
  let valid = false;
60
89
  let response = "";
90
+ let responseForLocal;
91
+ let secretLocations = [];
61
92
  const description = paramSpec.description || "";
62
93
  const label = paramSpec.label.trim();
63
94
  logger_1.logger.info(`\n${clc.bold(label)}${clc.bold(paramSpec.required ? "" : " (Optional)")}: ${marked(description).trim()}`);
@@ -92,16 +123,23 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
92
123
  }
93
124
  },
94
125
  message: "Which options do you want enabled for this parameter? " +
95
- "Press Space to select, then Enter to confirm your choices. " +
96
- "You may select multiple options.",
126
+ "Press Space to select, then Enter to confirm your choices. ",
97
127
  choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
98
128
  });
99
129
  valid = checkResponse(response, paramSpec);
100
130
  break;
101
131
  case extensionsApi_1.ParamType.SECRET:
102
- response = reconfiguring
103
- ? await promptReconfigureSecret(projectId, instanceId, paramSpec)
104
- : await promptCreateSecret(projectId, instanceId, paramSpec);
132
+ while (!secretLocations.length) {
133
+ secretLocations = await promptSecretLocations();
134
+ }
135
+ if (secretLocations.includes(SecretLocation.CLOUD.toString())) {
136
+ response = args.reconfiguring
137
+ ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec)
138
+ : await promptCreateSecret(args.projectId, args.instanceId, paramSpec);
139
+ }
140
+ if (secretLocations.includes(SecretLocation.LOCAL.toString())) {
141
+ responseForLocal = await promptLocalSecret(args.instanceId, paramSpec);
142
+ }
105
143
  valid = true;
106
144
  break;
107
145
  default:
@@ -114,9 +152,38 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
114
152
  valid = checkResponse(response, paramSpec);
115
153
  }
116
154
  }
117
- return response;
155
+ return Object.assign({ baseValue: response }, (responseForLocal ? { local: responseForLocal } : {}));
118
156
  }
119
157
  exports.askForParam = askForParam;
158
+ async function promptSecretLocations() {
159
+ return await (0, prompt_1.promptOnce)({
160
+ name: "input",
161
+ type: "checkbox",
162
+ message: "Where would you like to store your secrets? You must select at least one value",
163
+ choices: [
164
+ {
165
+ checked: true,
166
+ name: "Google Cloud Secret Manager",
167
+ value: SecretLocation.CLOUD.toString(),
168
+ },
169
+ {
170
+ checked: false,
171
+ name: "Local file (Only used by Firebase Emulator)",
172
+ value: SecretLocation.LOCAL.toString(),
173
+ },
174
+ ],
175
+ });
176
+ }
177
+ async function promptLocalSecret(instanceId, paramSpec) {
178
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
179
+ const value = await (0, prompt_1.promptOnce)({
180
+ name: paramSpec.param,
181
+ type: "input",
182
+ message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
183
+ `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
184
+ });
185
+ return value;
186
+ }
120
187
  async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
121
188
  const action = await (0, prompt_1.promptOnce)({
122
189
  type: "list",
@@ -210,21 +277,3 @@ function getInquirerDefault(options, def) {
210
277
  return defaultOption ? defaultOption.label || defaultOption.value : "";
211
278
  }
212
279
  exports.getInquirerDefault = getInquirerDefault;
213
- async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
214
- if (_.isEmpty(paramSpecs)) {
215
- logger_1.logger.debug("No params were specified for this extension.");
216
- return {};
217
- }
218
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
219
- const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
220
- const result = {};
221
- const promises = _.map(substituted, (paramSpec) => {
222
- return async () => {
223
- result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring);
224
- };
225
- });
226
- await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
227
- logger_1.logger.info();
228
- return result;
229
- }
230
- exports.ask = ask;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getParams = exports.getExtensionFunctionInfo = exports.buildOptions = void 0;
3
+ exports.getParams = exports.getSecretEnvVars = exports.getNonSecretEnv = exports.getExtensionFunctionInfo = exports.buildOptions = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const _ = require("lodash");
6
6
  const path = require("path");
@@ -8,6 +8,7 @@ const paramHelper = require("../paramHelper");
8
8
  const specHelper = require("./specHelper");
9
9
  const localHelper = require("../localHelper");
10
10
  const triggerHelper = require("./triggerHelper");
11
+ const extensionsApi_1 = require("../extensionsApi");
11
12
  const extensionsHelper = require("../extensionsHelper");
12
13
  const config_1 = require("../../config");
13
14
  const error_1 = require("../../error");
@@ -35,9 +36,9 @@ async function buildOptions(options) {
35
36
  return options;
36
37
  }
37
38
  exports.buildOptions = buildOptions;
38
- async function getExtensionFunctionInfo(extensionDir, instanceId, params) {
39
+ async function getExtensionFunctionInfo(extensionDir, instanceId, paramValues) {
39
40
  const spec = await specHelper.readExtensionYaml(extensionDir);
40
- const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, params);
41
+ const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues);
41
42
  const extensionTriggers = functionResources
42
43
  .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r))
43
44
  .map((trigger) => {
@@ -45,12 +46,43 @@ async function getExtensionFunctionInfo(extensionDir, instanceId, params) {
45
46
  return trigger;
46
47
  });
47
48
  const nodeMajorVersion = specHelper.getNodeVersion(functionResources);
49
+ const nonSecretEnv = getNonSecretEnv(spec.params, paramValues);
50
+ const secretEnvVariables = getSecretEnvVars(spec.params, paramValues);
48
51
  return {
49
52
  extensionTriggers,
50
53
  nodeMajorVersion,
54
+ nonSecretEnv,
55
+ secretEnvVariables,
51
56
  };
52
57
  }
53
58
  exports.getExtensionFunctionInfo = getExtensionFunctionInfo;
59
+ const isSecretParam = (p) => p.type === extensionsHelper.SpecParamType.SECRET || p.type === extensionsApi_1.ParamType.SECRET;
60
+ function getNonSecretEnv(params, paramValues) {
61
+ const getNonSecretEnv = Object.assign({}, paramValues);
62
+ const secretParams = params.filter(isSecretParam);
63
+ for (const p of secretParams) {
64
+ delete getNonSecretEnv[p.param];
65
+ }
66
+ return getNonSecretEnv;
67
+ }
68
+ exports.getNonSecretEnv = getNonSecretEnv;
69
+ function getSecretEnvVars(params, paramValues) {
70
+ const secretEnvVar = [];
71
+ const secretParams = params.filter(isSecretParam);
72
+ for (const s of secretParams) {
73
+ if (paramValues[s.param]) {
74
+ const [, projectId, , secret, , version] = paramValues[s.param].split("/");
75
+ secretEnvVar.push({
76
+ key: s.param,
77
+ secret,
78
+ projectId,
79
+ version,
80
+ });
81
+ }
82
+ }
83
+ return secretEnvVar;
84
+ }
85
+ exports.getSecretEnvVars = getSecretEnvVars;
54
86
  function getParams(options, extensionSpec) {
55
87
  const projectId = (0, projectUtils_1.needProjectId)(options);
56
88
  const userParams = paramHelper.readEnvFile(options.testParams);
@@ -14,6 +14,7 @@ const api_1 = require("../api");
14
14
  const archiveDirectory_1 = require("../archiveDirectory");
15
15
  const utils_1 = require("./utils");
16
16
  const functionsConfig_1 = require("../functionsConfig");
17
+ const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
17
18
  const resolveSource_1 = require("./resolveSource");
18
19
  const error_1 = require("../error");
19
20
  const diagnose_1 = require("./diagnose");
@@ -29,6 +30,7 @@ const logger_1 = require("../logger");
29
30
  const utils_2 = require("../utils");
30
31
  const changelog_1 = require("./changelog");
31
32
  const getProjectNumber_1 = require("../getProjectNumber");
33
+ const constants_1 = require("../emulator/constants");
32
34
  var SpecParamType;
33
35
  (function (SpecParamType) {
34
36
  SpecParamType["SELECT"] = "select";
@@ -76,21 +78,28 @@ function getDBInstanceFromURL(databaseUrl = "") {
76
78
  return "";
77
79
  }
78
80
  exports.getDBInstanceFromURL = getDBInstanceFromURL;
79
- async function getFirebaseProjectParams(projectId) {
80
- const body = await (0, functionsConfig_1.getFirebaseConfig)({ project: projectId });
81
- const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
81
+ async function getFirebaseProjectParams(projectId, emulatorMode = false) {
82
+ var _a, _b;
83
+ const body = emulatorMode
84
+ ? await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)
85
+ : await (0, functionsConfig_1.getFirebaseConfig)({ project: projectId });
86
+ const projectNumber = emulatorMode && constants_1.Constants.isDemoProject(projectId)
87
+ ? constants_1.Constants.FAKE_PROJECT_NUMBER
88
+ : await (0, getProjectNumber_1.getProjectNumber)({ projectId });
89
+ const databaseURL = (_a = body === null || body === void 0 ? void 0 : body.databaseURL) !== null && _a !== void 0 ? _a : `https://${projectId}.firebaseio.com`;
90
+ const storageBucket = (_b = body === null || body === void 0 ? void 0 : body.storageBucket) !== null && _b !== void 0 ? _b : `${projectId}.appspot.com`;
82
91
  const FIREBASE_CONFIG = JSON.stringify({
83
- projectId: body.projectId,
84
- databaseURL: body.databaseURL,
85
- storageBucket: body.storageBucket,
92
+ projectId,
93
+ databaseURL,
94
+ storageBucket,
86
95
  });
87
96
  return {
88
- PROJECT_ID: body.projectId,
97
+ PROJECT_ID: projectId,
89
98
  PROJECT_NUMBER: projectNumber,
90
- DATABASE_URL: body.databaseURL,
91
- STORAGE_BUCKET: body.storageBucket,
99
+ DATABASE_URL: databaseURL,
100
+ STORAGE_BUCKET: storageBucket,
92
101
  FIREBASE_CONFIG,
93
- DATABASE_INSTANCE: getDBInstanceFromURL(body.databaseURL),
102
+ DATABASE_INSTANCE: getDBInstanceFromURL(databaseURL),
94
103
  };
95
104
  }
96
105
  exports.getFirebaseProjectParams = getFirebaseProjectParams;
@@ -1,11 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeToManifest = void 0;
3
+ exports.showPreviewWarning = exports.showDeprecationWarning = exports.readInstanceParam = exports.getInstanceRef = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
4
4
  const clc = require("cli-color");
5
+ const path = require("path");
5
6
  const refs = require("./refs");
7
+ const config_1 = require("../config");
6
8
  const logger_1 = require("../logger");
7
9
  const prompt_1 = require("../prompt");
8
- async function writeToManifest(specs, config, options) {
10
+ const paramHelper_1 = require("./paramHelper");
11
+ const error_1 = require("../error");
12
+ const utils = require("../utils");
13
+ const extensionsHelper_1 = require("./extensionsHelper");
14
+ const extensionsApi_1 = require("./extensionsApi");
15
+ exports.ENV_DIRECTORY = "extensions";
16
+ async function writeToManifest(specs, config, options, allowOverwrite = false) {
9
17
  if (config.has("extensions") &&
10
18
  Object.keys(config.get("extensions")).length &&
11
19
  !options.nonInteractive &&
@@ -13,36 +21,156 @@ async function writeToManifest(specs, config, options) {
13
21
  const currentExtensions = Object.entries(config.get("extensions"))
14
22
  .map((i) => `${i[0]}: ${i[1]}`)
15
23
  .join("\n\t");
16
- const overwrite = await (0, prompt_1.promptOnce)({
17
- type: "list",
18
- message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
19
- choices: [
20
- { name: "Overwrite", value: true },
21
- { name: "Merge", value: false },
22
- ],
23
- });
24
- if (overwrite) {
25
- config.set("extensions", {});
24
+ if (allowOverwrite) {
25
+ const overwrite = await (0, prompt_1.promptOnce)({
26
+ type: "list",
27
+ message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
28
+ choices: [
29
+ { name: "Overwrite", value: true },
30
+ { name: "Merge", value: false },
31
+ ],
32
+ });
33
+ if (overwrite) {
34
+ config.set("extensions", {});
35
+ }
26
36
  }
27
37
  }
28
38
  writeExtensionsToFirebaseJson(specs, config);
29
39
  await writeEnvFiles(specs, config, options.force);
40
+ await writeLocalSecrets(specs, config, options.force);
30
41
  }
31
42
  exports.writeToManifest = writeToManifest;
43
+ async function writeLocalSecrets(specs, config, force) {
44
+ for (const spec of specs) {
45
+ if (!spec.paramSpecs) {
46
+ continue;
47
+ }
48
+ const writeBuffer = {};
49
+ const locallyOverridenSecretParams = spec.paramSpecs.filter((p) => p.type === extensionsApi_1.ParamType.SECRET && spec.params[p.param].local);
50
+ for (const paramSpec of locallyOverridenSecretParams) {
51
+ const key = paramSpec.param;
52
+ const localValue = spec.params[key].local;
53
+ writeBuffer[key] = localValue;
54
+ }
55
+ const content = Object.entries(writeBuffer)
56
+ .sort((a, b) => {
57
+ return a[0].localeCompare(b[0]);
58
+ })
59
+ .map((r) => `${r[0]}=${r[1]}`)
60
+ .join("\n");
61
+ if (content) {
62
+ await config.askWriteProjectFile(`extensions/${spec.instanceId}.secret.local`, content, force);
63
+ }
64
+ }
65
+ }
66
+ exports.writeLocalSecrets = writeLocalSecrets;
67
+ function removeFromManifest(instanceId, config) {
68
+ if (!instanceExists(instanceId, config)) {
69
+ throw new error_1.FirebaseError(`Extension instance ${instanceId} not found in firebase.json.`);
70
+ }
71
+ const extensions = config.get("extensions", {});
72
+ extensions[instanceId] = undefined;
73
+ config.set("extensions", extensions);
74
+ config.writeProjectFile("firebase.json", config.src);
75
+ logger_1.logger.info(`Removed extension instance ${instanceId} from firebase.json`);
76
+ config.deleteProjectFile(`extensions/${instanceId}.env`);
77
+ logger_1.logger.info(`Removed extension instance environment config extensions/${instanceId}.env`);
78
+ if (config.projectFileExists(`extensions/${instanceId}.env.local`)) {
79
+ config.deleteProjectFile(`extensions/${instanceId}.env.local`);
80
+ logger_1.logger.info(`Removed extension instance local environment config extensions/${instanceId}.env.local`);
81
+ }
82
+ if (config.projectFileExists(`extensions/${instanceId}.secret.local`)) {
83
+ config.deleteProjectFile(`extensions/${instanceId}.secret.local`);
84
+ logger_1.logger.info(`Removed extension instance local secret config extensions/${instanceId}.secret.local`);
85
+ }
86
+ }
87
+ exports.removeFromManifest = removeFromManifest;
88
+ function loadConfig(options) {
89
+ const existingConfig = config_1.Config.load(options, true);
90
+ if (!existingConfig) {
91
+ throw new error_1.FirebaseError("Not currently in a Firebase directory. Run `firebase init` to create a Firebase directory.");
92
+ }
93
+ return existingConfig;
94
+ }
95
+ exports.loadConfig = loadConfig;
96
+ function instanceExists(instanceId, config) {
97
+ return !!config.get("extensions", {})[instanceId];
98
+ }
99
+ exports.instanceExists = instanceExists;
100
+ function getInstanceRef(instanceId, config) {
101
+ if (!instanceExists(instanceId, config)) {
102
+ throw new error_1.FirebaseError(`Could not find extension instance ${instanceId} in firebase.json`);
103
+ }
104
+ const ref = config.get("extensions", {})[instanceId];
105
+ return refs.parse(ref);
106
+ }
107
+ exports.getInstanceRef = getInstanceRef;
32
108
  function writeExtensionsToFirebaseJson(specs, config) {
33
109
  const extensions = config.get("extensions", {});
34
110
  for (const s of specs) {
35
111
  extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
36
112
  }
37
113
  config.set("extensions", extensions);
38
- logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
39
114
  config.writeProjectFile("firebase.json", config.src);
115
+ utils.logSuccess("Wrote extensions to " + clc.bold("firebase.json") + "...");
40
116
  }
41
117
  async function writeEnvFiles(specs, config, force) {
42
118
  for (const spec of specs) {
43
119
  const content = Object.entries(spec.params)
44
- .map((r) => `${r[0]}=${r[1]}`)
120
+ .sort((a, b) => {
121
+ return a[0].localeCompare(b[0]);
122
+ })
123
+ .map((r) => `${r[0]}=${r[1].baseValue}`)
45
124
  .join("\n");
46
125
  await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
47
126
  }
48
127
  }
128
+ function readInstanceParam(args) {
129
+ var _a;
130
+ const aliases = (_a = args.aliases) !== null && _a !== void 0 ? _a : [];
131
+ const filesToCheck = [
132
+ `${args.instanceId}.env`,
133
+ ...aliases.map((alias) => `${args.instanceId}.env.${alias}`),
134
+ ...(args.projectNumber ? [`${args.instanceId}.env.${args.projectNumber}`] : []),
135
+ ...(args.projectId ? [`${args.instanceId}.env.${args.projectId}`] : []),
136
+ ];
137
+ if (args.checkLocal) {
138
+ filesToCheck.push(`${args.instanceId}.env.local`);
139
+ }
140
+ let noFilesFound = true;
141
+ const combinedParams = {};
142
+ for (const fileToCheck of filesToCheck) {
143
+ try {
144
+ const params = readParamsFile(args.projectDir, fileToCheck);
145
+ logger_1.logger.debug(`Successfully read params from ${fileToCheck}`);
146
+ noFilesFound = false;
147
+ Object.assign(combinedParams, params);
148
+ }
149
+ catch (err) {
150
+ logger_1.logger.debug(`${err}`);
151
+ }
152
+ }
153
+ if (noFilesFound) {
154
+ throw new error_1.FirebaseError(`No params file found for ${args.instanceId}`);
155
+ }
156
+ return combinedParams;
157
+ }
158
+ exports.readInstanceParam = readInstanceParam;
159
+ function readParamsFile(projectDir, fileName) {
160
+ const paramPath = path.join(projectDir, exports.ENV_DIRECTORY, fileName);
161
+ const params = (0, paramHelper_1.readEnvFile)(paramPath);
162
+ return params;
163
+ }
164
+ function showDeprecationWarning() {
165
+ utils.logLabeledWarning(extensionsHelper_1.logPrefix, "The behavior of ext:install, ext:update, ext:configure, and ext:uninstall will change in firebase-tools@11.0.0. " +
166
+ "Instead of deploying extensions directly, " +
167
+ "changes to extension instances will be written to firebase.json and ./extensions/*.env. " +
168
+ `Then ${clc.bold("firebase deploy (--only extensions)")} will deploy the changes to your Firebase project. ` +
169
+ `To access this behavior now, pass the ${clc.bold("--local")} flag.`);
170
+ }
171
+ exports.showDeprecationWarning = showDeprecationWarning;
172
+ function showPreviewWarning() {
173
+ utils.logLabeledWarning(extensionsHelper_1.logPrefix, "These changes will be reflected in your Firebase Emulator after restart. " +
174
+ `Run ${clc.bold("firebase deploy (--only extensions)")} to deploy the changes to your Firebase project. `);
175
+ }
176
+ exports.showPreviewWarning = showPreviewWarning;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readEnvFile = exports.getParamsFromFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = void 0;
3
+ exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
4
4
  const _ = require("lodash");
5
5
  const path = require("path");
6
6
  const clc = require("cli-color");
@@ -11,6 +11,22 @@ const extensionsHelper_1 = require("./extensionsHelper");
11
11
  const askUserForParam = require("./askUserForParam");
12
12
  const track = require("../track");
13
13
  const env = require("../functions/env");
14
+ function getBaseParamBindings(params) {
15
+ let ret = {};
16
+ for (const [k, v] of Object.entries(params)) {
17
+ ret = Object.assign(Object.assign({}, ret), { [k]: v.baseValue });
18
+ }
19
+ return ret;
20
+ }
21
+ exports.getBaseParamBindings = getBaseParamBindings;
22
+ function buildBindingOptionsWithBaseValue(baseParams) {
23
+ let paramOptions = {};
24
+ for (const [k, v] of Object.entries(baseParams)) {
25
+ paramOptions = Object.assign(Object.assign({}, paramOptions), { [k]: { baseValue: v } });
26
+ }
27
+ return paramOptions;
28
+ }
29
+ exports.buildBindingOptionsWithBaseValue = buildBindingOptionsWithBaseValue;
14
30
  function setNewDefaults(params, newDefaults) {
15
31
  params.forEach((param) => {
16
32
  if (newDefaults[param.param.toUpperCase()]) {
@@ -19,6 +35,7 @@ function setNewDefaults(params, newDefaults) {
19
35
  });
20
36
  return params;
21
37
  }
38
+ exports.setNewDefaults = setNewDefaults;
22
39
  function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
23
40
  const specParams = _.cloneDeep(_.get(extensionInstance, "config.source.spec.params", []));
24
41
  const currentParams = _.cloneDeep(_.get(extensionInstance, "config.params", {}));
@@ -87,29 +104,36 @@ async function getParamsForUpdate(args) {
87
104
  }
88
105
  exports.getParamsForUpdate = getParamsForUpdate;
89
106
  async function promptForNewParams(args) {
107
+ const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
90
108
  const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
91
109
  const comparer = (param1, param2) => {
92
110
  return param1.type === param2.type && param1.param === param2.param;
93
111
  };
94
- let paramsDiffDeletions = _.differenceWith(args.spec.params, _.get(args.newSpec, "params", []), comparer);
112
+ const oldParams = args.spec.params.filter((p) => Object.keys(args.currentParams).includes(p.param));
113
+ let paramsDiffDeletions = _.differenceWith(oldParams, args.newSpec.params, comparer);
95
114
  paramsDiffDeletions = (0, extensionsHelper_1.substituteParams)(paramsDiffDeletions, firebaseProjectParams);
96
- let paramsDiffAdditions = _.differenceWith(args.newSpec.params, _.get(args.spec, "params", []), comparer);
115
+ let paramsDiffAdditions = _.differenceWith(args.newSpec.params, oldParams, comparer);
97
116
  paramsDiffAdditions = (0, extensionsHelper_1.substituteParams)(paramsDiffAdditions, firebaseProjectParams);
98
117
  if (paramsDiffDeletions.length) {
99
118
  logger_1.logger.info("The following params will no longer be used:");
100
119
  paramsDiffDeletions.forEach((param) => {
101
120
  logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
102
- delete args.currentParams[param.param.toUpperCase()];
121
+ delete newParamBindingOptions[param.param.toUpperCase()];
103
122
  });
104
123
  }
105
124
  if (paramsDiffAdditions.length) {
106
125
  logger_1.logger.info("To update this instance, configure the following new parameters:");
107
126
  for (const param of paramsDiffAdditions) {
108
- const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
109
- args.currentParams[param.param] = chosenValue;
127
+ const chosenValue = await askUserForParam.askForParam({
128
+ projectId: args.projectId,
129
+ instanceId: args.instanceId,
130
+ paramSpec: param,
131
+ reconfiguring: false,
132
+ });
133
+ newParamBindingOptions[param.param] = chosenValue;
110
134
  }
111
135
  }
112
- return args.currentParams;
136
+ return newParamBindingOptions;
113
137
  }
114
138
  exports.promptForNewParams = promptForNewParams;
115
139
  function getParamsFromFile(args) {
@@ -125,9 +149,8 @@ function getParamsFromFile(args) {
125
149
  const params = (0, extensionsHelper_1.populateDefaultParams)(envParams, args.paramSpecs);
126
150
  (0, extensionsHelper_1.validateCommandLineParams)(params, args.paramSpecs);
127
151
  logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
128
- return params;
152
+ return buildBindingOptionsWithBaseValue(params);
129
153
  }
130
- exports.getParamsFromFile = getParamsFromFile;
131
154
  function readEnvFile(envPath) {
132
155
  const buf = fs.readFileSync(path.resolve(envPath), "utf8");
133
156
  const result = env.parse(buf.toString().trim());