firebase-tools 11.16.1 → 11.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/deploy/extensions/planner.js +2 -1
  2. package/lib/deploy/functions/deploy.js +5 -2
  3. package/lib/deploy/functions/runtimes/index.js +2 -1
  4. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +1 -0
  5. package/lib/deploy/lifecycleHooks.js +2 -1
  6. package/lib/emulator/auth/apiSpec.js +549 -201
  7. package/lib/emulator/auth/operations.js +4 -2
  8. package/lib/emulator/auth/server.js +19 -8
  9. package/lib/emulator/eventarcEmulator.js +2 -1
  10. package/lib/emulator/eventarcEmulatorUtils.js +60 -0
  11. package/lib/emulator/extensions/validation.js +2 -0
  12. package/lib/emulator/extensionsEmulator.js +1 -1
  13. package/lib/emulator/functionsEmulator.js +40 -29
  14. package/lib/extensions/billingMigrationHelper.js +2 -1
  15. package/lib/extensions/displayExtensionInfo.js +2 -1
  16. package/lib/extensions/emulator/specHelper.js +4 -3
  17. package/lib/extensions/emulator/triggerHelper.js +64 -20
  18. package/lib/extensions/extensionsHelper.js +9 -9
  19. package/lib/extensions/types.js +2 -1
  20. package/lib/extensions/utils.js +14 -1
  21. package/lib/firestore/indexes-api.js +7 -1
  22. package/lib/firestore/indexes-sort.js +4 -0
  23. package/lib/firestore/indexes.js +31 -5
  24. package/lib/firestore/util.js +5 -1
  25. package/lib/firestore/validator.js +7 -1
  26. package/lib/frameworks/angular/index.js +3 -0
  27. package/lib/frameworks/next/index.js +59 -16
  28. package/lib/frameworks/next/interfaces.js +2 -0
  29. package/lib/frameworks/next/utils.js +34 -0
  30. package/lib/frameworks/nuxt/index.js +3 -0
  31. package/lib/frameworks/utils.js +24 -0
  32. package/lib/frameworks/vite/index.js +4 -1
  33. package/lib/init/features/emulators.js +1 -1
  34. package/lib/utils.js +1 -1
  35. package/npm-shrinkwrap.json +38 -72
  36. package/package.json +1 -1
  37. package/schema/firebase-config.json +4 -2
@@ -1126,7 +1126,8 @@ async function signInWithIdp(state, reqBody) {
1126
1126
  oauthExpiresIn: coercePrimitiveToString(response.oauthExpireIn),
1127
1127
  };
1128
1128
  if (response.isNewUser) {
1129
- let updates = Object.assign(Object.assign({}, accountUpdates.fields), { lastLoginAt: Date.now().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined });
1129
+ const timestamp = new Date();
1130
+ let updates = Object.assign(Object.assign({}, accountUpdates.fields), { createdAt: timestamp.getTime().toString(), lastLoginAt: timestamp.getTime().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined });
1130
1131
  const localId = state.generateLocalId();
1131
1132
  const userBeforeCreate = Object.assign({ localId }, updates);
1132
1133
  const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, {
@@ -1187,7 +1188,8 @@ async function signInWithIdp(state, reqBody) {
1187
1188
  async function signInWithPassword(state, reqBody) {
1188
1189
  (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
1189
1190
  (0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
1190
- (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
1191
+ (0, errors_1.assert)(reqBody.email !== undefined, "MISSING_EMAIL");
1192
+ (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
1191
1193
  (0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
1192
1194
  if (reqBody.captchaResponse || reqBody.captchaChallenge) {
1193
1195
  throw new errors_1.NotImplementedError("captcha unimplemented");
@@ -97,14 +97,24 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
97
97
  registerLegacyRoutes(app);
98
98
  (0, handlers_1.registerHandlers)(app, (apiKey, tenantId) => getProjectStateById(getProjectIdByApiKey(apiKey), tenantId));
99
99
  const apiKeyAuthenticator = (ctx, info) => {
100
- if (info.in !== "query") {
101
- throw new Error('apiKey must be defined as in: "query" in API spec.');
102
- }
103
100
  if (!info.name) {
104
- throw new Error("apiKey param name is undefined in API spec.");
101
+ throw new Error("apiKey param/header name is undefined in API spec.");
102
+ }
103
+ let key;
104
+ const req = ctx.req;
105
+ switch (info.in) {
106
+ case "header":
107
+ key = req.get(info.name);
108
+ break;
109
+ case "query": {
110
+ const q = req.query[info.name];
111
+ key = typeof q === "string" ? q : undefined;
112
+ break;
113
+ }
114
+ default:
115
+ throw new Error('apiKey must be defined as in: "query" or "header" in API spec.');
105
116
  }
106
- const key = ctx.req.query[info.name];
107
- if (typeof key === "string" && key.length > 0) {
117
+ if (key) {
108
118
  return { type: "success", user: getProjectIdByApiKey(key) };
109
119
  }
110
120
  else {
@@ -138,7 +148,8 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
138
148
  const apis = await exegesisExpress.middleware(specForRouter(), {
139
149
  controllers: { auth: toExegesisController(operations_1.authOperations, getProjectStateById) },
140
150
  authenticators: {
141
- apiKey: apiKeyAuthenticator,
151
+ apiKeyQuery: apiKeyAuthenticator,
152
+ apiKeyHeader: apiKeyAuthenticator,
142
153
  Oauth2: oauth2Authenticator,
143
154
  },
144
155
  autoHandleHttpErrors(err) {
@@ -206,7 +217,7 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
206
217
  postController(ctx) {
207
218
  if (ctx.res.statusCode === 401) {
208
219
  const requirements = ctx.api.operationObject.security;
209
- if (requirements === null || requirements === void 0 ? void 0 : requirements.some((req) => req.apiKey)) {
220
+ if (requirements === null || requirements === void 0 ? void 0 : requirements.some((req) => req.apiKeyQuery || req.apiKeyHeader)) {
210
221
  throw new errors_2.PermissionDeniedError("The request is missing a valid API key.");
211
222
  }
212
223
  else {
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
8
8
  const emulatorLogger_1 = require("./emulatorLogger");
9
9
  const registry_1 = require("./registry");
10
10
  const error_1 = require("../error");
11
+ const eventarcEmulatorUtils_1 = require("./eventarcEmulatorUtils");
11
12
  class EventarcEmulator {
12
13
  constructor(args) {
13
14
  this.args = args;
@@ -89,7 +90,7 @@ class EventarcEmulator {
89
90
  .request({
90
91
  method: "POST",
91
92
  path: `/functions/projects/${trigger.projectId}/triggers/${trigger.triggerName}`,
92
- body: JSON.stringify(event),
93
+ body: JSON.stringify((0, eventarcEmulatorUtils_1.cloudEventFromProtoToJson)(event)),
93
94
  responseType: "stream",
94
95
  resolveOnHTTPError: true,
95
96
  })
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cloudEventFromProtoToJson = void 0;
4
+ const error_1 = require("../error");
5
+ const BUILT_IN_ATTRS = ["time", "datacontenttype", "subject"];
6
+ function cloudEventFromProtoToJson(ce) {
7
+ if (ce["id"] === undefined) {
8
+ throw new error_1.FirebaseError("CloudEvent 'id' is required.");
9
+ }
10
+ if (ce["type"] === undefined) {
11
+ throw new error_1.FirebaseError("CloudEvent 'type' is required.");
12
+ }
13
+ if (ce["specVersion"] === undefined) {
14
+ throw new error_1.FirebaseError("CloudEvent 'specVersion' is required.");
15
+ }
16
+ if (ce["source"] === undefined) {
17
+ throw new error_1.FirebaseError("CloudEvent 'source' is required.");
18
+ }
19
+ const out = {
20
+ id: ce["id"],
21
+ type: ce["type"],
22
+ specversion: ce["specVersion"],
23
+ source: ce["source"],
24
+ subject: getOptionalAttribute(ce, "subject", "ceString"),
25
+ time: getRequiredAttribute(ce, "time", "ceTimestamp"),
26
+ data: getData(ce),
27
+ datacontenttype: getRequiredAttribute(ce, "datacontenttype", "ceString"),
28
+ };
29
+ for (const attr in ce["attributes"]) {
30
+ if (BUILT_IN_ATTRS.includes(attr)) {
31
+ continue;
32
+ }
33
+ out[attr] = getRequiredAttribute(ce, attr, "ceString");
34
+ }
35
+ return out;
36
+ }
37
+ exports.cloudEventFromProtoToJson = cloudEventFromProtoToJson;
38
+ function getOptionalAttribute(ce, attr, type) {
39
+ return ce["attributes"][attr][type];
40
+ }
41
+ function getRequiredAttribute(ce, attr, type) {
42
+ const val = ce["attributes"][attr][type];
43
+ if (val === undefined) {
44
+ throw new error_1.FirebaseError("CloudEvent must contain " + attr + " attribute");
45
+ }
46
+ return val;
47
+ }
48
+ function getData(ce) {
49
+ const contentType = getRequiredAttribute(ce, "datacontenttype", "ceString");
50
+ switch (contentType) {
51
+ case "application/json":
52
+ return JSON.parse(ce["textData"]);
53
+ case "text/plain":
54
+ return ce["textData"];
55
+ case undefined:
56
+ return undefined;
57
+ default:
58
+ throw new error_1.FirebaseError("Unsupported content type: " + contentType);
59
+ }
60
+ }
@@ -59,6 +59,8 @@ function checkForUnemulatedTriggerTypes(backend, options) {
59
59
  return !(0, controller_1.shouldStart)(options, types_1.Emulators.AUTH);
60
60
  case constants_1.Constants.SERVICE_STORAGE:
61
61
  return !(0, controller_1.shouldStart)(options, types_1.Emulators.STORAGE);
62
+ case constants_1.Constants.SERVICE_EVENTARC:
63
+ return !(0, controller_1.shouldStart)(options, types_1.Emulators.EVENTARC);
62
64
  default:
63
65
  return true;
64
66
  }
@@ -142,7 +142,7 @@ class ExtensionsEmulator {
142
142
  const emulatableBackend = {
143
143
  functionsDir,
144
144
  env: nonSecretEnv,
145
- codebase: "",
145
+ codebase: instance.instanceId,
146
146
  secretEnv: secretEnvVariables,
147
147
  predefinedTriggers: extensionTriggers,
148
148
  nodeMajorVersion: nodeMajorVersion,
@@ -334,7 +334,7 @@ class FunctionsEmulator {
334
334
  added = await this.addFirestoreTrigger(this.args.projectId, key, definition.eventTrigger);
335
335
  break;
336
336
  case constants_1.Constants.SERVICE_REALTIME_DATABASE:
337
- added = await this.addRealtimeDatabaseTrigger(this.args.projectId, key, definition.eventTrigger, signature, definition.region);
337
+ added = await this.addRealtimeDatabaseTrigger(this.args.projectId, definition.id, key, definition.eventTrigger, signature, definition.region);
338
338
  break;
339
339
  case constants_1.Constants.SERVICE_PUBSUB:
340
340
  added = await this.addPubsubTrigger(definition.name, key, definition.eventTrigger, signature, definition.schedule);
@@ -377,6 +377,12 @@ class FunctionsEmulator {
377
377
  }
378
378
  }
379
379
  if (this.args.debugPort) {
380
+ emulatableBackend.secretEnv = Object.values(toSetup.reduce((acc, curr) => {
381
+ for (const secret of curr.secretEnvironmentVariables || []) {
382
+ acc[secret.key] = secret;
383
+ }
384
+ return acc;
385
+ }, {}));
380
386
  await this.startRuntime(emulatableBackend);
381
387
  }
382
388
  }
@@ -438,7 +444,7 @@ class FunctionsEmulator {
438
444
  }
439
445
  return { bundle, apiPath, instance };
440
446
  }
441
- getV2DatabaseApiAttributes(projectId, key, eventTrigger, region) {
447
+ getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region) {
442
448
  var _a, _b, _c;
443
449
  const instance = ((_a = eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.instance) || ((_b = eventTrigger.eventFilterPathPatterns) === null || _b === void 0 ? void 0 : _b.instance);
444
450
  if (!instance) {
@@ -448,6 +454,9 @@ class FunctionsEmulator {
448
454
  if (!ref) {
449
455
  throw new error_1.FirebaseError("A database reference must be supplied.");
450
456
  }
457
+ if (region !== "us-central1") {
458
+ this.logger.logLabeled("WARN", `functions[${id}]`, `function region is defined outside the database region, will not trigger.`);
459
+ }
451
460
  const bundle = JSON.stringify({
452
461
  name: `projects/${projectId}/locations/${region}/triggers/${key}`,
453
462
  path: ref,
@@ -458,12 +467,12 @@ class FunctionsEmulator {
458
467
  const apiPath = "/.settings/functionTriggers.json";
459
468
  return { bundle, apiPath, instance };
460
469
  }
461
- async addRealtimeDatabaseTrigger(projectId, key, eventTrigger, signature, region) {
470
+ async addRealtimeDatabaseTrigger(projectId, id, key, eventTrigger, signature, region) {
462
471
  if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.DATABASE)) {
463
472
  return false;
464
473
  }
465
474
  const { bundle, apiPath, instance } = signature === "cloudevent"
466
- ? this.getV2DatabaseApiAttributes(projectId, key, eventTrigger, region)
475
+ ? this.getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region)
467
476
  : this.getV1DatabaseApiAttributes(projectId, key, eventTrigger);
468
477
  logger_1.logger.debug(`addRealtimeDatabaseTrigger[${instance}]`, JSON.stringify(bundle));
469
478
  const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE);
@@ -746,34 +755,32 @@ class FunctionsEmulator {
746
755
  this.logger.logLabeled("ERROR", "functions", `Failed to read local secrets file ${secretPath}: ${e.message}`);
747
756
  }
748
757
  }
749
- if (trigger) {
750
- const secrets = trigger.secretEnvironmentVariables || [];
751
- const accesses = secrets
752
- .filter((s) => !secretEnvs[s.key])
753
- .map(async (s) => {
754
- var _a;
755
- this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`);
756
- const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.secret, (_a = s.version) !== null && _a !== void 0 ? _a : "latest");
757
- return [s.key, value];
758
- });
759
- const accessResults = await (0, utils_1.allSettled)(accesses);
760
- const errs = [];
761
- for (const result of accessResults) {
762
- if (result.status === "rejected") {
763
- errs.push(result.reason);
764
- }
765
- else {
766
- const [k, v] = result.value;
767
- secretEnvs[k] = v;
768
- }
758
+ const secrets = (trigger === null || trigger === void 0 ? void 0 : trigger.secretEnvironmentVariables) || backend.secretEnv;
759
+ const accesses = secrets
760
+ .filter((s) => !secretEnvs[s.key])
761
+ .map(async (s) => {
762
+ var _a;
763
+ this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`);
764
+ const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.secret, (_a = s.version) !== null && _a !== void 0 ? _a : "latest");
765
+ return [s.key, value];
766
+ });
767
+ const accessResults = await (0, utils_1.allSettled)(accesses);
768
+ const errs = [];
769
+ for (const result of accessResults) {
770
+ if (result.status === "rejected") {
771
+ errs.push(result.reason);
769
772
  }
770
- if (errs.length > 0) {
771
- this.logger.logLabeled("ERROR", "functions", "Unable to access secret environment variables from Google Cloud Secret Manager. " +
772
- "Make sure the credential used for the Functions Emulator have access " +
773
- `or provide override values in ${secretPath}:\n\t` +
774
- errs.join("\n\t"));
773
+ else {
774
+ const [k, v] = result.value;
775
+ secretEnvs[k] = v;
775
776
  }
776
777
  }
778
+ if (errs.length > 0) {
779
+ this.logger.logLabeled("ERROR", "functions", "Unable to access secret environment variables from Google Cloud Secret Manager. " +
780
+ "Make sure the credential used for the Functions Emulator have access " +
781
+ `or provide override values in ${secretPath}:\n\t` +
782
+ errs.join("\n\t"));
783
+ }
777
784
  return secretEnvs;
778
785
  }
779
786
  async startRuntime(backend, trigger) {
@@ -881,6 +888,10 @@ class FunctionsEmulator {
881
888
  return;
882
889
  }
883
890
  const record = this.getTriggerRecordByKey(triggerId);
891
+ if (!record.enabled) {
892
+ res.status(204).send("Background triggers are currently disabled.");
893
+ return;
894
+ }
884
895
  const trigger = record.def;
885
896
  logger_1.logger.debug(`Accepted request ${method} ${req.url} --> ${triggerId}`);
886
897
  const reqBody = req.rawBody;
@@ -7,6 +7,7 @@ const error_1 = require("../error");
7
7
  const extensionsHelper_1 = require("./extensionsHelper");
8
8
  const prompt_1 = require("../prompt");
9
9
  const utils = require("../utils");
10
+ const utils_1 = require("./utils");
10
11
  marked.setOptions({
11
12
  renderer: new TerminalRenderer(),
12
13
  });
@@ -34,7 +35,7 @@ function hasRuntime(spec, runtime) {
34
35
  const specVersion = spec.specVersion || defaultSpecVersion;
35
36
  const defaultRuntime = defaultRuntimes[specVersion];
36
37
  const resources = spec.resources || [];
37
- return resources.some((r) => { var _a; return runtime === (((_a = r.properties) === null || _a === void 0 ? void 0 : _a.runtime) || defaultRuntime); });
38
+ return resources.some((r) => runtime === ((0, utils_1.getResourceRuntime)(r) || defaultRuntime));
38
39
  }
39
40
  function displayNode10UpdateBillingNotice(curSpec, newSpec) {
40
41
  if (hasRuntime(curSpec, "nodejs8") && hasRuntime(newSpec, "nodejs10")) {
@@ -8,6 +8,7 @@ const utils = require("../utils");
8
8
  const extensionsHelper_1 = require("./extensionsHelper");
9
9
  const logger_1 = require("../logger");
10
10
  const error_1 = require("../error");
11
+ const types_1 = require("./types");
11
12
  const iam = require("../gcp/iam");
12
13
  const secretsUtils_1 = require("./secretsUtils");
13
14
  marked.setOptions({
@@ -80,7 +81,7 @@ function displayApis(apis) {
80
81
  return "**APIs used by this Extension**:\n" + lines.join("\n");
81
82
  }
82
83
  function usesTasks(spec) {
83
- return spec.resources.some((r) => { var _a; return ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.taskQueueTrigger) !== undefined; });
84
+ return spec.resources.some((r) => { var _a; return r.type === types_1.FUNCTIONS_RESOURCE_TYPE && ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.taskQueueTrigger) !== undefined; });
84
85
  }
85
86
  function impliedRoles(spec) {
86
87
  var _a, _b, _c;
@@ -6,11 +6,13 @@ const path = require("path");
6
6
  const fs = require("fs-extra");
7
7
  const error_1 = require("../../error");
8
8
  const extensionsHelper_1 = require("../extensionsHelper");
9
+ const utils_1 = require("../utils");
9
10
  const functionsEmulatorUtils_1 = require("../../emulator/functionsEmulatorUtils");
10
11
  const SPEC_FILE = "extension.yaml";
11
12
  const POSTINSTALL_FILE = "POSTINSTALL.md";
12
13
  const validFunctionTypes = [
13
14
  "firebaseextensions.v1beta.function",
15
+ "firebaseextensions.v1beta.v2function",
14
16
  "firebaseextensions.v1beta.scheduledFunction",
15
17
  ];
16
18
  function wrappedSafeLoad(source) {
@@ -68,9 +70,8 @@ exports.getFunctionProperties = getFunctionProperties;
68
70
  function getNodeVersion(resources) {
69
71
  const invalidRuntimes = [];
70
72
  const versions = resources.map((r) => {
71
- var _a, _b;
72
- if ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.runtime) {
73
- const runtimeName = (_b = r.properties) === null || _b === void 0 ? void 0 : _b.runtime;
73
+ if ((0, utils_1.getResourceRuntime)(r)) {
74
+ const runtimeName = (0, utils_1.getResourceRuntime)(r);
74
75
  const runtime = (0, functionsEmulatorUtils_1.parseRuntimeVersion)(runtimeName);
75
76
  if (!runtime) {
76
77
  invalidRuntimes.push(runtimeName);
@@ -4,30 +4,74 @@ exports.functionResourceToEmulatedTriggerDefintion = void 0;
4
4
  const functionsEmulatorShared_1 = require("../../emulator/functionsEmulatorShared");
5
5
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
6
6
  const types_1 = require("../../emulator/types");
7
+ const types_2 = require("../../extensions/types");
7
8
  const proto = require("../../gcp/proto");
9
+ const error_1 = require("../../error");
8
10
  function functionResourceToEmulatedTriggerDefintion(resource) {
9
- const etd = {
10
- name: resource.name,
11
- entryPoint: resource.name,
12
- platform: "gcfv1",
13
- };
14
- const properties = resource.properties || {};
15
- proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
16
- proto.convertIfPresent(etd, properties, "regions", "location", (str) => [str]);
17
- proto.copyIfPresent(etd, properties, "availableMemoryMb");
18
- if (properties.httpsTrigger) {
19
- etd.httpsTrigger = properties.httpsTrigger;
20
- }
21
- if (properties.eventTrigger) {
22
- etd.eventTrigger = {
23
- eventType: properties.eventTrigger.eventType,
24
- resource: properties.eventTrigger.resource,
25
- service: (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType),
11
+ const resourceType = resource.type;
12
+ if (resource.type === types_2.FUNCTIONS_RESOURCE_TYPE) {
13
+ const etd = {
14
+ name: resource.name,
15
+ entryPoint: resource.name,
16
+ platform: "gcfv1",
26
17
  };
18
+ const properties = resource.properties || {};
19
+ proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
20
+ proto.convertIfPresent(etd, properties, "regions", "location", (str) => [str]);
21
+ proto.copyIfPresent(etd, properties, "availableMemoryMb");
22
+ if (properties.httpsTrigger) {
23
+ etd.httpsTrigger = properties.httpsTrigger;
24
+ }
25
+ if (properties.eventTrigger) {
26
+ etd.eventTrigger = {
27
+ eventType: properties.eventTrigger.eventType,
28
+ resource: properties.eventTrigger.resource,
29
+ service: (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType),
30
+ };
31
+ }
32
+ else {
33
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("WARN", `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.`);
34
+ }
35
+ return etd;
27
36
  }
28
- else {
29
- emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("WARN", `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.`);
37
+ if (resource.type === types_2.FUNCTIONS_V2_RESOURCE_TYPE) {
38
+ const etd = {
39
+ name: resource.name,
40
+ entryPoint: resource.name,
41
+ platform: "gcfv2",
42
+ };
43
+ const properties = resource.properties || {};
44
+ proto.convertIfPresent(etd, properties, "regions", "location", (str) => [str]);
45
+ if (properties.serviceConfig) {
46
+ proto.copyIfPresent(etd, properties.serviceConfig, "timeoutSeconds");
47
+ proto.convertIfPresent(etd, properties.serviceConfig, "availableMemoryMb", "availableMemory", (mem) => parseInt(mem));
48
+ }
49
+ if (properties.eventTrigger) {
50
+ etd.eventTrigger = {
51
+ eventType: properties.eventTrigger.eventType,
52
+ service: (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType),
53
+ };
54
+ proto.copyIfPresent(etd.eventTrigger, properties.eventTrigger, "channel");
55
+ if (properties.eventTrigger.eventFilters) {
56
+ const eventFilters = {};
57
+ const eventFilterPathPatterns = {};
58
+ for (const filter of properties.eventTrigger.eventFilters) {
59
+ if (filter.operator === undefined) {
60
+ eventFilters[filter.attribute] = filter.value;
61
+ }
62
+ else if (filter.operator === "match-path-pattern") {
63
+ eventFilterPathPatterns[filter.attribute] = filter.value;
64
+ }
65
+ }
66
+ etd.eventTrigger.eventFilters = eventFilters;
67
+ etd.eventTrigger.eventFilterPathPatterns = eventFilterPathPatterns;
68
+ }
69
+ }
70
+ else {
71
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("WARN", `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.`);
72
+ }
73
+ return etd;
30
74
  }
31
- return etd;
75
+ throw new error_1.FirebaseError("Unexpected resource type " + resourceType);
32
76
  }
33
77
  exports.functionResourceToEmulatedTriggerDefintion = functionResourceToEmulatedTriggerDefintion;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.canonicalizeRefInput = exports.diagnoseAndFixProject = exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.incrementPrereleaseVersion = exports.ensureExtensionsApiEnabled = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
3
+ exports.canonicalizeRefInput = exports.diagnoseAndFixProject = exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.incrementPrereleaseVersion = exports.ensureExtensionsApiEnabled = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
4
4
  const clc = require("colorette");
5
5
  const ora = require("ora");
6
6
  const semver = require("semver");
@@ -66,9 +66,6 @@ exports.AUTOPOULATED_PARAM_PLACEHOLDERS = {
66
66
  DATABASE_INSTANCE: "project-id-default-rtdb",
67
67
  DATABASE_URL: "https://project-id-default-rtdb.firebaseio.com",
68
68
  };
69
- exports.resourceTypeToNiceName = {
70
- "firebaseextensions.v1beta.function": "Cloud Function",
71
- };
72
69
  function getDBInstanceFromURL(databaseUrl = "") {
73
70
  const instanceRegex = new RegExp("(?:https://)(.*)(?:.firebaseio.com)");
74
71
  const matches = instanceRegex.exec(databaseUrl);
@@ -558,12 +555,15 @@ async function diagnoseAndFixProject(options) {
558
555
  }
559
556
  }
560
557
  exports.diagnoseAndFixProject = diagnoseAndFixProject;
561
- async function canonicalizeRefInput(extensionName) {
562
- if (extensionName.split("/").length < 2) {
563
- const [extensionID, version] = extensionName.split("@");
564
- extensionName = `firebase/${extensionID}@${version || "latest"}`;
558
+ async function canonicalizeRefInput(refInput) {
559
+ let inferredRef = refInput;
560
+ if (refInput.split("/").length < 2) {
561
+ inferredRef = `firebase/${inferredRef}`;
562
+ }
563
+ if (refInput.split("@").length < 2) {
564
+ inferredRef = `${inferredRef}@latest`;
565
565
  }
566
- const ref = refs.parse(extensionName);
566
+ const ref = refs.parse(inferredRef);
567
567
  ref.version = await (0, planner_1.resolveVersion)(ref);
568
568
  return refs.toExtensionVersionRef(ref);
569
569
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ParamType = exports.FUNCTIONS_RESOURCE_TYPE = exports.Visibility = exports.RegistryLaunchStage = void 0;
3
+ exports.ParamType = exports.FUNCTIONS_V2_RESOURCE_TYPE = exports.FUNCTIONS_RESOURCE_TYPE = exports.Visibility = exports.RegistryLaunchStage = void 0;
4
4
  var RegistryLaunchStage;
5
5
  (function (RegistryLaunchStage) {
6
6
  RegistryLaunchStage["EXPERIMENTAL"] = "EXPERIMENTAL";
@@ -15,6 +15,7 @@ var Visibility;
15
15
  Visibility["PUBLIC"] = "public";
16
16
  })(Visibility = exports.Visibility || (exports.Visibility = {}));
17
17
  exports.FUNCTIONS_RESOURCE_TYPE = "firebaseextensions.v1beta.function";
18
+ exports.FUNCTIONS_V2_RESOURCE_TYPE = "firebaseextensions.v1beta.v2function";
18
19
  var ParamType;
19
20
  (function (ParamType) {
20
21
  ParamType["STRING"] = "STRING";
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatTimestamp = exports.getRandomString = exports.convertOfficialExtensionsToList = exports.convertExtensionOptionToLabeledList = exports.onceWithJoin = void 0;
3
+ exports.getResourceRuntime = exports.formatTimestamp = exports.getRandomString = exports.convertOfficialExtensionsToList = exports.convertExtensionOptionToLabeledList = exports.onceWithJoin = void 0;
4
4
  const prompt_1 = require("../prompt");
5
+ const types_1 = require("./types");
5
6
  async function onceWithJoin(question) {
6
7
  const response = await (0, prompt_1.promptOnce)(question);
7
8
  if (Array.isArray(response)) {
@@ -48,3 +49,15 @@ function formatTimestamp(timestamp) {
48
49
  return withoutMs.replace("T", " ");
49
50
  }
50
51
  exports.formatTimestamp = formatTimestamp;
52
+ function getResourceRuntime(resource) {
53
+ var _a, _b, _c;
54
+ switch (resource.type) {
55
+ case types_1.FUNCTIONS_RESOURCE_TYPE:
56
+ return (_a = resource.properties) === null || _a === void 0 ? void 0 : _a.runtime;
57
+ case types_1.FUNCTIONS_V2_RESOURCE_TYPE:
58
+ return (_c = (_b = resource.properties) === null || _b === void 0 ? void 0 : _b.buildConfig) === null || _c === void 0 ? void 0 : _c.runtime;
59
+ default:
60
+ return undefined;
61
+ }
62
+ }
63
+ exports.getResourceRuntime = getResourceRuntime;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.State = exports.ArrayConfig = exports.Order = exports.QueryScope = exports.Mode = void 0;
3
+ exports.StateTtl = exports.State = exports.ArrayConfig = exports.Order = exports.QueryScope = exports.Mode = void 0;
4
4
  var Mode;
5
5
  (function (Mode) {
6
6
  Mode["ASCENDING"] = "ASCENDING";
@@ -27,3 +27,9 @@ var State;
27
27
  State["READY"] = "READY";
28
28
  State["NEEDS_REPAIR"] = "NEEDS_REPAIR";
29
29
  })(State = exports.State || (exports.State = {}));
30
+ var StateTtl;
31
+ (function (StateTtl) {
32
+ StateTtl["CREATING"] = "CREATING";
33
+ StateTtl["ACTIVE"] = "ACTIVE";
34
+ StateTtl["NEEDS_REPAIR"] = "NEEDS_REPAIR";
35
+ })(StateTtl = exports.StateTtl || (exports.StateTtl = {}));
@@ -50,6 +50,10 @@ function compareFieldOverride(a, b) {
50
50
  if (a.collectionGroup !== b.collectionGroup) {
51
51
  return a.collectionGroup.localeCompare(b.collectionGroup);
52
52
  }
53
+ const compareTtl = Number(!!a.ttl) - Number(!!b.ttl);
54
+ if (compareTtl) {
55
+ return compareTtl;
56
+ }
53
57
  if (a.fieldPath !== b.fieldPath) {
54
58
  return a.fieldPath.localeCompare(b.fieldPath);
55
59
  }