firebase-tools 11.2.1 → 11.2.2

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.
@@ -28,10 +28,10 @@ async function checkValidTargetFilters(options) {
28
28
  if (options.except) {
29
29
  return reject(new error_1.FirebaseError("Cannot specify both --only and --except"));
30
30
  }
31
- const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions"].includes(t));
31
+ const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions", "firestore"].includes(t));
32
32
  const targetsForNonFilteredTypes = targetsForTypes(only, ...nonFilteredTypes);
33
33
  if (targetsForNonFilteredTypes.length && targetsHaveFilters(...targetsForNonFilteredTypes)) {
34
- return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions and hosting"));
34
+ return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions, hosting, and firestore"));
35
35
  }
36
36
  const targetsForFunctions = targetsForTypes(only, "functions");
37
37
  if (targetsForFunctions.length &&
@@ -4,6 +4,7 @@ exports.command = void 0;
4
4
  const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
5
5
  const command_1 = require("../command");
6
6
  const planner = require("../deploy/extensions/planner");
7
+ const etags_1 = require("../extensions/etags");
7
8
  const export_1 = require("../extensions/export");
8
9
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
9
10
  const manifest = require("../extensions/manifest");
@@ -63,4 +64,5 @@ exports.command = new command_1.Command("ext:export")
63
64
  nonInteractive: options.nonInteractive,
64
65
  force: options.force,
65
66
  }, true);
67
+ (0, etags_1.saveEtags)(options.rc, projectId, have);
66
68
  });
@@ -54,6 +54,7 @@ async function have(projectId) {
54
54
  params: i.config.params,
55
55
  allowedEventTypes: i.config.allowedEventTypes,
56
56
  eventarcChannel: i.config.eventarcChannel,
57
+ etag: i.etag,
57
58
  };
58
59
  if (i.config.extensionRef) {
59
60
  const ref = refs.parse(i.config.extensionRef);
@@ -13,6 +13,7 @@ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
13
13
  const secretsUtils_1 = require("../../extensions/secretsUtils");
14
14
  const secrets_1 = require("./secrets");
15
15
  const warnings_1 = require("../../extensions/warnings");
16
+ const etags_1 = require("../../extensions/etags");
16
17
  async function prepare(context, options, payload) {
17
18
  var _a;
18
19
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -28,6 +29,22 @@ async function prepare(context, options, payload) {
28
29
  projectDir: options.config.projectDir,
29
30
  extensions: options.config.get("extensions"),
30
31
  });
32
+ const etagsChanged = (0, etags_1.detectEtagChanges)(options.rc, projectId, context.have);
33
+ if (etagsChanged.length) {
34
+ (0, warnings_1.outOfBandChangesWarning)(etagsChanged);
35
+ if (!options.force && options.nonInteractive) {
36
+ throw new error_1.FirebaseError("Pass the --force flag to overwrite out of band changes in non-interactive mode");
37
+ }
38
+ else if (!options.force &&
39
+ !options.nonInteractive &&
40
+ !(await prompt.promptOnce({
41
+ type: "confirm",
42
+ message: `Do you wish to continue deploying these extension instances?`,
43
+ default: false,
44
+ }))) {
45
+ throw new error_1.FirebaseError("Deployment cancelled");
46
+ }
47
+ }
31
48
  const usingSecrets = await Promise.all((_a = context.want) === null || _a === void 0 ? void 0 : _a.map(secrets_1.checkSpecForSecrets));
32
49
  if (usingSecrets.some((i) => i)) {
33
50
  await (0, secretsUtils_1.ensureSecretManagerApiEnabled)(options);
@@ -44,7 +61,7 @@ async function prepare(context, options, payload) {
44
61
  !options.nonInteractive &&
45
62
  !(await prompt.promptOnce({
46
63
  type: "confirm",
47
- message: `Do you wish to continue deploying these extensions?`,
64
+ message: `Do you wish to continue deploying these extension instances?`,
48
65
  default: true,
49
66
  }))) {
50
67
  throw new error_1.FirebaseError("Deployment cancelled");
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.release = void 0;
4
4
  const queue_1 = require("../../throttler/queue");
5
5
  const tasks = require("./tasks");
6
+ const planner = require("./planner");
6
7
  const error_1 = require("../../error");
7
8
  const errors_1 = require("./errors");
8
9
  const projectUtils_1 = require("../../projectUtils");
10
+ const etags_1 = require("../../extensions/etags");
9
11
  async function release(context, options, payload) {
10
12
  var _a, _b, _c, _d;
11
13
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -35,6 +37,8 @@ async function release(context, options, payload) {
35
37
  deploymentQueue.process();
36
38
  deploymentQueue.close();
37
39
  await deploymentPromise;
40
+ const newHave = await planner.have(projectId);
41
+ (0, etags_1.saveEtags)(options.rc, projectId, newHave);
38
42
  if (errorHandler.hasErrors()) {
39
43
  errorHandler.print();
40
44
  throw new error_1.FirebaseError(`Extensions deployment failed.`);
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveBackend = exports.of = exports.empty = void 0;
3
+ exports.toBackend = exports.resolveBackend = exports.of = exports.empty = void 0;
4
4
  const backend = require("./backend");
5
5
  const proto = require("../../gcp/proto");
6
6
  const api = require("../../.../../api");
7
+ const params = require("./params");
8
+ const previews_1 = require("../../previews");
7
9
  const error_1 = require("../../error");
8
10
  const functional_1 = require("../../functional");
9
11
  function empty() {
@@ -20,45 +22,19 @@ function of(endpoints) {
20
22
  return build;
21
23
  }
22
24
  exports.of = of;
23
- function resolveInt(from) {
24
- if (from == null) {
25
- return 0;
26
- }
27
- else if (typeof from === "string") {
28
- throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
29
- }
30
- return from;
31
- }
32
- function resolveString(from) {
33
- if (from == null) {
34
- return "";
35
- }
36
- else if (from.includes("{{") && from.includes("}}")) {
37
- throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
38
- }
39
- return from;
40
- }
41
- function resolveBoolean(from) {
42
- if (from == null) {
43
- return false;
44
- }
45
- else if (typeof from === "string") {
46
- throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
47
- }
48
- return from;
49
- }
50
25
  function isMemoryOption(value) {
51
26
  return value == null || [128, 256, 512, 1024, 2048, 4096, 8192].includes(value);
52
27
  }
53
- function resolveBackend(build, userEnvs) {
54
- for (const param of build.params) {
55
- const expectedEnv = param.param;
56
- if (!userEnvs.hasOwnProperty(expectedEnv)) {
57
- throw new error_1.FirebaseError("Build specified parameter " +
58
- expectedEnv +
59
- " but it was not present in the user dotenv files or Cloud Secret Manager");
60
- }
28
+ async function resolveBackend(build, userEnvOpt, userEnvs) {
29
+ const projectId = userEnvOpt.projectId;
30
+ let paramValues = {};
31
+ if (previews_1.previews.functionsparams) {
32
+ paramValues = await params.resolveParams(build.params, projectId, userEnvs);
61
33
  }
34
+ return toBackend(build, paramValues);
35
+ }
36
+ exports.resolveBackend = resolveBackend;
37
+ function toBackend(build, paramValues) {
62
38
  const bkEndpoints = [];
63
39
  for (const endpointId of Object.keys(build.endpoints)) {
64
40
  const bdEndpoint = build.endpoints[endpointId];
@@ -67,7 +43,7 @@ function resolveBackend(build, userEnvs) {
67
43
  regions = [api.functionsDefaultRegion];
68
44
  }
69
45
  for (const region of regions) {
70
- const trigger = discoverTrigger(bdEndpoint);
46
+ const trigger = discoverTrigger(bdEndpoint, paramValues);
71
47
  if (typeof bdEndpoint.platform === "undefined") {
72
48
  throw new error_1.FirebaseError("platform can't be undefined");
73
49
  }
@@ -76,21 +52,25 @@ function resolveBackend(build, userEnvs) {
76
52
  }
77
53
  let timeout;
78
54
  if (bdEndpoint.timeoutSeconds) {
79
- timeout = resolveInt(bdEndpoint.timeoutSeconds);
55
+ timeout = params.resolveInt(bdEndpoint.timeoutSeconds, paramValues);
80
56
  }
81
57
  else {
82
58
  timeout = 60;
83
59
  }
84
60
  const bkEndpoint = Object.assign({ id: endpointId, project: bdEndpoint.project, region: region, entryPoint: bdEndpoint.entryPoint, platform: bdEndpoint.platform, runtime: bdEndpoint.runtime, timeoutSeconds: timeout }, trigger);
85
- proto.renameIfPresent(bkEndpoint, bdEndpoint, "maxInstances", "maxInstances", resolveInt);
86
- proto.renameIfPresent(bkEndpoint, bdEndpoint, "minInstances", "minInstances", resolveInt);
87
- proto.renameIfPresent(bkEndpoint, bdEndpoint, "concurrency", "concurrency", resolveInt);
61
+ proto.renameIfPresent(bkEndpoint, bdEndpoint, "maxInstances", "maxInstances", (from) => {
62
+ return params.resolveInt(from, paramValues);
63
+ });
64
+ proto.renameIfPresent(bkEndpoint, bdEndpoint, "minInstances", "minInstances", (from) => {
65
+ return params.resolveInt(from, paramValues);
66
+ });
67
+ proto.renameIfPresent(bkEndpoint, bdEndpoint, "concurrency", "concurrency", (from) => {
68
+ return params.resolveInt(from, paramValues);
69
+ });
88
70
  proto.copyIfPresent(bkEndpoint, bdEndpoint, "ingressSettings", "availableMemoryMb", "environmentVariables", "labels");
89
71
  proto.copyIfPresent(bkEndpoint, bdEndpoint, "secretEnvironmentVariables");
90
72
  if (bdEndpoint.vpc) {
91
- bkEndpoint.vpc = {
92
- connector: resolveString(bdEndpoint.vpc.connector).replace("$REGION", region),
93
- };
73
+ bkEndpoint.vpc = { connector: params.resolveString(bdEndpoint.vpc.connector, paramValues) };
94
74
  proto.copyIfPresent(bkEndpoint.vpc, bdEndpoint.vpc, "egressSettings");
95
75
  }
96
76
  proto.renameIfPresent(bkEndpoint, bdEndpoint, "serviceAccountEmail", "serviceAccount");
@@ -104,8 +84,11 @@ function resolveBackend(build, userEnvs) {
104
84
  bkend.requiredAPIs = build.requiredAPIs;
105
85
  return bkend;
106
86
  }
107
- exports.resolveBackend = resolveBackend;
108
- function discoverTrigger(endpoint) {
87
+ exports.toBackend = toBackend;
88
+ function discoverTrigger(endpoint, paramValues) {
89
+ const resolveInt = (from) => params.resolveInt(from, paramValues);
90
+ const resolveString = (from) => params.resolveString(from, paramValues);
91
+ const resolveBoolean = (from) => params.resolveBoolean(from, paramValues);
109
92
  let trigger;
110
93
  if ("httpsTrigger" in endpoint) {
111
94
  const bkHttps = {};
@@ -122,15 +105,13 @@ function discoverTrigger(endpoint) {
122
105
  }
123
106
  else if ("eventTrigger" in endpoint) {
124
107
  const bkEventFilters = {};
125
- for (const key in endpoint.eventTrigger.eventFilters) {
126
- if (typeof key === "string") {
127
- bkEventFilters[key] = resolveString(endpoint.eventTrigger.eventFilters[key]);
128
- }
108
+ for (const [key, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
109
+ bkEventFilters[key] = params.resolveString(value, paramValues);
129
110
  }
130
111
  const bkEvent = {
131
112
  eventType: endpoint.eventTrigger.eventType,
132
113
  eventFilters: bkEventFilters,
133
- retry: resolveBoolean(endpoint.eventTrigger.retry),
114
+ retry: resolveBoolean(endpoint.eventTrigger.retry || false),
134
115
  };
135
116
  if (endpoint.eventTrigger.serviceAccount) {
136
117
  bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount;
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveParams = exports.resolveBoolean = exports.resolveString = exports.resolveInt = void 0;
4
+ const logger_1 = require("../../logger");
5
+ const error_1 = require("../../error");
6
+ const prompt_1 = require("../../prompt");
7
+ const functional_1 = require("../../functional");
8
+ function isCEL(expr) {
9
+ return typeof expr === "string" && expr.includes("{{") && expr.includes("}}");
10
+ }
11
+ function dependenciesCEL(expr) {
12
+ const deps = [];
13
+ const paramCapture = /{{ params\.(\w+) }}/g;
14
+ let match;
15
+ while ((match = paramCapture.exec(expr)) != null) {
16
+ deps.push(match[1]);
17
+ }
18
+ return deps;
19
+ }
20
+ function resolveInt(from, paramValues) {
21
+ if (typeof from === "number") {
22
+ return from;
23
+ }
24
+ const match = /\A{{ params\.(\w+) }}\z/.exec(from);
25
+ if (!match) {
26
+ throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
27
+ }
28
+ const referencedParamValue = paramValues[match[1]];
29
+ if (typeof referencedParamValue !== "number") {
30
+ throw new error_1.FirebaseError("Referenced numeric parameter '" +
31
+ match +
32
+ "' resolved to non-number value " +
33
+ referencedParamValue);
34
+ }
35
+ return referencedParamValue;
36
+ }
37
+ exports.resolveInt = resolveInt;
38
+ function resolveString(from, paramValues) {
39
+ if (!isCEL(from)) {
40
+ return from;
41
+ }
42
+ let output = from;
43
+ const paramCapture = /{{ params\.(\w+) }}/g;
44
+ let match;
45
+ while ((match = paramCapture.exec(from)) != null) {
46
+ const referencedParamValue = paramValues[match[1]];
47
+ if (typeof referencedParamValue !== "string") {
48
+ throw new error_1.FirebaseError("Referenced string parameter '" +
49
+ match[1] +
50
+ "' resolved to non-string value " +
51
+ referencedParamValue);
52
+ }
53
+ output = output.replace(`{{ params.${match[1]} }}`, referencedParamValue);
54
+ }
55
+ if (isCEL(output)) {
56
+ throw new error_1.FirebaseError("CEL evaluation of non-identity expression '" + from + "' not yet supported");
57
+ }
58
+ return output;
59
+ }
60
+ exports.resolveString = resolveString;
61
+ function resolveBoolean(from, paramValues) {
62
+ if (typeof from === "string" && /{{ params\.(\w+) }}/.test(from)) {
63
+ const match = /{{ params\.(\w+) }}/.exec(from);
64
+ const referencedParamValue = paramValues[match[1]];
65
+ if (typeof referencedParamValue !== "boolean") {
66
+ throw new error_1.FirebaseError("Referenced boolean parameter '" +
67
+ match +
68
+ "' resolved to non-boolean value " +
69
+ referencedParamValue);
70
+ }
71
+ return referencedParamValue;
72
+ }
73
+ else if (typeof from === "string") {
74
+ throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
75
+ }
76
+ return from;
77
+ }
78
+ exports.resolveBoolean = resolveBoolean;
79
+ function resolveDefaultCEL(type, expr, currentEnv) {
80
+ const deps = dependenciesCEL(expr);
81
+ const allDepsFound = deps.every((dep) => !!currentEnv[dep]);
82
+ if (!allDepsFound) {
83
+ throw new error_1.FirebaseError("Build specified parameter with un-resolvable default value " +
84
+ expr +
85
+ "; dependencies missing.");
86
+ }
87
+ switch (type) {
88
+ case "string":
89
+ return resolveString(expr, currentEnv);
90
+ case "int":
91
+ return resolveInt(expr, currentEnv);
92
+ default:
93
+ throw new error_1.FirebaseError("Build specified parameter with default " + expr + " of unsupported type");
94
+ }
95
+ }
96
+ function canSatisfyParam(param, value) {
97
+ if (param.type === "string") {
98
+ return typeof value === "string";
99
+ }
100
+ else if (param.type === "int") {
101
+ return typeof value === "number" && Number.isInteger(value);
102
+ }
103
+ (0, functional_1.assertExhaustive)(param);
104
+ }
105
+ async function resolveParams(params, projectId, userEnvs) {
106
+ const paramValues = {};
107
+ for (const param of params.filter((param) => userEnvs.hasOwnProperty(param.param))) {
108
+ if (!canSatisfyParam(param, userEnvs[param.param])) {
109
+ throw new error_1.FirebaseError("Parameter " +
110
+ param.param +
111
+ " resolved to value from dotenv files " +
112
+ userEnvs[param.param] +
113
+ " of wrong type");
114
+ }
115
+ paramValues[param.param] = userEnvs[param.param];
116
+ }
117
+ for (const param of params.filter((param) => !userEnvs.hasOwnProperty(param.param))) {
118
+ let paramDefault = param.default;
119
+ if (paramDefault && isCEL(paramDefault)) {
120
+ paramDefault = resolveDefaultCEL(param.type, paramDefault, paramValues);
121
+ }
122
+ if (paramDefault && !canSatisfyParam(param, paramDefault)) {
123
+ throw new error_1.FirebaseError("Parameter " + param.param + " has default value " + paramDefault + " of wrong type");
124
+ }
125
+ paramValues[param.param] = await promptParam(param, paramDefault);
126
+ }
127
+ return paramValues;
128
+ }
129
+ exports.resolveParams = resolveParams;
130
+ async function promptParam(param, resolvedDefault) {
131
+ if (param.type === "string") {
132
+ return promptStringParam(param, resolvedDefault);
133
+ }
134
+ else if (param.type === "int") {
135
+ return promptIntParam(param, resolvedDefault);
136
+ }
137
+ (0, functional_1.assertExhaustive)(param);
138
+ }
139
+ async function promptStringParam(param, resolvedDefault) {
140
+ if (!param.input) {
141
+ const defaultToText = { type: "text", text: {} };
142
+ param.input = defaultToText;
143
+ }
144
+ switch (param.input.type) {
145
+ case "select":
146
+ throw new error_1.FirebaseError("Build specified string parameter " + param.param + " with unsupported input type 'select'");
147
+ case "text":
148
+ default:
149
+ let prompt = `Enter a value for ${param.label || param.param}`;
150
+ if (param.description) {
151
+ prompt += ` \n(${param.description})`;
152
+ }
153
+ return await (0, prompt_1.promptOnce)({
154
+ name: param.param,
155
+ type: "input",
156
+ default: resolvedDefault,
157
+ message: prompt,
158
+ });
159
+ }
160
+ }
161
+ async function promptIntParam(param, resolvedDefault) {
162
+ if (!param.input) {
163
+ const defaultToText = { type: "text", text: {} };
164
+ param.input = defaultToText;
165
+ }
166
+ switch (param.input.type) {
167
+ case "select":
168
+ throw new error_1.FirebaseError("Build specified int parameter " + param.param + " with unsupported input type 'select'");
169
+ case "text":
170
+ default:
171
+ let prompt = `Enter a value for ${param.label || param.param}`;
172
+ if (param.description) {
173
+ prompt += ` \n(${param.description})`;
174
+ }
175
+ let res;
176
+ while (true) {
177
+ res = await (0, prompt_1.promptOnce)({
178
+ name: param.param,
179
+ type: "number",
180
+ default: resolvedDefault,
181
+ message: prompt,
182
+ });
183
+ if (Number.isInteger(res)) {
184
+ return res;
185
+ }
186
+ logger_1.logger.error(`${param.label || param.param} must be an integer; retrying...`);
187
+ }
188
+ }
189
+ }
@@ -78,7 +78,7 @@ async function prepare(context, options, payload) {
78
78
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
79
79
  const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
80
80
  const wantBuild = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
81
- const wantBackend = build.resolveBackend(wantBuild, userEnvs);
81
+ const wantBackend = await build.resolveBackend(wantBuild, userEnvOpt, userEnvs);
82
82
  wantBackend.environmentVariables = envs;
83
83
  for (const endpoint of backend.allEndpoints(wantBackend)) {
84
84
  endpoint.environmentVariables = wantBackend.environmentVariables;
@@ -31,7 +31,7 @@ function runCommand(command, childOptions) {
31
31
  reject(new Error("Command terminated with signal " + signal));
32
32
  }
33
33
  else if (code !== 0) {
34
- reject(new Error("Command terminated with non-zero exit code" + code));
34
+ reject(new Error("Command terminated with non-zero exit code " + code));
35
35
  }
36
36
  else {
37
37
  resolve();
@@ -11,7 +11,7 @@ async function default_1(context, options) {
11
11
  _.set(context, "storage.rules", rulesConfig);
12
12
  const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
13
13
  _.set(context, "storage.rulesDeploy", rulesDeploy);
14
- if (_.isPlainObject(rulesConfig)) {
14
+ if (typeof rulesConfig === "object" && rulesConfig !== null) {
15
15
  const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
16
16
  rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
17
17
  _.set(context, "storage.rules", rulesConfig);
@@ -164,7 +164,7 @@ const Commands = {
164
164
  },
165
165
  ui: {
166
166
  binary: "node",
167
- args: ["--dns-result-order=ipv4first", getExecPath(types_1.Emulators.UI)],
167
+ args: [getExecPath(types_1.Emulators.UI)],
168
168
  optionalArgs: [],
169
169
  joinArgs: false,
170
170
  },
@@ -272,8 +272,13 @@ class FunctionsEmulator {
272
272
  await runtimeDelegate.build();
273
273
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
274
274
  const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env);
275
+ const userEnvOpt = {
276
+ functionsSource: emulatableBackend.functionsDir,
277
+ projectId: this.args.projectId,
278
+ projectAlias: this.args.projectAlias,
279
+ };
275
280
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
276
- const discoveredBackend = (0, build_1.resolveBackend)(discoveredBuild, environment);
281
+ const discoveredBackend = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
277
282
  const endpoints = backend.allEndpoints(discoveredBackend);
278
283
  (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
279
284
  for (const e of endpoints) {
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectEtagChanges = exports.saveEtags = void 0;
4
+ function saveEtags(rc, projectId, instances) {
5
+ rc.setEtags(projectId, "extensionInstances", etagsMap(instances));
6
+ }
7
+ exports.saveEtags = saveEtags;
8
+ function detectEtagChanges(rc, projectId, instances) {
9
+ const lastDeployedEtags = rc.getEtags(projectId).extensionInstances;
10
+ const currentEtags = etagsMap(instances);
11
+ if (!lastDeployedEtags || !Object.keys(lastDeployedEtags).length) {
12
+ return [];
13
+ }
14
+ const changedExtensionInstances = Object.entries(lastDeployedEtags)
15
+ .filter(([instanceName, etag]) => etag !== currentEtags[instanceName])
16
+ .map((i) => i[0]);
17
+ const newExtensionInstances = Object.keys(currentEtags).filter((instanceName) => !lastDeployedEtags[instanceName]);
18
+ return newExtensionInstances.concat(changedExtensionInstances);
19
+ }
20
+ exports.detectEtagChanges = detectEtagChanges;
21
+ function etagsMap(instances) {
22
+ return instances.reduce((acc, i) => {
23
+ if (i.etag) {
24
+ acc[i.instanceId] = i.etag;
25
+ }
26
+ return acc;
27
+ }, {});
28
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.paramsFlagDeprecationWarning = exports.displayWarningsForDeploy = exports.displayWarningPrompts = void 0;
3
+ exports.outOfBandChangesWarning = exports.paramsFlagDeprecationWarning = exports.displayWarningsForDeploy = exports.displayWarningPrompts = void 0;
4
4
  const { marked } = require("marked");
5
5
  const clc = require("cli-color");
6
6
  const types_1 = require("./types");
@@ -75,3 +75,9 @@ function paramsFlagDeprecationWarning() {
75
75
  "See https://firebase.google.com/docs/extensions/manifest for more details");
76
76
  }
77
77
  exports.paramsFlagDeprecationWarning = paramsFlagDeprecationWarning;
78
+ function outOfBandChangesWarning(instanceIds) {
79
+ logger_1.logger.warn("The following instances may have been changed in the Firebase console or by another machine since the last deploy from this machine.\n\t" +
80
+ clc.bold(instanceIds.join("\n\t")) +
81
+ "\nIf you proceed with this deployment, those changes will be overwritten. To avoid this, run `firebase ext:export` to sync these changes to your local extensions manifest.");
82
+ }
83
+ exports.outOfBandChangesWarning = outOfBandChangesWarning;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
3
+ exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.writeUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
4
4
  const clc = require("cli-color");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
@@ -145,6 +145,10 @@ function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, })
145
145
  return findEnvfiles(functionsSource, projectId, projectAlias, isEmulator).length > 0;
146
146
  }
147
147
  exports.hasUserEnvs = hasUserEnvs;
148
+ function writeUserEnvs(toWrite, envOpts) {
149
+ throw new error_1.FirebaseError("Persisting user-defined parameters to .env files is not yet implemented.");
150
+ }
151
+ exports.writeUserEnvs = writeUserEnvs;
148
152
  function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
149
153
  var _a;
150
154
  const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
@@ -70,7 +70,7 @@ async function setVariablesRecursive(projectId, configId, varPath, val) {
70
70
  catch (e) {
71
71
  }
72
72
  }
73
- if (_.isPlainObject(parsed)) {
73
+ if (typeof parsed === "object" && parsed !== null) {
74
74
  return Promise.all(Object.entries(parsed).map(([key, item]) => {
75
75
  const newVarPath = varPath ? [varPath, key].join("/") : key;
76
76
  return setVariablesRecursive(projectId, configId, newVarPath, item);
package/lib/gcp/rules.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.testRuleset = exports.updateOrCreateRelease = exports.updateRelease = exports.createRelease = exports.createRuleset = exports.deleteRuleset = exports.getRulesetId = exports.listAllRulesets = exports.listRulesets = exports.getRulesetContent = exports.listAllReleases = exports.listReleases = exports.getLatestRulesetName = void 0;
4
- const _ = require("lodash");
5
4
  const api_1 = require("../api");
6
5
  const apiv2_1 = require("../apiv2");
7
6
  const logger_1 = require("../logger");
@@ -51,7 +50,7 @@ async function listAllReleases(projectId) {
51
50
  }
52
51
  pageToken = response.nextPageToken;
53
52
  } while (pageToken);
54
- return _.orderBy(releases, ["createTime"], ["desc"]);
53
+ return releases.sort((a, b) => b.createTime.localeCompare(a.createTime));
55
54
  }
56
55
  exports.listAllReleases = listAllReleases;
57
56
  async function getRulesetContent(name) {
@@ -90,7 +89,7 @@ async function listAllRulesets(projectId) {
90
89
  }
91
90
  pageToken = response.nextPageToken;
92
91
  } while (pageToken);
93
- return _.orderBy(rulesets, ["createTime"], ["desc"]);
92
+ return rulesets.sort((a, b) => b.createTime.localeCompare(a.createTime));
94
93
  }
95
94
  exports.listAllRulesets = listAllRulesets;
96
95
  function getRulesetId(ruleset) {
@@ -43,7 +43,7 @@ async function getChannel(project = "-", site, channelId) {
43
43
  return res.body;
44
44
  }
45
45
  catch (e) {
46
- if (e.status === 404) {
46
+ if (e instanceof error_1.FirebaseError && e.status === 404) {
47
47
  return null;
48
48
  }
49
49
  throw e;
@@ -51,23 +51,22 @@ async function getChannel(project = "-", site, channelId) {
51
51
  }
52
52
  exports.getChannel = getChannel;
53
53
  async function listChannels(project = "-", site) {
54
- var _a, _b;
55
54
  const channels = [];
56
55
  let nextPageToken = "";
57
56
  for (;;) {
58
57
  try {
59
58
  const res = await apiClient.get(`/projects/${project}/sites/${site}/channels`, { queryParams: { pageToken: nextPageToken, pageSize: 10 } });
60
- const c = (_a = res.body) === null || _a === void 0 ? void 0 : _a.channels;
59
+ const c = res.body.channels;
61
60
  if (c) {
62
61
  channels.push(...c);
63
62
  }
64
- nextPageToken = ((_b = res.body) === null || _b === void 0 ? void 0 : _b.nextPageToken) || "";
63
+ nextPageToken = res.body.nextPageToken || "";
65
64
  if (!nextPageToken) {
66
65
  return channels;
67
66
  }
68
67
  }
69
68
  catch (e) {
70
- if (e.status === 404) {
69
+ if (e instanceof error_1.FirebaseError && e.status === 404) {
71
70
  throw new error_1.FirebaseError(`could not find channels for site "${site}"`, {
72
71
  original: e,
73
72
  });
@@ -116,23 +115,22 @@ async function createRelease(site, channel, version) {
116
115
  }
117
116
  exports.createRelease = createRelease;
118
117
  async function listSites(project) {
119
- var _a, _b;
120
118
  const sites = [];
121
119
  let nextPageToken = "";
122
120
  for (;;) {
123
121
  try {
124
122
  const res = await apiClient.get(`/projects/${project}/sites`, { queryParams: { pageToken: nextPageToken, pageSize: 10 } });
125
- const c = (_a = res.body) === null || _a === void 0 ? void 0 : _a.sites;
123
+ const c = res.body.sites;
126
124
  if (c) {
127
125
  sites.push(...c);
128
126
  }
129
- nextPageToken = ((_b = res.body) === null || _b === void 0 ? void 0 : _b.nextPageToken) || "";
127
+ nextPageToken = res.body.nextPageToken || "";
130
128
  if (!nextPageToken) {
131
129
  return sites;
132
130
  }
133
131
  }
134
132
  catch (e) {
135
- if (e.status === 404) {
133
+ if (e instanceof error_1.FirebaseError && e.status === 404) {
136
134
  throw new error_1.FirebaseError(`could not find sites for project "${project}"`, {
137
135
  original: e,
138
136
  });
@@ -148,7 +146,7 @@ async function getSite(project, site) {
148
146
  return res.body;
149
147
  }
150
148
  catch (e) {
151
- if (e.status === 404) {
149
+ if (e instanceof error_1.FirebaseError && e.status === 404) {
152
150
  throw new error_1.FirebaseError(`could not find site "${site}" for project "${project}"`, {
153
151
  original: e,
154
152
  });
@@ -158,7 +156,7 @@ async function getSite(project, site) {
158
156
  }
159
157
  exports.getSite = getSite;
160
158
  async function createSite(project, site, appId = "") {
161
- const res = await apiClient.post(`/projects/${project}/sites`, { appId: appId }, { queryParams: { site_id: site } });
159
+ const res = await apiClient.post(`/projects/${project}/sites`, { appId: appId }, { queryParams: { siteId: site } });
162
160
  return res.body;
163
161
  }
164
162
  exports.createSite = createSite;
@@ -16,8 +16,8 @@ const DURATIONS = {
16
16
  };
17
17
  exports.MAX_DURATION = 30 * Duration.DAY;
18
18
  exports.DEFAULT_DURATION = 7 * Duration.DAY;
19
- function calculateChannelExpireTTL(flag) {
20
- const match = exports.DURATION_REGEX.exec(flag || "");
19
+ function calculateChannelExpireTTL(flag = "") {
20
+ const match = exports.DURATION_REGEX.exec(flag);
21
21
  if (!match) {
22
22
  throw new error_1.FirebaseError(`"expires" flag must be a duration string (e.g. 24h or 7d) at most 30d`);
23
23
  }
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getFirebaseProject = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.PROJECTS_CREATE_QUESTIONS = exports.ProjectParentResourceType = void 0;
4
- const _ = require("lodash");
5
4
  const clc = require("cli-color");
6
5
  const ora = require("ora");
7
6
  const apiv2_1 = require("../apiv2");
@@ -110,15 +109,15 @@ async function selectProjectByPrompting() {
110
109
  return await getFirebaseProject(projectId);
111
110
  }
112
111
  async function selectProjectFromList(projects = []) {
113
- let choices = projects
112
+ const choices = projects
114
113
  .filter((p) => !!p)
115
114
  .map((p) => {
116
115
  return {
117
116
  name: p.projectId + (p.displayName ? ` (${p.displayName})` : ""),
118
117
  value: p.projectId,
119
118
  };
120
- });
121
- choices = _.orderBy(choices, ["name"], ["asc"]);
119
+ })
120
+ .sort((a, b) => a.name.localeCompare(b.name));
122
121
  if (choices.length >= 25) {
123
122
  utils.logBullet(`Don't want to scroll through all your projects? If you know your project ID, ` +
124
123
  `you can initialize it directly using ${clc.bold("firebase init --project <project_id>")}.\n`);
@@ -151,7 +150,7 @@ async function promptAvailableProjectId() {
151
150
  });
152
151
  }
153
152
  else {
154
- let choices = projects
153
+ const choices = projects
155
154
  .filter((p) => !!p)
156
155
  .map((p) => {
157
156
  const projectId = getProjectId(p);
@@ -159,8 +158,8 @@ async function promptAvailableProjectId() {
159
158
  name: projectId + (p.displayName ? ` (${p.displayName})` : ""),
160
159
  value: projectId,
161
160
  };
162
- });
163
- choices = _.orderBy(choices, ["name"], ["asc"]);
161
+ })
162
+ .sort((a, b) => a.name.localeCompare(b.name));
164
163
  return await (0, prompt_1.promptOnce)({
165
164
  type: "list",
166
165
  name: "id",
@@ -303,14 +303,14 @@ class ProfileReport {
303
303
  });
304
304
  });
305
305
  const paths = Object.keys(unindexed);
306
- paths.forEach((path) => {
306
+ for (const path of paths) {
307
307
  const indices = Object.keys(unindexed[path]);
308
- indices.forEach((index) => {
308
+ for (const index of indices) {
309
309
  const data = unindexed[path][index];
310
310
  const row = [path, extractReadableIndex(data.query), formatNumber(data.times)];
311
311
  table.push(row);
312
- });
313
- });
312
+ }
313
+ }
314
314
  return table;
315
315
  }
316
316
  renderBandwidth(pureData) {
@@ -327,8 +327,10 @@ class ProfileReport {
327
327
  times: b1.times + b2.times,
328
328
  };
329
329
  });
330
- const paths = _.orderBy(Object.keys(data), [(p) => data[p].bytes], ["desc"]);
331
- paths.forEach((path) => {
330
+ const paths = Object.keys(data).sort((a, b) => {
331
+ return data[b].bytes - data[a].bytes;
332
+ });
333
+ for (const path of paths) {
332
334
  const bandwidth = data[path];
333
335
  const row = [
334
336
  path,
@@ -337,7 +339,7 @@ class ProfileReport {
337
339
  formatBytes(bandwidth.bytes / bandwidth.times),
338
340
  ];
339
341
  table.push(row);
340
- });
342
+ }
341
343
  return table;
342
344
  }
343
345
  renderOutgoingBandwidth() {
@@ -392,12 +394,12 @@ class ProfileReport {
392
394
  rejected: s1.rejected + s2.rejected,
393
395
  };
394
396
  });
395
- let paths = Object.keys(data);
396
- paths = _.orderBy(paths, (path) => {
397
- const speed = data[path];
398
- return speed.millis / speed.times;
399
- }, ["desc"]);
400
- paths.forEach((path) => {
397
+ const paths = Object.keys(data).sort((a, b) => {
398
+ const speedA = data[a].millis / data[a].times;
399
+ const speedB = data[b].millis / data[b].times;
400
+ return speedB - speedA;
401
+ });
402
+ for (const path of paths) {
401
403
  const speed = data[path];
402
404
  const row = [
403
405
  path,
@@ -409,7 +411,7 @@ class ProfileReport {
409
411
  row.push(formatNumber(speed.rejected));
410
412
  }
411
413
  table.push(row);
412
- });
414
+ }
413
415
  return table;
414
416
  }
415
417
  renderReadSpeed() {
package/lib/rc.js CHANGED
@@ -25,7 +25,7 @@ exports.loadRC = loadRC;
25
25
  class RC {
26
26
  constructor(rcpath, data) {
27
27
  this.path = rcpath;
28
- this.data = Object.assign({ projects: {}, targets: {} }, data);
28
+ this.data = Object.assign({ projects: {}, targets: {}, etags: {} }, data);
29
29
  }
30
30
  static loadFile(rcpath) {
31
31
  let data = {};
@@ -148,6 +148,16 @@ class RC {
148
148
  }
149
149
  return target;
150
150
  }
151
+ getEtags(projectId) {
152
+ return this.data.etags[projectId] || { extensionInstances: {} };
153
+ }
154
+ setEtags(projectId, resourceType, etagData) {
155
+ if (!this.data.etags[projectId]) {
156
+ this.data.etags[projectId] = {};
157
+ }
158
+ this.data.etags[projectId][resourceType] = etagData;
159
+ this.save();
160
+ }
151
161
  save() {
152
162
  if (this.path) {
153
163
  fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2), {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "11.2.1",
3
+ "version": "11.2.2",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "firebase-tools",
9
- "version": "11.2.1",
9
+ "version": "11.2.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@google-cloud/pubsub": "^3.0.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "11.2.1",
3
+ "version": "11.2.2",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {