firebase-tools 11.15.0 → 11.16.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.
Files changed (38) hide show
  1. package/lib/commands/database-push.js +5 -0
  2. package/lib/commands/database-remove.js +2 -1
  3. package/lib/commands/database-set.js +5 -0
  4. package/lib/commands/database-update.js +5 -0
  5. package/lib/database/remove.js +2 -2
  6. package/lib/database/removeRemote.js +7 -2
  7. package/lib/deploy/functions/build.js +3 -0
  8. package/lib/deploy/functions/prepare.js +21 -8
  9. package/lib/deploy/functions/release/fabricator.js +2 -2
  10. package/lib/deploy/functions/release/sourceTokenScraper.js +34 -7
  11. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
  12. package/lib/deploy/hosting/convertConfig.js +21 -7
  13. package/lib/deploy/hosting/prepare.js +16 -5
  14. package/lib/deploy/hosting/release.js +2 -2
  15. package/lib/dynamicImport.js +7 -10
  16. package/lib/emulator/auth/server.js +2 -3
  17. package/lib/emulator/download.js +2 -1
  18. package/lib/emulator/downloadableEmulators.js +65 -50
  19. package/lib/emulator/extensionsEmulator.js +1 -1
  20. package/lib/emulator/functionsEmulator.js +2 -1
  21. package/lib/emulator/functionsEmulatorShared.js +12 -1
  22. package/lib/emulator/functionsRuntimeWorker.js +9 -2
  23. package/lib/emulator/storage/apis/firebase.js +7 -27
  24. package/lib/emulator/storage/apis/gcloud.js +9 -33
  25. package/lib/emulator/storage/apis/shared.js +43 -0
  26. package/lib/emulator/storage/rules/config.js +9 -0
  27. package/lib/emulator/storage/upload.js +2 -2
  28. package/lib/experiments.js +1 -3
  29. package/lib/extensions/provisioningHelper.js +10 -4
  30. package/lib/frameworks/index.js +9 -4
  31. package/lib/frameworks/next/index.js +57 -17
  32. package/lib/serve/hosting.js +4 -4
  33. package/npm-shrinkwrap.json +600 -50
  34. package/package.json +2 -2
  35. package/templates/emulators/default_storage.rules +8 -0
  36. package/templates/init/functions/golang/functions.go +1 -1
  37. package/templates/init/functions/javascript/index.js +2 -2
  38. package/templates/init/functions/typescript/index.ts +1 -1
@@ -19,6 +19,7 @@ exports.command = new command_1.Command("database:push <path> [infile]")
19
19
  .description("add a new JSON object to a list of data in your Firebase")
20
20
  .option("-d, --data <data>", "specify escaped JSON directly")
21
21
  .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
22
+ .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
22
23
  .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
23
24
  .before(requireDatabaseInstance_1.requireDatabaseInstance)
24
25
  .before(database_1.populateInstanceDetails)
@@ -30,6 +31,9 @@ exports.command = new command_1.Command("database:push <path> [infile]")
30
31
  const inStream = utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin);
31
32
  const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
32
33
  const u = new url_1.URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
34
+ if (options.disableTriggers) {
35
+ u.searchParams.set("disableTriggers", "true");
36
+ }
33
37
  if (!infile && !options.data) {
34
38
  utils.explainStdin();
35
39
  }
@@ -41,6 +45,7 @@ exports.command = new command_1.Command("database:push <path> [infile]")
41
45
  method: "POST",
42
46
  path: u.pathname,
43
47
  body: inStream,
48
+ queryParams: u.searchParams,
44
49
  });
45
50
  }
46
51
  catch (err) {
@@ -16,6 +16,7 @@ exports.command = new command_1.Command("database:remove <path>")
16
16
  .description("remove data from your Firebase at the specified path")
17
17
  .option("-f, --force", "pass this option to bypass confirmation prompt")
18
18
  .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
19
+ .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
19
20
  .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
20
21
  .before(requireDatabaseInstance_1.requireDatabaseInstance)
21
22
  .before(database_1.populateInstanceDetails)
@@ -35,7 +36,7 @@ exports.command = new command_1.Command("database:remove <path>")
35
36
  if (!confirm) {
36
37
  return utils.reject("Command aborted.", { exit: 1 });
37
38
  }
38
- const removeOps = new remove_1.default(options.instance, path, origin);
39
+ const removeOps = new remove_1.default(options.instance, path, origin, !!options.disableTriggers);
39
40
  await removeOps.execute();
40
41
  utils.logSuccess("Data removed successfully");
41
42
  });
@@ -21,6 +21,7 @@ exports.command = new command_1.Command("database:set <path> [infile]")
21
21
  .option("-d, --data <data>", "specify escaped JSON directly")
22
22
  .option("-f, --force", "pass this option to bypass confirmation prompt")
23
23
  .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
24
+ .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
24
25
  .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
25
26
  .before(requireDatabaseInstance_1.requireDatabaseInstance)
26
27
  .before(database_1.populateInstanceDetails)
@@ -32,6 +33,9 @@ exports.command = new command_1.Command("database:set <path> [infile]")
32
33
  const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
33
34
  const dbPath = utils.getDatabaseUrl(origin, options.instance, path);
34
35
  const dbJsonURL = new url_1.URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
36
+ if (options.disableTriggers) {
37
+ dbJsonURL.searchParams.set("disableTriggers", "true");
38
+ }
35
39
  const confirm = await (0, prompt_1.promptOnce)({
36
40
  type: "confirm",
37
41
  name: "force",
@@ -51,6 +55,7 @@ exports.command = new command_1.Command("database:set <path> [infile]")
51
55
  method: "PUT",
52
56
  path: dbJsonURL.pathname,
53
57
  body: inStream,
58
+ queryParams: dbJsonURL.searchParams,
54
59
  });
55
60
  }
56
61
  catch (err) {
@@ -21,6 +21,7 @@ exports.command = new command_1.Command("database:update <path> [infile]")
21
21
  .option("-d, --data <data>", "specify escaped JSON directly")
22
22
  .option("-f, --force", "pass this option to bypass confirmation prompt")
23
23
  .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
24
+ .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
24
25
  .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
25
26
  .before(requireDatabaseInstance_1.requireDatabaseInstance)
26
27
  .before(database_1.populateInstanceDetails)
@@ -44,6 +45,9 @@ exports.command = new command_1.Command("database:update <path> [infile]")
44
45
  (infile && fs.createReadStream(infile)) ||
45
46
  process.stdin;
46
47
  const jsonUrl = new url_1.URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
48
+ if (options.disableTriggers) {
49
+ jsonUrl.searchParams.set("disableTriggers", "true");
50
+ }
47
51
  if (!infile && !options.data) {
48
52
  utils.explainStdin();
49
53
  }
@@ -53,6 +57,7 @@ exports.command = new command_1.Command("database:update <path> [infile]")
53
57
  method: "PATCH",
54
58
  path: jsonUrl.pathname,
55
59
  body: inStream,
60
+ queryParams: jsonUrl.searchParams,
56
61
  });
57
62
  }
58
63
  catch (err) {
@@ -15,9 +15,9 @@ const INITIAL_DELETE_BATCH_SIZE = 25;
15
15
  const INITIAL_LIST_NUM_SUB_PATH = 100;
16
16
  const MAX_LIST_NUM_SUB_PATH = 204800;
17
17
  class DatabaseRemove {
18
- constructor(instance, path, host) {
18
+ constructor(instance, path, host, disableTriggers) {
19
19
  this.path = path;
20
- this.remote = new removeRemote_1.RTDBRemoveRemote(instance, host);
20
+ this.remote = new removeRemote_1.RTDBRemoveRemote(instance, host, disableTriggers);
21
21
  this.deleteJobStack = new stack_1.Stack({
22
22
  name: "delete stack",
23
23
  concurrency: 1,
@@ -6,9 +6,10 @@ const url_1 = require("url");
6
6
  const logger_1 = require("../logger");
7
7
  const utils = require("../utils");
8
8
  class RTDBRemoveRemote {
9
- constructor(instance, host) {
9
+ constructor(instance, host, disableTriggers) {
10
10
  this.instance = instance;
11
11
  this.host = host;
12
+ this.disableTriggers = disableTriggers;
12
13
  const url = new url_1.URL(utils.getDatabaseUrl(this.host, this.instance, "/"));
13
14
  this.apiClient = new apiv2_1.Client({ urlPrefix: url.origin, auth: true });
14
15
  }
@@ -25,7 +26,11 @@ class RTDBRemoveRemote {
25
26
  async patch(path, body, note) {
26
27
  const t0 = Date.now();
27
28
  const url = new url_1.URL(utils.getDatabaseUrl(this.host, this.instance, path + ".json"));
28
- const queryParams = { print: "silent", writeSizeLimit: "tiny" };
29
+ const queryParams = {
30
+ print: "silent",
31
+ writeSizeLimit: "tiny",
32
+ disableTriggers: this.disableTriggers.toString(),
33
+ };
29
34
  const res = await this.apiClient.request({
30
35
  method: "PATCH",
31
36
  path: url.pathname,
@@ -160,6 +160,9 @@ function toBackend(build, paramValues) {
160
160
  const bkEndpoints = [];
161
161
  for (const endpointId of Object.keys(build.endpoints)) {
162
162
  const bdEndpoint = build.endpoints[endpointId];
163
+ if (r.resolveBoolean(bdEndpoint.omit || false)) {
164
+ continue;
165
+ }
163
166
  let regions = bdEndpoint.region;
164
167
  if (typeof regions === "undefined") {
165
168
  regions = [api.functionsDefaultRegion];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveCpu = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = void 0;
3
+ exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  const clc = require("colorette");
5
5
  const backend = require("./backend");
6
6
  const build = require("./build");
@@ -25,6 +25,8 @@ const v1_1 = require("../../functions/events/v1");
25
25
  const serviceusage_1 = require("../../gcp/serviceusage");
26
26
  const applyHash_1 = require("./cache/applyHash");
27
27
  const backend_1 = require("./backend");
28
+ const functional_1 = require("../../functional");
29
+ exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
28
30
  function hasUserConfig(config) {
29
31
  return Object.keys(config).length > 1;
30
32
  }
@@ -94,7 +96,18 @@ async function prepare(context, options, payload) {
94
96
  }
95
97
  }
96
98
  for (const endpoint of backend.allEndpoints(wantBackend)) {
97
- endpoint.environmentVariables = wantBackend.environmentVariables;
99
+ endpoint.environmentVariables = wantBackend.environmentVariables || {};
100
+ let resource;
101
+ if (endpoint.platform === "gcfv1") {
102
+ resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;
103
+ }
104
+ else if (endpoint.platform === "gcfv2") {
105
+ resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`;
106
+ }
107
+ else {
108
+ (0, functional_1.assertExhaustive)(endpoint.platform);
109
+ }
110
+ endpoint.environmentVariables[exports.EVENTARC_SOURCE_ENV] = resource;
98
111
  endpoint.codebase = codebase;
99
112
  }
100
113
  wantBackends[codebase] = wantBackend;
@@ -143,7 +156,7 @@ async function prepare(context, options, payload) {
143
156
  for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) {
144
157
  inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
145
158
  await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
146
- resolveCpu(wantBackend);
159
+ resolveCpuAndConcurrency(wantBackend);
147
160
  validate.endpointsAreValid(wantBackend);
148
161
  inferBlockingDetails(wantBackend);
149
162
  }
@@ -205,9 +218,6 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
205
218
  if (typeof wantE.availableMemoryMb === "undefined" && haveE.availableMemoryMb) {
206
219
  wantE.availableMemoryMb = haveE.availableMemoryMb;
207
220
  }
208
- if (typeof wantE.concurrency === "undefined" && haveE.concurrency) {
209
- wantE.concurrency = haveE.concurrency;
210
- }
211
221
  if (typeof wantE.cpu === "undefined" && haveE.cpu) {
212
222
  wantE.cpu = haveE.cpu;
213
223
  }
@@ -264,7 +274,7 @@ function inferBlockingDetails(want) {
264
274
  }
265
275
  }
266
276
  exports.inferBlockingDetails = inferBlockingDetails;
267
- function resolveCpu(want) {
277
+ function resolveCpuAndConcurrency(want) {
268
278
  for (const e of backend.allEndpoints(want)) {
269
279
  if (e.platform === "gcfv1") {
270
280
  continue;
@@ -275,6 +285,9 @@ function resolveCpu(want) {
275
285
  else if (!e.cpu) {
276
286
  e.cpu = backend.memoryToGen2Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY);
277
287
  }
288
+ if (!e.concurrency) {
289
+ e.concurrency = e.cpu >= 1 ? backend.DEFAULT_CONCURRENCY : 1;
290
+ }
278
291
  }
279
292
  }
280
- exports.resolveCpu = resolveCpu;
293
+ exports.resolveCpuAndConcurrency = resolveCpuAndConcurrency;
@@ -167,9 +167,9 @@ class Fabricator {
167
167
  if (apiFunction.httpsTrigger) {
168
168
  apiFunction.httpsTrigger.securityLevel = "SECURE_ALWAYS";
169
169
  }
170
- apiFunction.sourceToken = await scraper.tokenPromise();
171
170
  const resultFunction = await this.functionExecutor
172
171
  .run(async () => {
172
+ apiFunction.sourceToken = await scraper.getToken();
173
173
  const op = await gcf.createFunction(apiFunction);
174
174
  return poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
175
175
  })
@@ -301,9 +301,9 @@ class Fabricator {
301
301
  throw new Error("Precondition failed");
302
302
  }
303
303
  const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl);
304
- apiFunction.sourceToken = await scraper.tokenPromise();
305
304
  const resultFunction = await this.functionExecutor
306
305
  .run(async () => {
306
+ apiFunction.sourceToken = await scraper.getToken();
307
307
  const op = await gcf.updateFunction(apiFunction);
308
308
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
309
309
  })
@@ -1,18 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SourceTokenScraper = void 0;
4
+ const error_1 = require("../../../error");
5
+ const functional_1 = require("../../../functional");
4
6
  const logger_1 = require("../../../logger");
5
7
  class SourceTokenScraper {
6
- constructor() {
7
- this.firstCall = true;
8
+ constructor(validDurationMs = 1500000) {
9
+ this.tokenValidDurationMs = validDurationMs;
8
10
  this.promise = new Promise((resolve) => (this.resolve = resolve));
11
+ this.fetchState = "NONE";
9
12
  }
10
- tokenPromise() {
11
- if (this.firstCall) {
12
- this.firstCall = false;
13
- return Promise.resolve(undefined);
13
+ async getToken() {
14
+ if (this.fetchState === "NONE") {
15
+ this.fetchState = "FETCHING";
16
+ return undefined;
14
17
  }
15
- return this.promise;
18
+ else if (this.fetchState === "FETCHING") {
19
+ return this.promise;
20
+ }
21
+ else if (this.fetchState === "VALID") {
22
+ if (this.isTokenExpired()) {
23
+ this.fetchState = "FETCHING";
24
+ this.promise = new Promise((resolve) => (this.resolve = resolve));
25
+ return undefined;
26
+ }
27
+ return this.promise;
28
+ }
29
+ else {
30
+ (0, functional_1.assertExhaustive)(this.fetchState);
31
+ }
32
+ }
33
+ isTokenExpired() {
34
+ if (this.expiry === undefined) {
35
+ throw new error_1.FirebaseError("Your deployment is checking the expiration of a source token that has not yet been polled. " +
36
+ "Hitting this case should never happen and should be considered a bug. " +
37
+ "Please file an issue at https://github.com/firebase/firebase-tools/issues " +
38
+ "and try deploying your functions again.");
39
+ }
40
+ return Date.now() >= this.expiry;
16
41
  }
17
42
  get poller() {
18
43
  return (op) => {
@@ -21,6 +46,8 @@ class SourceTokenScraper {
21
46
  const [, , , region] = ((_c = (_b = op.metadata) === null || _b === void 0 ? void 0 : _b.target) === null || _c === void 0 ? void 0 : _c.split("/")) || [];
22
47
  logger_1.logger.debug(`Got source token ${(_d = op.metadata) === null || _d === void 0 ? void 0 : _d.sourceToken} for region ${region}`);
23
48
  this.resolve((_e = op.metadata) === null || _e === void 0 ? void 0 : _e.sourceToken);
49
+ this.fetchState = "VALID";
50
+ this.expiry = Date.now() + this.tokenValidDurationMs;
24
51
  }
25
52
  };
26
53
  }
@@ -51,6 +51,7 @@ function assertBuildEndpoint(ep, id) {
51
51
  region: "array",
52
52
  platform: (platform) => build.AllFunctionsPlatforms.includes(platform),
53
53
  entryPoint: "string",
54
+ omit: "Field<boolean>?",
54
55
  availableMemoryMb: (mem) => mem === null || isCEL(mem) || build.isValidMemoryOption(mem),
55
56
  maxInstances: "Field<number>?",
56
57
  minInstances: "Field<number>?",
@@ -259,7 +260,7 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
259
260
  if ("serviceAccountEmail" in ep) {
260
261
  parsed.serviceAccount = ep.serviceAccountEmail;
261
262
  }
262
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "cpu", "maxInstances", "minInstances", "concurrency", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "serviceAccount");
263
+ (0, proto_1.copyIfPresent)(parsed, ep, "omit", "availableMemoryMb", "cpu", "maxInstances", "minInstances", "concurrency", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "serviceAccount");
263
264
  (0, proto_1.convertIfPresent)(parsed, ep, "secretEnvironmentVariables", (senvs) => {
264
265
  if (!senvs) {
265
266
  return null;
@@ -36,13 +36,13 @@ function extractPattern(type, source) {
36
36
  function findEndpointForRewrite(site, targetBackend, id, region) {
37
37
  const endpoints = backend.allEndpoints(targetBackend).filter((e) => e.id === id);
38
38
  if (endpoints.length === 0) {
39
- return;
39
+ return { matchingEndpoint: undefined, foundMatchingId: false };
40
40
  }
41
41
  if (endpoints.length === 1) {
42
42
  if (region && region !== endpoints[0].region) {
43
- return;
43
+ return { matchingEndpoint: undefined, foundMatchingId: true };
44
44
  }
45
- return endpoints[0];
45
+ return { matchingEndpoint: endpoints[0], foundMatchingId: true };
46
46
  }
47
47
  if (!region) {
48
48
  const us = endpoints.find((e) => e.region === "us-central1");
@@ -51,15 +51,19 @@ function findEndpointForRewrite(site, targetBackend, id, region) {
51
51
  }
52
52
  (0, utils_1.logLabeledBullet)(`hosting[${site}]`, `Function \`${id}\` found in multiple regions, defaulting to \`us-central1\`. ` +
53
53
  `To rewrite to a different region, specify a \`region\` for the rewrite in \`firebase.json\`.`);
54
- return us;
54
+ return { matchingEndpoint: us, foundMatchingId: true };
55
55
  }
56
- return endpoints.find((e) => e.region === region);
56
+ return {
57
+ matchingEndpoint: endpoints.find((e) => e.region === region),
58
+ foundMatchingId: true,
59
+ };
57
60
  }
58
61
  exports.findEndpointForRewrite = findEndpointForRewrite;
59
- async function convertConfig(context, deploy) {
62
+ async function convertConfig(context, functionsPayload, deploy) {
60
63
  var _a, _b, _c, _d;
61
64
  const config = {};
62
65
  const hasBackends = !!((_a = deploy.config.rewrites) === null || _a === void 0 ? void 0 : _a.some((r) => "function" in r || "run" in r));
66
+ const wantBackend = backend.merge(...Object.values(functionsPayload.functions || {}).map((c) => c.wantBackend));
63
67
  let haveBackend = backend.empty();
64
68
  if (hasBackends) {
65
69
  try {
@@ -87,8 +91,18 @@ async function convertConfig(context, deploy) {
87
91
  }
88
92
  const id = rewrite.function.functionId;
89
93
  const region = rewrite.function.region;
90
- const endpoint = findEndpointForRewrite(deploy.config.site, haveBackend, id, region);
94
+ const deployingEndpointSearch = findEndpointForRewrite(deploy.config.site, wantBackend, id, region);
95
+ const existingEndpointSearch = !deployingEndpointSearch.foundMatchingId && !deployingEndpointSearch.matchingEndpoint
96
+ ? findEndpointForRewrite(deploy.config.site, haveBackend, id, region)
97
+ : undefined;
98
+ const endpoint = deployingEndpointSearch.matchingEndpoint
99
+ ? deployingEndpointSearch.matchingEndpoint
100
+ : existingEndpointSearch === null || existingEndpointSearch === void 0 ? void 0 : existingEndpointSearch.matchingEndpoint;
91
101
  if (!endpoint) {
102
+ if (deployingEndpointSearch.foundMatchingId || (existingEndpointSearch === null || existingEndpointSearch === void 0 ? void 0 : existingEndpointSearch.foundMatchingId)) {
103
+ throw new error_1.FirebaseError(`Unable to find a valid endpoint for function. Functions matching the rewrite
104
+ are present but in the wrong region.`);
105
+ }
92
106
  (0, utils_1.logLabeledWarning)(`hosting[${deploy.config.site}]`, `Unable to find a valid endpoint for function \`${id}\`, but still including it in the config`);
93
107
  const apiRewrite = Object.assign(Object.assign({}, target), { function: id });
94
108
  if (region) {
@@ -6,6 +6,7 @@ const api = require("../../hosting/api");
6
6
  const config = require("../../hosting/config");
7
7
  const deploymentTool = require("../../deploymentTool");
8
8
  const functional_1 = require("../../functional");
9
+ const track_1 = require("../../track");
9
10
  async function prepare(context, options) {
10
11
  if (options.public) {
11
12
  if (Array.isArray(options.config.get("hosting"))) {
@@ -17,11 +18,21 @@ async function prepare(context, options) {
17
18
  if (configs.length === 0) {
18
19
  return Promise.resolve();
19
20
  }
20
- const version = {
21
- status: "CREATED",
22
- labels: deploymentTool.labels(),
23
- };
24
- const versions = await Promise.all(configs.map((config) => api.createVersion(config.site, version)));
21
+ const versions = await Promise.all(configs.map(async (config) => {
22
+ const labels = Object.assign({}, deploymentTool.labels());
23
+ if (config.webFramework) {
24
+ labels["firebase-web-framework"] = config.webFramework;
25
+ }
26
+ const version = {
27
+ status: "CREATED",
28
+ labels,
29
+ };
30
+ const [, versionName] = await Promise.all([
31
+ (0, track_1.track)("hosting_deploy", config.webFramework || "classic"),
32
+ api.createVersion(config.site, version),
33
+ ]);
34
+ return versionName;
35
+ }));
25
36
  context.hosting = {
26
37
  deploys: [],
27
38
  };
@@ -6,7 +6,7 @@ const logger_1 = require("../../logger");
6
6
  const utils = require("../../utils");
7
7
  const convertConfig_1 = require("./convertConfig");
8
8
  const error_1 = require("../../error");
9
- async function release(context, options) {
9
+ async function release(context, options, functionsPayload) {
10
10
  if (!context.hosting || !context.hosting.deploys) {
11
11
  return;
12
12
  }
@@ -18,7 +18,7 @@ async function release(context, options) {
18
18
  utils.logLabeledBullet(`hosting[${deploy.config.site}]`, "finalizing version...");
19
19
  const update = {
20
20
  status: "FINALIZED",
21
- config: await (0, convertConfig_1.convertConfig)(context, deploy),
21
+ config: await (0, convertConfig_1.convertConfig)(context, functionsPayload, deploy),
22
22
  };
23
23
  const versionId = utils.last(deploy.version.split("/"));
24
24
  const finalizedVersion = await api.updateVersion(deploy.config.site, versionId, update);
@@ -1,15 +1,12 @@
1
- "use strict";
2
1
  const { pathToFileURL } = require("url");
3
- exports.dynamicImport = function (mod) {
4
- if (mod.startsWith("file://"))
5
- return Promise.resolve().then(() => require(mod));
6
- if (mod.startsWith("/"))
7
- return Promise.resolve().then(() => require(pathToFileURL(mod).toString()));
2
+
3
+ exports.dynamicImport = function(mod) {
4
+ if (mod.startsWith("file://")) return import(mod);
5
+ if (mod.startsWith("/")) return import(pathToFileURL(mod).toString());
8
6
  try {
9
7
  const path = require.resolve(mod);
10
- return Promise.resolve().then(() => require(pathToFileURL(path).toString()));
11
- }
12
- catch (e) {
8
+ return import(pathToFileURL(path).toString());
9
+ } catch(e) {
13
10
  return Promise.reject(e);
14
11
  }
15
- };
12
+ }
@@ -259,9 +259,8 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
259
259
  defaultProjectId !== projectId) {
260
260
  const errorString = `Multiple projectIds are not recommended in single project mode. ` +
261
261
  `Requested project ID ${projectId}, but the emulator is configured for ` +
262
- `${defaultProjectId}. This warning will become an error in the future. To opt-out of ` +
263
- `single project mode add/set the \'"single_project_mode"\' false' property in the` +
264
- ` firebase.json emulators config.`;
262
+ `${defaultProjectId}. To opt-out of single project mode add/set the ` +
263
+ `\'"singleProjectMode"\' false' property in the firebase.json emulators config.`;
265
264
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.AUTH).log("WARN", errorString);
266
265
  if (singleProjectMode === index_1.SingleProjectMode.ERROR) {
267
266
  throw new errors_2.BadRequestError(errorString);
@@ -27,6 +27,7 @@ async function downloadEmulator(name) {
27
27
  if (emulator.unzipDir) {
28
28
  await unzip(emulator.downloadPath, emulator.unzipDir);
29
29
  }
30
+ await new Promise((f) => setTimeout(f, 2000));
30
31
  const executablePath = emulator.binaryPath || emulator.downloadPath;
31
32
  fs.chmodSync(executablePath, 0o755);
32
33
  removeOldFiles(name, emulator);
@@ -54,7 +55,7 @@ function unzip(zipPath, unzipDir) {
54
55
  fs.createReadStream(zipPath)
55
56
  .pipe(unzipper.Extract({ path: unzipDir }))
56
57
  .on("error", reject)
57
- .on("finish", resolve);
58
+ .on("close", resolve);
58
59
  });
59
60
  }
60
61
  function removeOldFiles(name, emulator, removeAllVersions = false) {
@@ -16,79 +16,94 @@ const download_1 = require("../emulator/download");
16
16
  const experiments = require("../experiments");
17
17
  const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
18
18
  const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
19
+ const EMULATOR_UPDATE_DETAILS = {
20
+ database: {
21
+ version: "4.11.0",
22
+ expectedSize: 34318940,
23
+ expectedChecksum: "311609538bd65666eb724ef47c2e6466",
24
+ },
25
+ firestore: {
26
+ version: "1.15.1",
27
+ expectedSize: 61475851,
28
+ expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee",
29
+ },
30
+ storage: {
31
+ version: "1.1.1",
32
+ expectedSize: 46448285,
33
+ expectedChecksum: "691982db4019d49d345a97151bdea7e2",
34
+ },
35
+ ui: experiments.isEnabled("emulatoruisnapshot")
36
+ ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" }
37
+ : {
38
+ version: "1.11.1",
39
+ expectedSize: 3061713,
40
+ expectedChecksum: "a4944414518be206280b495f526f18bf",
41
+ },
42
+ pubsub: {
43
+ version: "0.7.1",
44
+ expectedSize: 65137179,
45
+ expectedChecksum: "b59a6e705031a54a69e5e1dced7ca9bf",
46
+ },
47
+ };
19
48
  exports.DownloadDetails = {
20
49
  database: {
21
- downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.10.0.jar"),
22
- version: "4.10.0",
50
+ downloadPath: path.join(CACHE_DIR, `firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`),
51
+ version: EMULATOR_UPDATE_DETAILS.database.version,
23
52
  opts: {
24
53
  cacheDir: CACHE_DIR,
25
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.10.0.jar",
26
- expectedSize: 34230230,
27
- expectedChecksum: "e99b23f0e723813de4f4ea0e879b46b0",
54
+ remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`,
55
+ expectedSize: EMULATOR_UPDATE_DETAILS.database.expectedSize,
56
+ expectedChecksum: EMULATOR_UPDATE_DETAILS.database.expectedChecksum,
28
57
  namePrefix: "firebase-database-emulator",
29
58
  },
30
59
  },
31
60
  firestore: {
32
- downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.15.1.jar"),
33
- version: "1.15.1",
61
+ downloadPath: path.join(CACHE_DIR, `cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`),
62
+ version: EMULATOR_UPDATE_DETAILS.firestore.version,
34
63
  opts: {
35
64
  cacheDir: CACHE_DIR,
36
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.15.1.jar",
37
- expectedSize: 61475851,
38
- expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee",
65
+ remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`,
66
+ expectedSize: EMULATOR_UPDATE_DETAILS.firestore.expectedSize,
67
+ expectedChecksum: EMULATOR_UPDATE_DETAILS.firestore.expectedChecksum,
39
68
  namePrefix: "cloud-firestore-emulator",
40
69
  },
41
70
  },
42
71
  storage: {
43
- downloadPath: path.join(CACHE_DIR, "cloud-storage-rules-runtime-v1.1.1.jar"),
44
- version: "1.1.1",
72
+ downloadPath: path.join(CACHE_DIR, `cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`),
73
+ version: EMULATOR_UPDATE_DETAILS.storage.version,
45
74
  opts: {
46
75
  cacheDir: CACHE_DIR,
47
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v1.1.1.jar",
48
- expectedSize: 46448285,
49
- expectedChecksum: "691982db4019d49d345a97151bdea7e2",
76
+ remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`,
77
+ expectedSize: EMULATOR_UPDATE_DETAILS.storage.expectedSize,
78
+ expectedChecksum: EMULATOR_UPDATE_DETAILS.storage.expectedChecksum,
50
79
  namePrefix: "cloud-storage-rules-emulator",
51
80
  },
52
81
  },
53
- ui: experiments.isEnabled("emulatoruisnapshot")
54
- ? {
55
- version: "SNAPSHOT",
56
- downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
57
- unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
58
- binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server", "server.js"),
59
- opts: {
60
- cacheDir: CACHE_DIR,
61
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
62
- expectedSize: -1,
63
- expectedChecksum: "",
64
- skipCache: true,
65
- skipChecksumAndSize: true,
66
- namePrefix: "ui",
67
- },
68
- }
69
- : {
70
- version: "1.11.1",
71
- downloadPath: path.join(CACHE_DIR, "ui-v1.11.1.zip"),
72
- unzipDir: path.join(CACHE_DIR, "ui-v1.11.1"),
73
- binaryPath: path.join(CACHE_DIR, "ui-v1.11.1", "server", "server.js"),
74
- opts: {
75
- cacheDir: CACHE_DIR,
76
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.11.1.zip",
77
- expectedSize: 3061713,
78
- expectedChecksum: "a4944414518be206280b495f526f18bf",
79
- namePrefix: "ui",
80
- },
82
+ ui: {
83
+ version: EMULATOR_UPDATE_DETAILS.ui.version,
84
+ downloadPath: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}.zip`),
85
+ unzipDir: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}`),
86
+ binaryPath: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}`, "server", "server.js"),
87
+ opts: {
88
+ cacheDir: CACHE_DIR,
89
+ remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v${EMULATOR_UPDATE_DETAILS.ui.version}.zip`,
90
+ expectedSize: EMULATOR_UPDATE_DETAILS.ui.expectedSize,
91
+ expectedChecksum: EMULATOR_UPDATE_DETAILS.ui.expectedChecksum,
92
+ skipCache: experiments.isEnabled("emulatoruisnapshot"),
93
+ skipChecksumAndSize: experiments.isEnabled("emulatoruisnapshot"),
94
+ namePrefix: "ui",
81
95
  },
96
+ },
82
97
  pubsub: {
83
- downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"),
84
- version: "0.1.0",
85
- unzipDir: path.join(CACHE_DIR, "pubsub-emulator-0.1.0"),
86
- binaryPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0", `pubsub-emulator/bin/cloud-pubsub-emulator${process.platform === "win32" ? ".bat" : ""}`),
98
+ downloadPath: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`),
99
+ version: EMULATOR_UPDATE_DETAILS.pubsub.version,
100
+ unzipDir: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`),
101
+ binaryPath: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`, `pubsub-emulator/bin/cloud-pubsub-emulator${process.platform === "win32" ? ".bat" : ""}`),
87
102
  opts: {
88
103
  cacheDir: CACHE_DIR,
89
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.1.0.zip",
90
- expectedSize: 36623622,
91
- expectedChecksum: "81704b24737d4968734d3e175f4cde71",
104
+ remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`,
105
+ expectedSize: EMULATOR_UPDATE_DETAILS.pubsub.expectedSize,
106
+ expectedChecksum: EMULATOR_UPDATE_DETAILS.pubsub.expectedChecksum,
92
107
  namePrefix: "pubsub-emulator",
93
108
  },
94
109
  },