firebase-tools 11.2.2 → 11.4.1

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.
package/README.md CHANGED
@@ -174,11 +174,11 @@ Use `firebase:deploy --only remoteconfig` to update and publish a project's Fire
174
174
 
175
175
  The Firebase CLI can use one of four authentication methods listed in descending priority:
176
176
 
177
- - **User Token** - provide an explicit long-lived Firebase user token generated from `firebase login:ci`. Note that these tokens are extremely sensitive long-lived credentials and are not the right option for most cases. Consider using service account authorization instead. The token can be set in one of two ways:
177
+ - **User Token** - **DEPRECATED: this authentication method will be removed in a future major version of `firebase-tools`; use a service account to authenticate instead** - provide an explicit long-lived Firebase user token generated from `firebase login:ci`. Note that these tokens are extremely sensitive long-lived credentials and are not the right option for most cases. Consider using service account authorization instead. The token can be set in one of two ways:
178
178
  - Set the `--token` flag on any command, for example `firebase --token="<token>" projects:list`.
179
179
  - Set the `FIREBASE_TOKEN` environment variable.
180
180
  - **Local Login** - run `firebase login` to log in to the CLI directly as yourself. The CLI will cache an authorized user credential on your machine.
181
- - **Service Account** - set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to point to the path of a JSON service account key file.
181
+ - **Service Account** - set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to point to the path of a JSON service account key file. For more details, see Google Cloud's [Getting started with authentication](https://cloud.google.com/docs/authentication/getting-started) guide.
182
182
  - **Application Default Credentials** - if you use the `gcloud` CLI and log in with `gcloud auth application-default login`, the Firebase CLI will use them if none of the above credentials are present.
183
183
 
184
184
  ### Multiple Accounts
@@ -224,21 +224,14 @@ or `HTTP_PROXY` value in your environment to the URL of your proxy (e.g.
224
224
  The Firebase CLI requires a browser to complete authentication, but is fully
225
225
  compatible with CI and other headless environments.
226
226
 
227
- 1. On a machine with a browser, install the Firebase CLI.
228
- 2. Run `firebase login:ci` to log in and print out a new [refresh token](https://developers.google.com/identity/protocols/OAuth2)
229
- (the current CLI session will not be affected).
230
- 3. Store the output token in a secure but accessible way in your CI system.
227
+ Complete the following steps to run Firebase commands in a CI environment. Find detailed instructions for each step in Google Cloud's [Getting started with authentication](https://cloud.google.com/docs/authentication/getting-started) guide.
231
228
 
232
- There are two ways to use this token when running Firebase commands:
229
+ 1. Create a service account and grant it the appropriate level of access to your project.
230
+ 1. Create a service account key (JSON file) for that service account.
231
+ 1. Store the key file in a secure, accessible way in your CI system.
232
+ 1. Set `GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json` in your CI system when running Firebase commands.
233
233
 
234
- 1. Store the token as the environment variable `FIREBASE_TOKEN` and it will
235
- automatically be utilized.
236
- 2. Run all commands with the `--token <token>` flag in your CI system.
237
-
238
- The order of precedence for token loading is flag, environment variable, active project.
239
-
240
- On any machine with the Firebase CLI, running `firebase logout --token <token>`
241
- will immediately revoke access for the specified token.
234
+ To disable access for the service account, [find the service account](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts) for your project in the Google Cloud Console, and then either remove the key, or disable or delete the service account.
242
235
 
243
236
  ## Using as a Module
244
237
 
package/lib/apiv2.js CHANGED
@@ -15,6 +15,7 @@ const responseToError_1 = require("./responseToError");
15
15
  const FormData = require("form-data");
16
16
  const pkg = require("../package.json");
17
17
  const CLI_VERSION = pkg.version;
18
+ const GOOG_QUOTA_USER = "x-goog-quota-user";
18
19
  let accessToken = "";
19
20
  let refreshToken = "";
20
21
  function setRefreshToken(token = "") {
@@ -309,6 +310,10 @@ class Client {
309
310
  }
310
311
  const logURL = this.requestURL(options);
311
312
  logger_1.logger.debug(`>>> [apiv2][query] ${options.method} ${logURL} ${queryParamsLog}`);
313
+ const headers = options.headers;
314
+ if (headers && headers.has(GOOG_QUOTA_USER)) {
315
+ logger_1.logger.debug(`>>> [apiv2][(partial)header] ${options.method} ${logURL} x-goog-quota-user=${headers.get(GOOG_QUOTA_USER) || ""}`);
316
+ }
312
317
  if (options.body !== undefined) {
313
318
  let logBody = "[omitted]";
314
319
  if (!((_b = options.skipLog) === null || _b === void 0 ? void 0 : _b.body)) {
@@ -19,6 +19,7 @@ function targetsHaveFilters(...targets) {
19
19
  function targetsHaveNoFilters(...targets) {
20
20
  return targets.some((t) => !t.includes(":"));
21
21
  }
22
+ const FILTERABLE_TARGETS = new Set(["hosting", "functions", "firestore", "storage", "database"]);
22
23
  async function checkValidTargetFilters(options) {
23
24
  const only = !options.only ? [] : options.only.split(",");
24
25
  return new Promise((resolve, reject) => {
@@ -28,10 +29,10 @@ async function checkValidTargetFilters(options) {
28
29
  if (options.except) {
29
30
  return reject(new error_1.FirebaseError("Cannot specify both --only and --except"));
30
31
  }
31
- const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions", "firestore"].includes(t));
32
+ const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !FILTERABLE_TARGETS.has(t));
32
33
  const targetsForNonFilteredTypes = targetsForTypes(only, ...nonFilteredTypes);
33
34
  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, hosting, and firestore"));
35
+ return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions, hosting, storage, and firestore"));
35
36
  }
36
37
  const targetsForFunctions = targetsForTypes(only, "functions");
37
38
  if (targetsForFunctions.length &&
package/lib/command.js CHANGED
@@ -64,6 +64,7 @@ class Command {
64
64
  });
65
65
  if (this.helpText) {
66
66
  cmd.on("--help", () => {
67
+ console.log();
67
68
  console.log(this.helpText);
68
69
  });
69
70
  }
@@ -12,6 +12,11 @@ const { marked } = require("marked");
12
12
  const logger_1 = require("../logger");
13
13
  exports.command = new command_1.Command("hosting:clone <source> <targetChannel>")
14
14
  .description("clone a version from one site to another")
15
+ .help(`<source> and <targetChannel> accept the following format: <siteId>:<channelId>
16
+
17
+ For example, to copy the content for a site \`my-site\` from a preview channel \`staging\` to a \`live\` channel, the command would look be:
18
+
19
+ firebase hosting:clone my-site:foo my-site:live`)
15
20
  .before(requireAuth_1.requireAuth)
16
21
  .action(async (source = "", targetChannel = "") => {
17
22
  var _a, _b, _c, _d;
@@ -14,6 +14,8 @@ exports.command = new command_1.Command("login:ci")
14
14
  if (options.nonInteractive) {
15
15
  throw new error_1.FirebaseError("Cannot run login:ci in non-interactive mode.");
16
16
  }
17
+ utils.logWarning("Authenticating with a `login:ci` token is deprecated and will be removed in a future major version of `firebase-tools`. " +
18
+ "Instead, use a service account key with `GOOGLE_APPLICATION_CREDENTIALS`: https://cloud.google.com/docs/authentication/getting-started");
17
19
  const userCredentials = await auth.loginGoogle(options.localhost);
18
20
  logger_1.logger.info();
19
21
  utils.logSuccess("Success! Use this token to login on a CI server:\n\n" +
@@ -19,6 +19,23 @@ function getRulesConfig(projectId, options) {
19
19
  if (dbConfig === undefined) {
20
20
  return [];
21
21
  }
22
+ const rc = options.rc;
23
+ let allDatabases = !options.only;
24
+ const onlyDatabases = new Set();
25
+ if (options.only) {
26
+ const split = options.only.split(",");
27
+ if (split.includes("database")) {
28
+ allDatabases = true;
29
+ }
30
+ else {
31
+ for (const value of split) {
32
+ if (value.startsWith("database:")) {
33
+ const target = value.split(":")[1];
34
+ onlyDatabases.add(target);
35
+ }
36
+ }
37
+ }
38
+ }
22
39
  if (!Array.isArray(dbConfig)) {
23
40
  if (dbConfig && dbConfig.rules) {
24
41
  utils.assertIsStringOrUndefined(options.instance);
@@ -31,22 +48,32 @@ function getRulesConfig(projectId, options) {
31
48
  }
32
49
  }
33
50
  const results = [];
34
- const rc = options.rc;
35
51
  for (const c of dbConfig) {
36
- if (c.target) {
37
- rc.requireTarget(projectId, "database", c.target);
38
- const instances = rc.target(projectId, "database", c.target);
39
- for (const i of instances) {
40
- results.push({ instance: i, rules: c.rules });
52
+ const { instance, target } = c;
53
+ if (target) {
54
+ if (allDatabases || onlyDatabases.has(target)) {
55
+ rc.requireTarget(projectId, "database", target);
56
+ const instances = rc.target(projectId, "database", target);
57
+ for (const i of instances) {
58
+ results.push({ instance: i, rules: c.rules });
59
+ }
60
+ onlyDatabases.delete(target);
41
61
  }
42
62
  }
43
- else if (c.instance) {
44
- results.push(c);
63
+ else if (instance) {
64
+ if (allDatabases) {
65
+ results.push(c);
66
+ }
45
67
  }
46
68
  else {
47
69
  throw new error_1.FirebaseError('Must supply either "target" or "instance" in database config');
48
70
  }
49
71
  }
72
+ if (!allDatabases && onlyDatabases.size !== 0) {
73
+ throw new error_1.FirebaseError(`Could not find configurations in firebase.json for the following database targets: ${[
74
+ ...onlyDatabases,
75
+ ].join(", ")}`);
76
+ }
50
77
  return results;
51
78
  }
52
79
  exports.getRulesConfig = getRulesConfig;
@@ -93,7 +93,7 @@ function discoverTrigger(endpoint, paramValues) {
93
93
  if ("httpsTrigger" in endpoint) {
94
94
  const bkHttps = {};
95
95
  if (endpoint.httpsTrigger.invoker) {
96
- bkHttps.invoker = [endpoint.httpsTrigger.invoker];
96
+ bkHttps.invoker = endpoint.httpsTrigger.invoker;
97
97
  }
98
98
  trigger = { httpsTrigger: bkHttps };
99
99
  }
@@ -113,12 +113,22 @@ function discoverTrigger(endpoint, paramValues) {
113
113
  eventFilters: bkEventFilters,
114
114
  retry: resolveBoolean(endpoint.eventTrigger.retry || false),
115
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
+ }
116
123
  if (endpoint.eventTrigger.serviceAccount) {
117
124
  bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount;
118
125
  }
119
126
  if (endpoint.eventTrigger.region) {
120
127
  bkEvent.region = resolveString(endpoint.eventTrigger.region);
121
128
  }
129
+ if (endpoint.eventTrigger.channel) {
130
+ bkEvent.channel = endpoint.eventTrigger.channel;
131
+ }
122
132
  trigger = { eventTrigger: bkEvent };
123
133
  }
124
134
  else if ("scheduleTrigger" in endpoint) {
@@ -67,6 +67,10 @@ async function release(context, options, payload) {
67
67
  const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
68
68
  if (allErrors.length) {
69
69
  const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
70
+ logger_1.logger.debug("Functions deploy failed.");
71
+ for (const error of allErrors) {
72
+ logger_1.logger.debug(JSON.stringify(error, null, 2));
73
+ }
70
74
  throw new error_1.FirebaseError("There was an error deploying functions", Object.assign(Object.assign({}, opts), { exit: 2 }));
71
75
  }
72
76
  }
@@ -28,10 +28,12 @@ function buildFromV1Alpha1(yaml, project, region, runtime) {
28
28
  (0, parsing_1.requireKeys)("", manifest, "endpoints");
29
29
  (0, parsing_1.assertKeyTypes)("", manifest, {
30
30
  specVersion: "string",
31
+ params: "array",
31
32
  requiredAPIs: "array",
32
33
  endpoints: "object",
33
34
  });
34
35
  const bd = build.empty();
36
+ bd.params = manifest.params || [];
35
37
  bd.requiredAPIs = parseRequiredAPIs(manifest);
36
38
  for (const id of Object.keys(manifest.endpoints)) {
37
39
  const me = manifest.endpoints[id];
@@ -49,6 +51,7 @@ function backendFromV1Alpha1(yaml, project, region, runtime) {
49
51
  (0, parsing_1.requireKeys)("", manifest, "endpoints");
50
52
  (0, parsing_1.assertKeyTypes)("", manifest, {
51
53
  specVersion: "string",
54
+ params: "array",
52
55
  requiredAPIs: "array",
53
56
  endpoints: "object",
54
57
  });
@@ -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
  }
@@ -19,7 +19,7 @@ exports.obtainStorageBindings = obtainStorageBindings;
19
19
  async function ensureStorageTriggerRegion(endpoint) {
20
20
  const { eventTrigger } = endpoint;
21
21
  if (!eventTrigger.region) {
22
- logger_1.logger.debug("Looking up bucket region for the storage event trigger");
22
+ logger_1.logger.debug(`Looking up bucket region for the storage event trigger on bucket ${eventTrigger.eventFilters.bucket}`);
23
23
  try {
24
24
  const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
25
25
  eventTrigger.region = bucket.location.toLowerCase();
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertConfig = void 0;
4
4
  const error_1 = require("../../error");
5
5
  const backend_1 = require("../functions/backend");
6
+ const backend = require("../functions/backend");
7
+ const utils_1 = require("../../utils");
6
8
  function has(obj, k) {
7
9
  return obj[k] !== undefined;
8
10
  }
@@ -39,24 +41,66 @@ async function convertConfig(context, payload, config, finalize) {
39
41
  if (!config) {
40
42
  return out;
41
43
  }
42
- const endpointBeingDeployed = (serviceId, region = "us-central1") => {
43
- var _a;
44
+ const endpointFromBackend = (targetBackend, functionsEndpointInfo) => {
45
+ const backendsForId = backend.allEndpoints(targetBackend).filter((endpoint) => {
46
+ return endpoint.id === functionsEndpointInfo.serviceId;
47
+ });
48
+ const matchingBackends = backendsForId.filter((endpoint) => {
49
+ return ((!functionsEndpointInfo.region || endpoint.region === functionsEndpointInfo.region) &&
50
+ (!functionsEndpointInfo.platform || endpoint.platform === functionsEndpointInfo.platform));
51
+ });
52
+ if (matchingBackends.length > 1) {
53
+ throw new error_1.FirebaseError(`More than one backend found for function name: ${functionsEndpointInfo.serviceId}. If the function is deployed in multiple regions, you must specify a region.`);
54
+ }
55
+ if (matchingBackends.length === 1) {
56
+ const endpoint = matchingBackends[0];
57
+ if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint)) {
58
+ return endpoint;
59
+ }
60
+ }
61
+ return;
62
+ };
63
+ const endpointBeingDeployed = (functionsEndpointInfo) => {
44
64
  for (const { wantBackend } of Object.values(payload.functions || {})) {
45
- const endpoint = (_a = wantBackend === null || wantBackend === void 0 ? void 0 : wantBackend.endpoints[region]) === null || _a === void 0 ? void 0 : _a[serviceId];
46
- if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
65
+ if (!wantBackend) {
66
+ continue;
67
+ }
68
+ const endpoint = endpointFromBackend(wantBackend, functionsEndpointInfo);
69
+ if (endpoint) {
47
70
  return endpoint;
71
+ }
48
72
  }
49
- return undefined;
73
+ return;
50
74
  };
51
- const matchingEndpoint = async (serviceId, region = "us-central1") => {
52
- const pendingEndpoint = endpointBeingDeployed(serviceId, region);
75
+ const matchingEndpoint = async (functionsEndpointInfo) => {
76
+ const pendingEndpoint = endpointBeingDeployed(functionsEndpointInfo);
53
77
  if (pendingEndpoint)
54
78
  return pendingEndpoint;
55
79
  const backend = await (0, backend_1.existingBackend)(context);
56
80
  return (0, backend_1.allEndpoints)(backend).find((it) => (0, backend_1.isHttpsTriggered)(it) &&
57
- it.platform === "gcfv2" &&
58
- it.id === serviceId &&
59
- it.region === region);
81
+ it.id === functionsEndpointInfo.serviceId &&
82
+ (!functionsEndpointInfo.platform || it.platform === functionsEndpointInfo.platform) &&
83
+ (!functionsEndpointInfo.region || it.region === functionsEndpointInfo.region));
84
+ };
85
+ const findEndpointWithValidRegion = async (rewrite, context) => {
86
+ if ("function" in rewrite) {
87
+ const foundEndpointToBeDeployed = endpointBeingDeployed({
88
+ serviceId: rewrite.function,
89
+ region: rewrite.region,
90
+ });
91
+ if (foundEndpointToBeDeployed) {
92
+ return foundEndpointToBeDeployed;
93
+ }
94
+ const existingBackend = await backend.existingBackend(context);
95
+ const endpointAlreadyDeployed = endpointFromBackend(existingBackend, {
96
+ serviceId: rewrite.function,
97
+ region: rewrite.region,
98
+ });
99
+ if (endpointAlreadyDeployed) {
100
+ return endpointAlreadyDeployed;
101
+ }
102
+ }
103
+ return;
60
104
  };
61
105
  if (Array.isArray(config.rewrites)) {
62
106
  out.rewrites = [];
@@ -66,19 +110,33 @@ async function convertConfig(context, payload, config, finalize) {
66
110
  vRewrite.path = rewrite.destination;
67
111
  }
68
112
  else if ("function" in rewrite) {
69
- if (!finalize && endpointBeingDeployed(rewrite.function, rewrite.region))
113
+ if (!finalize &&
114
+ endpointBeingDeployed({
115
+ serviceId: rewrite.function,
116
+ platform: "gcfv2",
117
+ region: rewrite.region,
118
+ })) {
70
119
  continue;
71
- const endpoint = await matchingEndpoint(rewrite.function, rewrite.region);
120
+ }
121
+ const endpoint = await matchingEndpoint({
122
+ serviceId: rewrite.function,
123
+ platform: "gcfv2",
124
+ region: rewrite.region,
125
+ });
72
126
  if (endpoint) {
73
127
  vRewrite.run = { serviceId: endpoint.id, region: endpoint.region };
74
128
  }
75
129
  else {
76
130
  vRewrite.function = rewrite.function;
77
- if (rewrite.region) {
78
- vRewrite.functionRegion = rewrite.region;
131
+ const foundEndpoint = await findEndpointWithValidRegion(rewrite, context);
132
+ if (foundEndpoint) {
133
+ vRewrite.functionRegion = foundEndpoint.region;
79
134
  }
80
135
  else {
81
- vRewrite.functionRegion = "us-central1";
136
+ if (rewrite.region && rewrite.region !== "us-central1") {
137
+ throw new error_1.FirebaseError(`Unable to find a valid endpoint for function \`${vRewrite.function}\``);
138
+ }
139
+ (0, utils_1.logLabeledWarning)(`hosting[${config.site}]`, `Unable to find a valid endpoint for function \`${vRewrite.function}\`, but still including it in the config`);
82
140
  }
83
141
  }
84
142
  }
@@ -86,8 +144,14 @@ async function convertConfig(context, payload, config, finalize) {
86
144
  vRewrite.dynamicLinks = rewrite.dynamicLinks;
87
145
  }
88
146
  else if ("run" in rewrite) {
89
- if (!finalize && endpointBeingDeployed(rewrite.run.serviceId, rewrite.run.region))
147
+ if (!finalize &&
148
+ endpointBeingDeployed({
149
+ serviceId: rewrite.run.serviceId,
150
+ platform: "gcfv2",
151
+ region: rewrite.run.region,
152
+ })) {
90
153
  continue;
154
+ }
91
155
  vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run);
92
156
  }
93
157
  out.rewrites.push(vRewrite);
@@ -36,7 +36,7 @@ async function deploy(context, options) {
36
36
  (0, utils_1.logLabeledBullet)(`hosting[${deploy.site}]`, 'no "public" directory to upload, continuing with release');
37
37
  return runDeploys(deploys, debugging);
38
38
  }
39
- (0, utils_1.logLabeledBullet)("hosting[" + deploy.site + "]", "beginning deploy...");
39
+ (0, utils_1.logLabeledBullet)(`hosting[${deploy.site}]`, "beginning deploy...");
40
40
  const t0 = Date.now();
41
41
  const publicDir = options.config.path(deploy.config.public);
42
42
  const files = (0, listFiles_1.listFiles)(publicDir, deploy.config.ignore);
@@ -52,7 +52,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
52
52
  for (const targetName of targetNames) {
53
53
  const target = TARGETS[targetName];
54
54
  if (!target) {
55
- return Promise.reject(new error_1.FirebaseError((0, cli_color_1.bold)(targetName) + " is not a valid deploy target", { exit: 1 }));
55
+ return Promise.reject(new error_1.FirebaseError(`${(0, cli_color_1.bold)(targetName)} is not a valid deploy target`));
56
56
  }
57
57
  predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
58
58
  prepares.push(target.prepare);
@@ -3,25 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const _ = require("lodash");
4
4
  const gcp = require("../../gcp");
5
5
  const rulesDeploy_1 = require("../../rulesDeploy");
6
+ const error_1 = require("../../error");
6
7
  async function default_1(context, options) {
7
8
  let rulesConfig = options.config.get("storage");
8
9
  if (!rulesConfig) {
9
10
  return;
10
11
  }
12
+ const onlyTargets = new Set();
13
+ let allStorage = !options.only;
14
+ if (options.only) {
15
+ const split = options.only.split(",");
16
+ if (split.includes("storage")) {
17
+ allStorage = true;
18
+ }
19
+ else {
20
+ for (const value of split) {
21
+ if (value.startsWith("storage:")) {
22
+ onlyTargets.add(value.split(":")[1]);
23
+ }
24
+ }
25
+ }
26
+ }
11
27
  _.set(context, "storage.rules", rulesConfig);
12
28
  const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
13
29
  _.set(context, "storage.rulesDeploy", rulesDeploy);
14
- if (typeof rulesConfig === "object" && rulesConfig !== null) {
30
+ if (!Array.isArray(rulesConfig)) {
15
31
  const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
16
32
  rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
17
33
  _.set(context, "storage.rules", rulesConfig);
18
34
  }
19
- rulesConfig.forEach((ruleConfig) => {
20
- if (ruleConfig.target) {
21
- options.rc.requireTarget(context.projectId, "storage", ruleConfig.target);
35
+ for (const ruleConfig of rulesConfig) {
36
+ const target = ruleConfig.target;
37
+ if (target) {
38
+ options.rc.requireTarget(context.projectId, "storage", target);
22
39
  }
23
- rulesDeploy.addFile(ruleConfig.rules);
24
- });
40
+ if (allStorage || onlyTargets.has(target)) {
41
+ rulesDeploy.addFile(ruleConfig.rules);
42
+ onlyTargets.delete(target);
43
+ }
44
+ }
45
+ if (!allStorage && onlyTargets.size !== 0) {
46
+ throw new error_1.FirebaseError(`Could not find rules for the following storage targets: ${[...onlyTargets].join(", ")}`);
47
+ }
25
48
  await rulesDeploy.compile();
26
49
  }
27
50
  exports.default = default_1;
@@ -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);
@@ -301,7 +301,6 @@ async function startAll(options, showUI = true) {
301
301
  const extensionsBackends = await extensionEmulator.getExtensionBackends();
302
302
  const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
303
303
  emulatableBackends.push(...filteredExtensionsBackends);
304
- void (0, track_1.track)("Emulator Run", types_1.Emulators.EXTENSIONS);
305
304
  await startEmulator(extensionEmulator);
306
305
  }
307
306
  if (emulatableBackends.length) {
@@ -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
  },
@@ -646,6 +646,9 @@ class FunctionsEmulator {
646
646
  envs.GCLOUD_PROJECT = this.args.projectId;
647
647
  envs.K_REVISION = "1";
648
648
  envs.PORT = "80";
649
+ if (trigger === null || trigger === void 0 ? void 0 : trigger.timeoutSeconds) {
650
+ envs.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS = trigger.timeoutSeconds.toString();
651
+ }
649
652
  if (trigger) {
650
653
  const target = trigger.entryPoint;
651
654
  envs.FUNCTION_TARGET = target;