firebase-tools 11.17.0 → 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.
@@ -4,7 +4,7 @@ exports.getRuntimeDelegate = exports.getHumanFriendlyRuntimeName = exports.isVal
4
4
  const node = require("./node");
5
5
  const validate = require("../validate");
6
6
  const error_1 = require("../../../error");
7
- const RUNTIMES = ["nodejs10", "nodejs12", "nodejs14", "nodejs16"];
7
+ const RUNTIMES = ["nodejs10", "nodejs12", "nodejs14", "nodejs16", "nodejs18"];
8
8
  const EXPERIMENTAL_RUNTIMES = ["go113"];
9
9
  const DEPRECATED_RUNTIMES = ["nodejs6", "nodejs8"];
10
10
  function isDeprecatedRuntime(runtime) {
@@ -22,6 +22,7 @@ const MESSAGE_FRIENDLY_RUNTIMES = {
22
22
  nodejs12: "Node.js 12",
23
23
  nodejs14: "Node.js 14",
24
24
  nodejs16: "Node.js 16",
25
+ nodejs18: "Node.js 18",
25
26
  go113: "Go 1.13",
26
27
  };
27
28
  function getHumanFriendlyRuntimeName(runtime) {
@@ -14,6 +14,7 @@ const ENGINE_RUNTIMES = {
14
14
  12: "nodejs12",
15
15
  14: "nodejs14",
16
16
  16: "nodejs16",
17
+ 18: "nodejs18",
17
18
  };
18
19
  const ENGINE_RUNTIMES_NAMES = Object.values(ENGINE_RUNTIMES);
19
20
  exports.RUNTIME_NOT_SET = "`runtime` field is required but was not found in firebase.json.\n" +
@@ -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,
@@ -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
  }
@@ -749,34 +755,32 @@ class FunctionsEmulator {
749
755
  this.logger.logLabeled("ERROR", "functions", `Failed to read local secrets file ${secretPath}: ${e.message}`);
750
756
  }
751
757
  }
752
- if (trigger) {
753
- const secrets = trigger.secretEnvironmentVariables || [];
754
- const accesses = secrets
755
- .filter((s) => !secretEnvs[s.key])
756
- .map(async (s) => {
757
- var _a;
758
- this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`);
759
- const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.secret, (_a = s.version) !== null && _a !== void 0 ? _a : "latest");
760
- return [s.key, value];
761
- });
762
- const accessResults = await (0, utils_1.allSettled)(accesses);
763
- const errs = [];
764
- for (const result of accessResults) {
765
- if (result.status === "rejected") {
766
- errs.push(result.reason);
767
- }
768
- else {
769
- const [k, v] = result.value;
770
- secretEnvs[k] = v;
771
- }
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);
772
772
  }
773
- if (errs.length > 0) {
774
- this.logger.logLabeled("ERROR", "functions", "Unable to access secret environment variables from Google Cloud Secret Manager. " +
775
- "Make sure the credential used for the Functions Emulator have access " +
776
- `or provide override values in ${secretPath}:\n\t` +
777
- errs.join("\n\t"));
773
+ else {
774
+ const [k, v] = result.value;
775
+ secretEnvs[k] = v;
778
776
  }
779
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
+ }
780
784
  return secretEnvs;
781
785
  }
782
786
  async startRuntime(backend, trigger) {
@@ -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);
@@ -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
  }
@@ -98,7 +98,8 @@ class FirestoreIndexes {
98
98
  });
99
99
  }
100
100
  }
101
- for (const field of fieldOverridesToDeploy) {
101
+ const sortedFieldOverridesToDeploy = fieldOverridesToDeploy.sort(sort.compareFieldOverride);
102
+ for (const field of sortedFieldOverridesToDeploy) {
102
103
  const exists = existingFieldOverrides.some((x) => this.fieldMatchesSpec(x, field));
103
104
  if (exists) {
104
105
  logger_1.logger.debug(`Skipping existing field override: ${JSON.stringify(field)}`);
@@ -136,7 +137,7 @@ class FirestoreIndexes {
136
137
  }
137
138
  async listFieldOverrides(project) {
138
139
  const parent = `projects/${project}/databases/(default)/collectionGroups/-`;
139
- const url = `/${parent}/fields?filter=indexConfig.usesAncestorConfig=false`;
140
+ const url = `/${parent}/fields?filter=indexConfig.usesAncestorConfig=false OR ttlConfig:*`;
140
141
  const res = await this.apiClient.get(url);
141
142
  const fields = res.body.fields;
142
143
  if (!fields) {
@@ -164,6 +165,7 @@ class FirestoreIndexes {
164
165
  return {
165
166
  collectionGroup: parsedName.collectionGroupId,
166
167
  fieldPath: parsedName.fieldPath,
168
+ ttl: !!field.ttlConfig,
167
169
  indexes: fieldIndexes.map((index) => {
168
170
  const firstField = index.fields[0];
169
171
  return {
@@ -232,6 +234,9 @@ class FirestoreIndexes {
232
234
  validator.assertHas(field, "collectionGroup");
233
235
  validator.assertHas(field, "fieldPath");
234
236
  validator.assertHas(field, "indexes");
237
+ if (typeof field.ttl !== "undefined") {
238
+ validator.assertType("ttl", field.ttl, "boolean");
239
+ }
235
240
  field.indexes.forEach((index) => {
236
241
  validator.assertHasOneOf(index, ["arrayConfig", "order"]);
237
242
  if (index.arrayConfig) {
@@ -259,17 +264,27 @@ class FirestoreIndexes {
259
264
  ],
260
265
  };
261
266
  });
262
- const data = {
267
+ let data = {
263
268
  indexConfig: {
264
269
  indexes,
265
270
  },
266
271
  };
267
- await this.apiClient.patch(url, data);
272
+ if (spec.ttl) {
273
+ data = Object.assign(data, {
274
+ ttlConfig: {},
275
+ });
276
+ }
277
+ if (typeof spec.ttl !== "undefined") {
278
+ await this.apiClient.patch(url, data);
279
+ }
280
+ else {
281
+ await this.apiClient.patch(url, data, { queryParams: { updateMask: "indexConfig" } });
282
+ }
268
283
  }
269
284
  deleteField(field) {
270
285
  const url = field.name;
271
286
  const data = {};
272
- return this.apiClient.patch(`/${url}`, data, { queryParams: { updateMask: "indexConfig" } });
287
+ return this.apiClient.patch(`/${url}`, data);
273
288
  }
274
289
  createIndex(project, index) {
275
290
  const url = `/projects/${project}/databases/(default)/collectionGroups/${index.collectionGroup}/indexes`;
@@ -318,6 +333,13 @@ class FirestoreIndexes {
318
333
  if (parsedName.fieldPath !== spec.fieldPath) {
319
334
  return false;
320
335
  }
336
+ if (typeof spec.ttl !== "undefined" && util.booleanXOR(!!field.ttlConfig, spec.ttl)) {
337
+ return false;
338
+ }
339
+ else if (!!field.ttlConfig && typeof spec.ttl === "undefined") {
340
+ utils.logLabeledBullet("firestore", `there are TTL field overrides for collection ${spec.collectionGroup} defined in your project that are not present in your ` +
341
+ "firestore indexes file. The TTL policy won't be deleted since is not specified as false.");
342
+ }
321
343
  const fieldIndexes = field.indexConfig.indexes || [];
322
344
  if (fieldIndexes.length !== spec.indexes.length) {
323
345
  return false;
@@ -427,6 +449,10 @@ class FirestoreIndexes {
427
449
  else {
428
450
  result += " (no indexes)";
429
451
  }
452
+ const fieldTtl = field.ttlConfig;
453
+ if (fieldTtl) {
454
+ result += ` TTL(${fieldTtl.state})`;
455
+ }
430
456
  return result;
431
457
  }
432
458
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseFieldName = exports.parseIndexName = void 0;
3
+ exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = void 0;
4
4
  const error_1 = require("../error");
5
5
  const INDEX_NAME_REGEX = /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/;
6
6
  const FIELD_NAME_REGEX = /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/;
@@ -31,3 +31,7 @@ function parseFieldName(name) {
31
31
  };
32
32
  }
33
33
  exports.parseFieldName = parseFieldName;
34
+ function booleanXOR(a, b) {
35
+ return !!(Number(a) - Number(b));
36
+ }
37
+ exports.booleanXOR = booleanXOR;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertEnum = exports.assertHasOneOf = exports.assertHas = void 0;
3
+ exports.assertType = exports.assertEnum = exports.assertHasOneOf = exports.assertHas = void 0;
4
4
  const clc = require("colorette");
5
5
  const error_1 = require("../error");
6
6
  function assertHas(obj, prop) {
@@ -30,3 +30,9 @@ function assertEnum(obj, prop, valid) {
30
30
  }
31
31
  }
32
32
  exports.assertEnum = assertEnum;
33
+ function assertType(prop, propValue, type) {
34
+ if (typeof propValue !== type) {
35
+ throw new error_1.FirebaseError(`Property "${prop}" must be of type ${type}`);
36
+ }
37
+ }
38
+ exports.assertType = assertType;
@@ -8,10 +8,12 @@ const promises_1 = require("fs/promises");
8
8
  const __1 = require("..");
9
9
  const prompt_1 = require("../../prompt");
10
10
  const proxy_1 = require("../../hosting/proxy");
11
+ const utils_1 = require("../utils");
11
12
  exports.name = "Angular";
12
13
  exports.support = "experimental";
13
14
  exports.type = 3;
14
15
  const CLI_COMMAND = (0, path_1.join)("node_modules", ".bin", process.platform === "win32" ? "ng.cmd" : "ng");
16
+ const DEFAULT_BUILD_SCRIPT = ["ng build"];
15
17
  async function discover(dir) {
16
18
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
17
19
  return;
@@ -48,6 +50,7 @@ async function build(dir) {
48
50
  if (!success)
49
51
  throw new Error(error);
50
52
  };
53
+ await (0, utils_1.warnIfCustomBuildScript)(dir, exports.name, DEFAULT_BUILD_SCRIPT);
51
54
  if (!browserTarget)
52
55
  throw new Error("No build target...");
53
56
  if (prerenderTarget) {
@@ -13,7 +13,11 @@ const semver_1 = require("semver");
13
13
  const logger_1 = require("../../logger");
14
14
  const error_1 = require("../../error");
15
15
  const fsutils_1 = require("../../fsutils");
16
+ const utils_1 = require("./utils");
17
+ const utils_2 = require("../utils");
18
+ const utils_3 = require("../utils");
16
19
  const CLI_COMMAND = (0, path_1.join)("node_modules", ".bin", process.platform === "win32" ? "next.cmd" : "next");
20
+ const DEFAULT_BUILD_SCRIPT = ["next build"];
17
21
  exports.name = "Next.js";
18
22
  exports.support = "experimental";
19
23
  exports.type = 2;
@@ -34,7 +38,9 @@ async function discover(dir) {
34
38
  }
35
39
  exports.discover = discover;
36
40
  async function build(dir) {
41
+ var _a, _b;
37
42
  const { default: nextBuild } = (0, __1.relativeRequire)(dir, "next/dist/build");
43
+ await (0, utils_3.warnIfCustomBuildScript)(dir, exports.name, DEFAULT_BUILD_SCRIPT);
38
44
  const reactVersion = getReactVersion(dir);
39
45
  if (reactVersion && (0, semver_1.gte)(reactVersion, "18.0.0")) {
40
46
  process.env.__NEXT_REACT_ROOT = "true";
@@ -74,23 +80,41 @@ async function build(dir) {
74
80
  wantsBackend = false;
75
81
  }
76
82
  }
77
- const manifestBuffer = await (0, promises_1.readFile)((0, path_1.join)(dir, distDir, "routes-manifest.json"));
78
- const manifest = JSON.parse(manifestBuffer.toString());
83
+ const manifest = await (0, utils_2.readJSON)((0, path_1.join)(dir, distDir, "routes-manifest.json"));
79
84
  const { headers: nextJsHeaders = [], redirects: nextJsRedirects = [], rewrites: nextJsRewrites = [], } = manifest;
80
- const headers = nextJsHeaders.map(({ source, headers }) => ({ source, headers }));
85
+ const isEveryHeaderSupported = nextJsHeaders.every(utils_1.isHeaderSupportedByFirebase);
86
+ if (!isEveryHeaderSupported)
87
+ wantsBackend = true;
88
+ const headers = nextJsHeaders.filter(utils_1.isHeaderSupportedByFirebase).map(({ source, headers }) => ({
89
+ source: (0, utils_1.cleanEscapedChars)(source),
90
+ headers,
91
+ }));
92
+ const isEveryRedirectSupported = nextJsRedirects.every(utils_1.isRedirectSupportedByFirebase);
93
+ if (!isEveryRedirectSupported)
94
+ wantsBackend = true;
81
95
  const redirects = nextJsRedirects
82
- .filter(({ internal }) => !internal)
83
- .map(({ source, destination, statusCode: type }) => ({ source, destination, type }));
84
- const nextJsRewritesToUse = Array.isArray(nextJsRewrites)
85
- ? nextJsRewrites
86
- : nextJsRewrites.beforeFiles || [];
96
+ .filter(utils_1.isRedirectSupportedByFirebase)
97
+ .map(({ source, destination, statusCode: type }) => ({
98
+ source: (0, utils_1.cleanEscapedChars)(source),
99
+ destination,
100
+ type,
101
+ }));
102
+ const nextJsRewritesToUse = (0, utils_1.getNextjsRewritesToUse)(nextJsRewrites);
103
+ if (!Array.isArray(nextJsRewrites) &&
104
+ (((_a = nextJsRewrites.afterFiles) === null || _a === void 0 ? void 0 : _a.length) || ((_b = nextJsRewrites.fallback) === null || _b === void 0 ? void 0 : _b.length))) {
105
+ wantsBackend = true;
106
+ }
107
+ else {
108
+ const isEveryRewriteSupported = nextJsRewritesToUse.every(utils_1.isRewriteSupportedByFirebase);
109
+ if (!isEveryRewriteSupported)
110
+ wantsBackend = true;
111
+ }
87
112
  const rewrites = nextJsRewritesToUse
88
- .map(({ source, destination, has }) => {
89
- if (has)
90
- return undefined;
91
- return { source, destination };
92
- })
93
- .filter((it) => it);
113
+ .filter(utils_1.isRewriteSupportedByFirebase)
114
+ .map(({ source, destination }) => ({
115
+ source: (0, utils_1.cleanEscapedChars)(source),
116
+ destination,
117
+ }));
94
118
  return { wantsBackend, headers, redirects, rewrites };
95
119
  }
96
120
  exports.build = build;
@@ -131,14 +155,33 @@ async function ɵcodegenPublicDirectory(sourceDir, destDir) {
131
155
  await (0, promises_1.copyFile)(appPath, (0, path_1.join)(destDir, file));
132
156
  }
133
157
  }
134
- const prerenderManifestBuffer = await (0, promises_1.readFile)((0, path_1.join)(sourceDir, distDir, "prerender-manifest.json"));
135
- const prerenderManifest = JSON.parse(prerenderManifestBuffer.toString());
158
+ const [prerenderManifest, routesManifest] = await Promise.all([
159
+ (0, utils_2.readJSON)((0, path_1.join)(sourceDir, distDir, "prerender-manifest.json")),
160
+ (0, utils_2.readJSON)((0, path_1.join)(sourceDir, distDir, "routes-manifest.json")),
161
+ ]);
162
+ const { redirects = [], rewrites = [], headers = [] } = routesManifest;
163
+ const rewritesToUse = (0, utils_1.getNextjsRewritesToUse)(rewrites);
164
+ const rewritesNotSupportedByFirebase = rewritesToUse.filter((rewrite) => !(0, utils_1.isRewriteSupportedByFirebase)(rewrite));
165
+ const rewritesRegexesNotSupportedByFirebase = rewritesNotSupportedByFirebase.map((rewrite) => new RegExp(rewrite.regex));
166
+ const redirectsNotSupportedByFirebase = redirects.filter((redirect) => !(0, utils_1.isRedirectSupportedByFirebase)(redirect));
167
+ const redirectsRegexesNotSupportedByFirebase = redirectsNotSupportedByFirebase.map((redirect) => new RegExp(redirect.regex));
168
+ const headersNotSupportedByFirebase = headers.filter((header) => !(0, utils_1.isHeaderSupportedByFirebase)(header));
169
+ const headersRegexesNotSupportedByFirebase = headersNotSupportedByFirebase.map((header) => new RegExp(header.regex));
136
170
  for (const path in prerenderManifest.routes) {
137
171
  if (prerenderManifest.routes[path]) {
138
172
  const { initialRevalidateSeconds } = prerenderManifest.routes[path];
139
173
  if (initialRevalidateSeconds) {
140
174
  continue;
141
175
  }
176
+ const routeMatchUnsupportedRewrite = rewritesRegexesNotSupportedByFirebase.some((rewriteRegex) => rewriteRegex.test(path));
177
+ if (routeMatchUnsupportedRewrite)
178
+ continue;
179
+ const routeMatchUnsupportedRedirect = redirectsRegexesNotSupportedByFirebase.some((redirectRegex) => redirectRegex.test(path));
180
+ if (routeMatchUnsupportedRedirect)
181
+ continue;
182
+ const routeMatchUnsupportedHeader = headersRegexesNotSupportedByFirebase.some((headerRegex) => headerRegex.test(path));
183
+ if (routeMatchUnsupportedHeader)
184
+ continue;
142
185
  const parts = path
143
186
  .split("/")
144
187
  .slice(1)
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNextjsRewritesToUse = exports.isHeaderSupportedByFirebase = exports.isRedirectSupportedByFirebase = exports.isRewriteSupportedByFirebase = exports.cleanEscapedChars = exports.pathHasRegex = void 0;
4
+ const utils_1 = require("../utils");
5
+ function pathHasRegex(path) {
6
+ return /(?<!\\)\(/.test(path);
7
+ }
8
+ exports.pathHasRegex = pathHasRegex;
9
+ function cleanEscapedChars(path) {
10
+ return path.replace(/\\([(){}:+?*])/g, (a, b) => b);
11
+ }
12
+ exports.cleanEscapedChars = cleanEscapedChars;
13
+ function isRewriteSupportedByFirebase(rewrite) {
14
+ return !("has" in rewrite || pathHasRegex(rewrite.source) || (0, utils_1.isUrl)(rewrite.destination));
15
+ }
16
+ exports.isRewriteSupportedByFirebase = isRewriteSupportedByFirebase;
17
+ function isRedirectSupportedByFirebase(redirect) {
18
+ return !("has" in redirect || pathHasRegex(redirect.source) || "internal" in redirect);
19
+ }
20
+ exports.isRedirectSupportedByFirebase = isRedirectSupportedByFirebase;
21
+ function isHeaderSupportedByFirebase(header) {
22
+ return !("has" in header || pathHasRegex(header.source));
23
+ }
24
+ exports.isHeaderSupportedByFirebase = isHeaderSupportedByFirebase;
25
+ function getNextjsRewritesToUse(nextJsRewrites) {
26
+ if (Array.isArray(nextJsRewrites)) {
27
+ return nextJsRewrites;
28
+ }
29
+ if (nextJsRewrites === null || nextJsRewrites === void 0 ? void 0 : nextJsRewrites.beforeFiles) {
30
+ return nextJsRewrites.beforeFiles;
31
+ }
32
+ return [];
33
+ }
34
+ exports.getNextjsRewritesToUse = getNextjsRewritesToUse;
@@ -6,9 +6,11 @@ const promises_1 = require("fs/promises");
6
6
  const path_1 = require("path");
7
7
  const semver_1 = require("semver");
8
8
  const __1 = require("..");
9
+ const utils_1 = require("../utils");
9
10
  exports.name = "Nuxt";
10
11
  exports.support = "experimental";
11
12
  exports.type = 4;
13
+ const DEFAULT_BUILD_SCRIPT = ["nuxt build"];
12
14
  async function discover(dir) {
13
15
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
14
16
  return;
@@ -26,6 +28,7 @@ exports.discover = discover;
26
28
  async function build(root) {
27
29
  const { buildNuxt } = await (0, __1.relativeRequire)(root, "@nuxt/kit");
28
30
  const nuxtApp = await getNuxtApp(root);
31
+ await (0, utils_1.warnIfCustomBuildScript)(root, exports.name, DEFAULT_BUILD_SCRIPT);
29
32
  await buildNuxt(nuxtApp);
30
33
  return { wantsBackend: true };
31
34
  }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.warnIfCustomBuildScript = exports.readJSON = exports.isUrl = void 0;
4
+ const fs_extra_1 = require("fs-extra");
5
+ const path_1 = require("path");
6
+ const promises_1 = require("fs/promises");
7
+ function isUrl(url) {
8
+ return /^https?:\/\//.test(url);
9
+ }
10
+ exports.isUrl = isUrl;
11
+ function readJSON(file, options) {
12
+ return (0, fs_extra_1.readJSON)(file, options);
13
+ }
14
+ exports.readJSON = readJSON;
15
+ async function warnIfCustomBuildScript(dir, framework, defaultBuildScripts) {
16
+ var _a;
17
+ const packageJsonBuffer = await (0, promises_1.readFile)((0, path_1.join)(dir, "package.json"));
18
+ const packageJson = JSON.parse(packageJsonBuffer.toString());
19
+ const buildScript = (_a = packageJson.scripts) === null || _a === void 0 ? void 0 : _a.build;
20
+ if (buildScript && !defaultBuildScripts.includes(buildScript)) {
21
+ console.warn(`\nWARNING: Your package.json contains a custom build that is being ignored. Only the ${framework} default build script (e.g, "${defaultBuildScripts[0]}") is respected. If you have a more advanced build process you should build a custom integration https://firebase.google.com/docs/hosting/express\n`);
22
+ }
23
+ }
24
+ exports.warnIfCustomBuildScript = warnIfCustomBuildScript;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDevModeHandle = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.vitePluginDiscover = exports.viteDiscoverWithNpmDependency = exports.init = exports.initViteTemplate = exports.type = exports.support = exports.name = void 0;
3
+ exports.getDevModeHandle = exports.ɵcodegenPublicDirectory = exports.build = exports.discover = exports.vitePluginDiscover = exports.viteDiscoverWithNpmDependency = exports.init = exports.initViteTemplate = exports.DEFAULT_BUILD_SCRIPT = exports.type = exports.support = exports.name = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const fs_1 = require("fs");
6
6
  const fs_extra_1 = require("fs-extra");
@@ -8,10 +8,12 @@ const path_1 = require("path");
8
8
  const __1 = require("..");
9
9
  const proxy_1 = require("../../hosting/proxy");
10
10
  const prompt_1 = require("../../prompt");
11
+ const utils_1 = require("../utils");
11
12
  exports.name = "Vite";
12
13
  exports.support = "experimental";
13
14
  exports.type = 4;
14
15
  const CLI_COMMAND = (0, path_1.join)("node_modules", ".bin", process.platform === "win32" ? "vite.cmd" : "vite");
16
+ exports.DEFAULT_BUILD_SCRIPT = ["vite build", "tsc && vite build"];
15
17
  const initViteTemplate = (template) => async (setup) => await init(setup, template);
16
18
  exports.initViteTemplate = initViteTemplate;
17
19
  async function init(setup, baseTemplate = "vanilla") {
@@ -56,6 +58,7 @@ async function discover(dir, plugin, npmDependency) {
56
58
  exports.discover = discover;
57
59
  async function build(root) {
58
60
  const { build } = (0, __1.relativeRequire)(root, "vite");
61
+ await (0, utils_1.warnIfCustomBuildScript)(root, exports.name, exports.DEFAULT_BUILD_SCRIPT);
59
62
  await build({ root });
60
63
  }
61
64
  exports.build = build;
@@ -81,7 +81,7 @@ async function doSetup(setup, config) {
81
81
  name: "download",
82
82
  type: "confirm",
83
83
  message: "Would you like to download the emulators now?",
84
- default: false,
84
+ default: true,
85
85
  },
86
86
  ]);
87
87
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "11.17.0",
3
+ "version": "11.18.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "firebase-tools",
9
- "version": "11.17.0",
9
+ "version": "11.18.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@google-cloud/pubsub": "^3.0.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "11.17.0",
3
+ "version": "11.18.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -388,7 +388,8 @@
388
388
  "nodejs10",
389
389
  "nodejs12",
390
390
  "nodejs14",
391
- "nodejs16"
391
+ "nodejs16",
392
+ "nodejs18"
392
393
  ],
393
394
  "type": "string"
394
395
  },
@@ -442,7 +443,8 @@
442
443
  "nodejs10",
443
444
  "nodejs12",
444
445
  "nodejs14",
445
- "nodejs16"
446
+ "nodejs16",
447
+ "nodejs18"
446
448
  ],
447
449
  "type": "string"
448
450
  },