firebase-tools 11.2.0 → 11.3.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.
@@ -20,7 +20,7 @@ function targetsHaveNoFilters(...targets) {
20
20
  return targets.some((t) => !t.includes(":"));
21
21
  }
22
22
  async function checkValidTargetFilters(options) {
23
- const only = (options.only || "").split(",");
23
+ const only = !options.only ? [] : options.only.split(",");
24
24
  return new Promise((resolve, reject) => {
25
25
  if (!only.length) {
26
26
  return resolve();
@@ -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,13 +84,16 @@ 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 = {};
112
95
  if (endpoint.httpsTrigger.invoker) {
113
- bkHttps.invoker = [endpoint.httpsTrigger.invoker];
96
+ bkHttps.invoker = endpoint.httpsTrigger.invoker;
114
97
  }
115
98
  trigger = { httpsTrigger: bkHttps };
116
99
  }
@@ -122,22 +105,30 @@ 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
  };
116
+ if (endpoint.eventTrigger.eventFilterPathPatterns) {
117
+ const bkEventFiltersPathPatterns = {};
118
+ for (const [key, value] of Object.entries(endpoint.eventTrigger.eventFilterPathPatterns)) {
119
+ bkEventFiltersPathPatterns[key] = params.resolveString(value, paramValues);
120
+ }
121
+ bkEvent.eventFilterPathPatterns = bkEventFiltersPathPatterns;
122
+ }
135
123
  if (endpoint.eventTrigger.serviceAccount) {
136
124
  bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount;
137
125
  }
138
126
  if (endpoint.eventTrigger.region) {
139
127
  bkEvent.region = resolveString(endpoint.eventTrigger.region);
140
128
  }
129
+ if (endpoint.eventTrigger.channel) {
130
+ bkEvent.channel = endpoint.eventTrigger.channel;
131
+ }
141
132
  trigger = { eventTrigger: bkEvent };
142
133
  }
143
134
  else if ("scheduleTrigger" in endpoint) {
@@ -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
+ }
@@ -39,7 +39,7 @@ async function prepare(context, options, payload) {
39
39
  ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
40
40
  ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
41
41
  ensure.cloudBuildEnabled(projectId),
42
- ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "artifactregistry"),
42
+ ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "artifactregistry"),
43
43
  ]);
44
44
  const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
45
45
  context.firebaseConfig = firebaseConfig;
@@ -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;
@@ -126,7 +126,7 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
126
126
  logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
127
127
  }
128
128
  if (annotation.httpsTrigger.invoker) {
129
- trigger.invoker = annotation.httpsTrigger.invoker[0];
129
+ trigger.invoker = annotation.httpsTrigger.invoker;
130
130
  }
131
131
  triggered = { httpsTrigger: trigger };
132
132
  }
@@ -179,7 +179,7 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
179
179
  };
180
180
  }
181
181
  const endpointId = annotation.name;
182
- const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime, serviceAccount: annotation.serviceAccountEmail || "default" }, triggered);
182
+ const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime, serviceAccount: annotation.serviceAccountEmail || null }, triggered);
183
183
  if (annotation.vpcConnector != null) {
184
184
  let maybeId = annotation.vpcConnector;
185
185
  if (maybeId && !maybeId.includes("/")) {
@@ -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);
@@ -20,7 +20,7 @@ async function downloadToTmp(remoteUrl) {
20
20
  resolveOnHTTPError: true,
21
21
  });
22
22
  if (res.status !== 200) {
23
- throw new error_1.FirebaseError(`download failed, status ${res.status}`, { exit: 1 });
23
+ throw new error_1.FirebaseError(`download failed, status ${res.status}: ${await res.response.text()}`);
24
24
  }
25
25
  const total = parseInt(res.response.headers.get("content-length") || "0", 10);
26
26
  const totalMb = Math.ceil(total / 1000000);
@@ -29,13 +29,13 @@ exports.DownloadDetails = {
29
29
  },
30
30
  },
31
31
  firestore: {
32
- downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.3.jar"),
33
- version: "1.14.3",
32
+ downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.4.jar"),
33
+ version: "1.14.4",
34
34
  opts: {
35
35
  cacheDir: CACHE_DIR,
36
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.3.jar",
37
- expectedSize: 60442855,
38
- expectedChecksum: "63517534875818689639ee5dee57dd52",
36
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.4.jar",
37
+ expectedSize: 61017177,
38
+ expectedChecksum: "953d10e73798484aa0b84c45005faadb",
39
39
  namePrefix: "cloud-firestore-emulator",
40
40
  },
41
41
  },
@@ -55,7 +55,7 @@ exports.DownloadDetails = {
55
55
  version: "SNAPSHOT",
56
56
  downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
57
57
  unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
58
- binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
58
+ binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server", "server.js"),
59
59
  opts: {
60
60
  cacheDir: CACHE_DIR,
61
61
  remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
@@ -67,15 +67,15 @@ exports.DownloadDetails = {
67
67
  },
68
68
  }
69
69
  : {
70
- version: "1.7.0",
71
- downloadPath: path.join(CACHE_DIR, "ui-v1.7.0.zip"),
72
- unzipDir: path.join(CACHE_DIR, "ui-v1.7.0"),
73
- binaryPath: path.join(CACHE_DIR, "ui-v1.7.0", "server.bundle.js"),
70
+ version: "1.8.1",
71
+ downloadPath: path.join(CACHE_DIR, "ui-v1.8.1.zip"),
72
+ unzipDir: path.join(CACHE_DIR, "ui-v1.8.1"),
73
+ binaryPath: path.join(CACHE_DIR, "ui-v1.8.1", "server", "server.js"),
74
74
  opts: {
75
75
  cacheDir: CACHE_DIR,
76
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.7.0.zip",
77
- expectedSize: 4053708,
78
- expectedChecksum: "aea9ae19091df5974a88a8847aaf127c",
76
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.8.1.zip",
77
+ expectedSize: 3056552,
78
+ expectedChecksum: "92590fdda20f9883588438d9551111b5",
79
79
  namePrefix: "ui",
80
80
  },
81
81
  },
@@ -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) {
@@ -778,9 +783,10 @@ class FunctionsEmulator {
778
783
  }
779
784
  const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
780
785
  const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
786
+ const socketPath = (0, functionsEmulatorShared_1.getTemporarySocketPath)();
781
787
  const childProcess = spawn(opts.nodeBinary, args, {
782
788
  cwd: backend.functionsDir,
783
- env: Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs),
789
+ env: Object.assign(Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs), { PORT: socketPath }),
784
790
  stdio: ["pipe", "pipe", "pipe", "ipc"],
785
791
  });
786
792
  if (!childProcess.stderr) {
@@ -812,6 +818,7 @@ class FunctionsEmulator {
812
818
  }),
813
819
  events: emitter,
814
820
  cwd: backend.functionsDir,
821
+ socketPath,
815
822
  shutdown: () => {
816
823
  childProcess.kill();
817
824
  },
@@ -950,12 +957,6 @@ class FunctionsEmulator {
950
957
  await worker.waitForSocketReady();
951
958
  void (0, track_1.track)(EVENT_INVOKE, "https");
952
959
  this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
953
- if (!worker.lastArgs) {
954
- throw new error_1.FirebaseError("Cannot execute on a worker with no arguments");
955
- }
956
- if (!worker.lastArgs.frb.socketPath) {
957
- throw new error_1.FirebaseError(`Cannot execute on a worker without a socketPath: ${JSON.stringify(worker.lastArgs)}`);
958
- }
959
960
  const url = new url_1.URL(`${req.protocol}://${req.hostname}${req.url}`);
960
961
  const path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${triggerName}\/?`), "/");
961
962
  this.logger.log("DEBUG", `[functions] Got req.url=${req.url}, mapping to path=${path}`);
@@ -963,7 +964,7 @@ class FunctionsEmulator {
963
964
  method,
964
965
  path,
965
966
  headers: req.headers,
966
- socketPath: worker.lastArgs.frb.socketPath,
967
+ socketPath: worker.runtime.socketPath,
967
968
  }, (runtimeRes) => {
968
969
  function forwardStatusAndHeaders() {
969
970
  res.status(runtimeRes.statusCode || 200);