firebase-tools 13.15.4 → 13.17.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 (35) hide show
  1. package/lib/api.js +3 -1
  2. package/lib/apphosting/index.js +2 -1
  3. package/lib/commands/dataconnect-sql-diff.js +2 -1
  4. package/lib/commands/dataconnect-sql-migrate.js +0 -1
  5. package/lib/commands/firestore-databases-restore.js +39 -1
  6. package/lib/dataconnect/client.js +2 -1
  7. package/lib/dataconnect/schemaMigration.js +134 -48
  8. package/lib/dataconnect/types.js +1 -0
  9. package/lib/deploy/functions/prepare.js +0 -8
  10. package/lib/deploy/functions/release/planner.js +2 -1
  11. package/lib/emulator/apphosting/index.js +38 -0
  12. package/lib/emulator/apphosting/serve.js +26 -0
  13. package/lib/emulator/auth/handlers.js +27 -0
  14. package/lib/emulator/auth/operations.js +41 -3
  15. package/lib/emulator/auth/state.js +2 -1
  16. package/lib/emulator/constants.js +10 -0
  17. package/lib/emulator/controller.js +24 -3
  18. package/lib/emulator/downloadableEmulators.js +12 -12
  19. package/lib/emulator/env.js +3 -0
  20. package/lib/emulator/functionsEmulator.js +19 -0
  21. package/lib/emulator/functionsEmulatorShared.js +17 -0
  22. package/lib/emulator/portUtils.js +2 -0
  23. package/lib/emulator/registry.js +2 -0
  24. package/lib/emulator/taskQueue.js +341 -0
  25. package/lib/emulator/tasksEmulator.js +238 -0
  26. package/lib/emulator/types.js +6 -0
  27. package/lib/experiments.js +4 -0
  28. package/lib/firestore/api.js +2 -1
  29. package/lib/firestore/options.js +7 -0
  30. package/lib/gcp/cloudsql/connect.js +49 -37
  31. package/lib/gcp/cloudsql/permissions.js +160 -0
  32. package/lib/init/features/dataconnect/index.js +5 -2
  33. package/lib/utils.js +9 -1
  34. package/package.json +1 -1
  35. package/schema/firebase-config.json +24 -0
package/lib/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
- exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = void 0;
4
+ exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = void 0;
5
5
  const constants_1 = require("./emulator/constants");
6
6
  const logger_1 = require("./logger");
7
7
  const scopes = require("./scopes");
@@ -128,6 +128,8 @@ const githubClientSecret = () => utils.envOverride("GITHUB_CLIENT_SECRET", "3330
128
128
  exports.githubClientSecret = githubClientSecret;
129
129
  const dataconnectOrigin = () => utils.envOverride("FIREBASE_DATACONNECT_URL", "https://firebasedataconnect.googleapis.com");
130
130
  exports.dataconnectOrigin = dataconnectOrigin;
131
+ const dataconnectP4SADomain = () => utils.envOverride("FIREBASE_DATACONNECT_P4SA_DOMAIN", "gcp-sa-firebasedataconnect.iam.gserviceaccount.com");
132
+ exports.dataconnectP4SADomain = dataconnectP4SADomain;
131
133
  const dataConnectLocalConnString = () => utils.envOverride("FIREBASE_DATACONNECT_POSTGRESQL_STRING", "");
132
134
  exports.dataConnectLocalConnString = dataConnectLocalConnString;
133
135
  const cloudSQLAdminOrigin = () => utils.envOverride("CLOUD_SQL_URL", "https://sqladmin.googleapis.com");
@@ -33,7 +33,8 @@ async function tlsReady(url) {
33
33
  }
34
34
  catch (err) {
35
35
  const maybeNodeError = err;
36
- if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code)) {
36
+ if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code) ||
37
+ "EPROTO" === (maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.code)) {
37
38
  return false;
38
39
  }
39
40
  return true;
@@ -17,9 +17,10 @@ exports.command = new command_1.Command("dataconnect:sql:diff [serviceId]")
17
17
  ])
18
18
  .before(requireAuth_1.requireAuth)
19
19
  .action(async (serviceId, options) => {
20
+ var _a;
20
21
  const projectId = (0, projectUtils_1.needProjectId)(options);
21
22
  await (0, ensureApis_1.ensureApis)(projectId);
22
23
  const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
23
- const diffs = await (0, schemaMigration_1.diffSchema)(serviceInfo.schema);
24
+ const diffs = await (0, schemaMigration_1.diffSchema)(serviceInfo.schema, (_a = serviceInfo.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
24
25
  return { projectId, serviceId, diffs };
25
26
  });
@@ -17,7 +17,6 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
17
17
  "firebasedataconnect.schemas.list",
18
18
  "firebasedataconnect.schemas.update",
19
19
  "cloudsql.instances.connect",
20
- "cloudsql.users.create",
21
20
  ])
22
21
  .before(requireAuth_1.requireAuth)
23
22
  .withForce("Execute any required database changes without prompting")
@@ -8,12 +8,17 @@ const logger_1 = require("../logger");
8
8
  const requirePermissions_1 = require("../requirePermissions");
9
9
  const types_1 = require("../emulator/types");
10
10
  const commandUtils_1 = require("../emulator/commandUtils");
11
+ const options_1 = require("../firestore/options");
11
12
  const pretty_print_1 = require("../firestore/pretty-print");
12
13
  const error_1 = require("../error");
13
14
  exports.command = new command_1.Command("firestore:databases:restore")
14
15
  .description("Restore a Firestore database in your Firebase project.")
15
16
  .option("-d, --database <databaseID>", "ID of the database to restore into")
16
17
  .option("-b, --backup <backup>", "Backup from which to restore")
18
+ .option("-e, --encryption-type <encryptionType>", `Encryption method of the restored database; one of ${options_1.EncryptionType.USE_SOURCE_ENCRYPTION} (default), ` +
19
+ `${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}, ${options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION}`)
20
+ .option("-k, --kms-key-name <kmsKeyName>", "Resource ID of the Cloud KMS key to encrypt the restored database. This " +
21
+ "feature is allowlist only in initial launch.")
17
22
  .before(requirePermissions_1.requirePermissions, ["datastore.backups.restoreDatabase"])
18
23
  .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
19
24
  .action(async (options) => {
@@ -28,7 +33,28 @@ exports.command = new command_1.Command("firestore:databases:restore")
28
33
  throw new error_1.FirebaseError(`Missing required flag --backup. ${helpCommandText}`);
29
34
  }
30
35
  const backupName = options.backup;
31
- const databaseResp = await api.restoreDatabase(options.project, databaseId, backupName);
36
+ let encryptionConfig = undefined;
37
+ switch (options.encryptionType) {
38
+ case options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION:
39
+ throwIfKmsKeyNameIsSet(options.kmsKeyName);
40
+ encryptionConfig = { googleDefaultEncryption: {} };
41
+ break;
42
+ case options_1.EncryptionType.USE_SOURCE_ENCRYPTION:
43
+ throwIfKmsKeyNameIsSet(options.kmsKeyName);
44
+ encryptionConfig = { useSourceEncryption: {} };
45
+ break;
46
+ case options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION:
47
+ encryptionConfig = {
48
+ customerManagedEncryption: { kmsKeyName: getKmsKeyOrThrow(options.kmsKeyName) },
49
+ };
50
+ break;
51
+ case undefined:
52
+ throwIfKmsKeyNameIsSet(options.kmsKeyName);
53
+ break;
54
+ default:
55
+ throw new error_1.FirebaseError(`Invalid value for flag --encryption-type. ${helpCommandText}`);
56
+ }
57
+ const databaseResp = await api.restoreDatabase(options.project, databaseId, backupName, encryptionConfig);
32
58
  if (options.json) {
33
59
  logger_1.logger.info(JSON.stringify(databaseResp, undefined, 2));
34
60
  }
@@ -40,4 +66,16 @@ exports.command = new command_1.Command("firestore:databases:restore")
40
66
  logger_1.logger.info(`Once the restore is complete, your database may be viewed at ${printer.firebaseConsoleDatabaseUrl(options.project, databaseId)}`);
41
67
  }
42
68
  return databaseResp;
69
+ function throwIfKmsKeyNameIsSet(kmsKeyName) {
70
+ if (kmsKeyName) {
71
+ throw new error_1.FirebaseError("--kms-key-name can only be set when specifying an --encryption-type " +
72
+ `of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
73
+ }
74
+ }
75
+ function getKmsKeyOrThrow(kmsKeyName) {
76
+ if (kmsKeyName)
77
+ return kmsKeyName;
78
+ throw new error_1.FirebaseError("--kms-key-name must be provided when specifying an --encryption-type " +
79
+ `of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
80
+ }
43
81
  });
@@ -123,13 +123,14 @@ async function deleteConnector(name) {
123
123
  return;
124
124
  }
125
125
  exports.deleteConnector = deleteConnector;
126
- async function listConnectors(serviceName) {
126
+ async function listConnectors(serviceName, fields = []) {
127
127
  const connectors = [];
128
128
  const getNextPage = async (pageToken = "") => {
129
129
  const res = await dataconnectClient().get(`${serviceName}/connectors`, {
130
130
  queryParams: {
131
131
  pageSize: PAGE_SIZE_MAX,
132
132
  pageToken,
133
+ fields: fields.join(","),
133
134
  },
134
135
  });
135
136
  connectors.push(...(res.body.connectors || []));
@@ -6,23 +6,41 @@ const sql_formatter_1 = require("sql-formatter");
6
6
  const types_1 = require("./types");
7
7
  const client_1 = require("./client");
8
8
  const connect_1 = require("../gcp/cloudsql/connect");
9
+ const permissions_1 = require("../gcp/cloudsql/permissions");
9
10
  const prompt_1 = require("../prompt");
10
11
  const logger_1 = require("../logger");
11
12
  const error_1 = require("../error");
12
- const projectUtils_1 = require("../projectUtils");
13
13
  const utils_1 = require("../utils");
14
14
  const experiments = require("../experiments");
15
15
  const errors = require("./errors");
16
- async function diffSchema(schema) {
16
+ async function diffSchema(schema, schemaValidation) {
17
17
  const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
18
18
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
19
- setCompatibleMode(schema, databaseId, instanceName);
19
+ let diffs = [];
20
+ let validationMode = "STRICT";
21
+ if (experiments.isEnabled("fdccompatiblemode")) {
22
+ if (!schemaValidation) {
23
+ validationMode = "COMPATIBLE";
24
+ }
25
+ else {
26
+ validationMode = schemaValidation;
27
+ }
28
+ }
29
+ setSchemaValidationMode(schema, validationMode);
20
30
  try {
31
+ if (!schemaValidation && experiments.isEnabled("fdccompatiblemode")) {
32
+ (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
33
+ }
21
34
  await (0, client_1.upsertSchema)(schema, true);
22
- (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
35
+ if (validationMode === "STRICT") {
36
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
37
+ }
38
+ else {
39
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is compatible.`);
40
+ }
23
41
  }
24
42
  catch (err) {
25
- if (err.status !== 400) {
43
+ if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
26
44
  throw err;
27
45
  }
28
46
  const invalidConnectors = errors.getInvalidConnectors(err);
@@ -34,24 +52,56 @@ async function diffSchema(schema) {
34
52
  displayInvalidConnectors(invalidConnectors);
35
53
  }
36
54
  if (incompatible) {
37
- displaySchemaChanges(incompatible);
38
- return incompatible.diffs;
55
+ displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
56
+ diffs = incompatible.diffs;
39
57
  }
40
58
  }
41
- return [];
59
+ if (experiments.isEnabled("fdccompatiblemode")) {
60
+ if (!schemaValidation) {
61
+ validationMode = "STRICT";
62
+ setSchemaValidationMode(schema, validationMode);
63
+ try {
64
+ (0, utils_1.logLabeledBullet)("dataconnect", `generating schema changes, including optional changes...`);
65
+ await (0, client_1.upsertSchema)(schema, true);
66
+ (0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
67
+ }
68
+ catch (err) {
69
+ if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
70
+ throw err;
71
+ }
72
+ const incompatible = errors.getIncompatibleSchemaError(err);
73
+ if (incompatible) {
74
+ if (!diffsEqual(diffs, incompatible.diffs)) {
75
+ if (diffs.length === 0) {
76
+ displaySchemaChanges(incompatible, "STRICT_AFTER_COMPATIBLE", instanceName, databaseId);
77
+ }
78
+ else {
79
+ displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
80
+ }
81
+ diffs = incompatible.diffs;
82
+ }
83
+ else {
84
+ (0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return diffs;
42
91
  }
43
92
  exports.diffSchema = diffSchema;
44
93
  async function migrateSchema(args) {
45
94
  const { options, schema, validateOnly } = args;
46
95
  const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
47
96
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
48
- setCompatibleMode(schema, databaseId, instanceName);
97
+ const validationMode = experiments.isEnabled("fdccompatiblemode") ? "COMPATIBLE" : "STRICT";
98
+ setSchemaValidationMode(schema, validationMode);
49
99
  try {
50
100
  await (0, client_1.upsertSchema)(schema, validateOnly);
51
101
  logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
52
102
  }
53
103
  catch (err) {
54
- if (err.status !== 400) {
104
+ if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
55
105
  throw err;
56
106
  }
57
107
  const incompatible = errors.getIncompatibleSchemaError(err);
@@ -59,7 +109,7 @@ async function migrateSchema(args) {
59
109
  if (!incompatible && !invalidConnectors.length) {
60
110
  throw err;
61
111
  }
62
- const migrationMode = await promptForSchemaMigration(options, databaseId, incompatible, validateOnly);
112
+ const migrationMode = await promptForSchemaMigration(options, instanceName, databaseId, incompatible, validateOnly, validationMode);
63
113
  const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
64
114
  let diffs = [];
65
115
  if (incompatible) {
@@ -82,24 +132,23 @@ async function migrateSchema(args) {
82
132
  return [];
83
133
  }
84
134
  exports.migrateSchema = migrateSchema;
85
- function setCompatibleMode(schema, databaseId, instanceName) {
86
- var _a;
87
- if (experiments.isEnabled("fdccompatiblemode")) {
88
- if ((_a = schema.primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation) {
89
- schema.primaryDatasource.postgresql.schemaValidation = "COMPATIBLE";
90
- }
91
- else {
92
- schema.primaryDatasource = {
93
- postgresql: {
94
- database: databaseId,
95
- cloudSql: {
96
- instance: instanceName,
97
- },
98
- schemaValidation: "COMPATIBLE",
99
- },
100
- };
135
+ function diffsEqual(x, y) {
136
+ if (x.length !== y.length) {
137
+ return false;
138
+ }
139
+ for (let i = 0; i < x.length; i++) {
140
+ if (x[i].description !== y[i].description ||
141
+ x[i].destructive !== y[i].destructive ||
142
+ x[i].sql !== y[i].sql) {
143
+ return false;
101
144
  }
102
145
  }
146
+ return true;
147
+ }
148
+ function setSchemaValidationMode(schema, schemaValidation) {
149
+ if (experiments.isEnabled("fdccompatiblemode") && schema.primaryDatasource.postgresql) {
150
+ schema.primaryDatasource.postgresql.schemaValidation = schemaValidation;
151
+ }
103
152
  }
104
153
  function getIdentifiers(schema) {
105
154
  var _a, _b;
@@ -131,8 +180,6 @@ async function handleIncompatibleSchemaError(args) {
131
180
  if (incompatibleSchemaError.destructive && choice === "safe") {
132
181
  throw new error_1.FirebaseError("This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
133
182
  }
134
- const projectId = (0, projectUtils_1.needProjectId)(options);
135
- const iamUser = await (0, connect_1.setupIAMUser)(instanceId, databaseId, options);
136
183
  const commandsToExecute = incompatibleSchemaError.diffs
137
184
  .filter((d) => {
138
185
  switch (choice) {
@@ -146,25 +193,36 @@ async function handleIncompatibleSchemaError(args) {
146
193
  })
147
194
  .map((d) => d.sql);
148
195
  if (commandsToExecute.length) {
149
- await (0, connect_1.execute)([
150
- `SET ROLE "${(0, connect_1.firebaseowner)(databaseId)}"`,
151
- ...commandsToExecute,
152
- `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
153
- ], {
154
- projectId,
155
- instanceId,
156
- databaseId,
157
- username: iamUser,
158
- });
159
- return incompatibleSchemaError.diffs;
196
+ const commandsToExecuteBySuperUser = commandsToExecute.filter((sql) => sql.startsWith("CREATE EXTENSION") || sql.startsWith("CREATE SCHEMA"));
197
+ const commandsToExecuteByOwner = commandsToExecute.filter((sql) => !commandsToExecuteBySuperUser.includes(sql));
198
+ const userIsCSQLAdmin = await (0, permissions_1.iamUserIsCSQLAdmin)(options);
199
+ if (!userIsCSQLAdmin && commandsToExecuteBySuperUser.length) {
200
+ throw new error_1.FirebaseError(`Some SQL commands required for this migration require Admin permissions.\n
201
+ Please ask a user with 'roles/cloudsql.admin' to apply the following commands.\n
202
+ ${commandsToExecuteBySuperUser.join("\n")}`);
203
+ }
204
+ if (userIsCSQLAdmin) {
205
+ await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
206
+ }
207
+ if (!(await (0, permissions_1.checkSQLRoleIsGranted)(options, instanceId, databaseId, (0, permissions_1.firebaseowner)(databaseId), (await (0, connect_1.getIAMUser)(options)).user))) {
208
+ throw new error_1.FirebaseError(`Command aborted. Only users granted firebaseowner SQL role can run migrations.`);
209
+ }
210
+ if (commandsToExecuteBySuperUser.length) {
211
+ logger_1.logger.info(`The diffs require CloudSQL superuser permissions, attempting to apply changes as superuser.`);
212
+ await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, commandsToExecuteBySuperUser, false);
213
+ }
214
+ if (commandsToExecuteByOwner.length) {
215
+ await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [`SET ROLE "${(0, permissions_1.firebaseowner)(databaseId)}"`, ...commandsToExecuteByOwner], false);
216
+ return incompatibleSchemaError.diffs;
217
+ }
160
218
  }
161
219
  return [];
162
220
  }
163
- async function promptForSchemaMigration(options, databaseName, err, validateOnly) {
221
+ async function promptForSchemaMigration(options, instanceName, databaseId, err, validateOnly, schemaValidation) {
164
222
  if (!err) {
165
223
  return "none";
166
224
  }
167
- displaySchemaChanges(err);
225
+ displaySchemaChanges(err, schemaValidation, instanceName, databaseId);
168
226
  if (!options.nonInteractive) {
169
227
  if (validateOnly && options.force) {
170
228
  return "all";
@@ -179,7 +237,7 @@ async function promptForSchemaMigration(options, databaseName, err, validateOnly
179
237
  { name: "Abort changes", value: "none" },
180
238
  ];
181
239
  return await (0, prompt_1.promptOnce)({
182
- message: `Would you like to execute these changes against ${databaseName}?`,
240
+ message: `Would you like to execute these changes against ${databaseId}?`,
183
241
  type: "list",
184
242
  choices,
185
243
  });
@@ -264,19 +322,47 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
264
322
  await (0, client_1.upsertSchema)(currentSchema, false);
265
323
  }
266
324
  catch (err) {
267
- if (err.status >= 500) {
325
+ if ((err === null || err === void 0 ? void 0 : err.status) >= 500) {
268
326
  throw err;
269
327
  }
270
328
  logger_1.logger.debug(err);
271
329
  }
272
330
  }
273
- function displaySchemaChanges(error) {
331
+ function displaySchemaChanges(error, schemaValidation, instanceName, databaseId) {
274
332
  switch (error.violationType) {
275
333
  case "INCOMPATIBLE_SCHEMA":
276
334
  {
277
- const message = "Your new schema is incompatible with the schema of your CloudSQL database. " +
278
- "The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
279
- error.diffs.map(toString).join("\n");
335
+ let message;
336
+ if (schemaValidation === "COMPATIBLE") {
337
+ message =
338
+ "Your new application schema is incompatible with the schema of your PostgreSQL database " +
339
+ databaseId +
340
+ " in your CloudSQL instance " +
341
+ instanceName +
342
+ ". " +
343
+ "The following SQL statements will migrate your database schema to be compatible with your new Data Connect schema.\n" +
344
+ error.diffs.map(toString).join("\n");
345
+ }
346
+ else if (schemaValidation === "STRICT_AFTER_COMPATIBLE") {
347
+ message =
348
+ "Your new application schema is compatible with the schema of your PostgreSQL database " +
349
+ databaseId +
350
+ " in your CloudSQL instance " +
351
+ instanceName +
352
+ ", but contains unused tables or columns. " +
353
+ "The following optional SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
354
+ error.diffs.map(toString).join("\n");
355
+ }
356
+ else {
357
+ message =
358
+ "Your new application schema does not match the schema of your PostgreSQL database " +
359
+ databaseId +
360
+ " in your CloudSQL instance " +
361
+ instanceName +
362
+ ". " +
363
+ "The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
364
+ error.diffs.map(toString).join("\n");
365
+ }
280
366
  (0, utils_1.logLabeledWarning)("dataconnect", message);
281
367
  }
282
368
  break;
@@ -22,6 +22,7 @@ function toDatasource(projectId, locationId, ds) {
22
22
  cloudSql: {
23
23
  instance: `projects/${projectId}/locations/${locationId}/instances/${ds.postgresql.cloudSql.instanceId}`,
24
24
  },
25
+ schemaValidation: ds.postgresql.schemaValidation,
25
26
  },
26
27
  };
27
28
  }
@@ -27,7 +27,6 @@ const serviceusage_1 = require("../../gcp/serviceusage");
27
27
  const applyHash_1 = require("./cache/applyHash");
28
28
  const backend_1 = require("./backend");
29
29
  const functional_1 = require("../../functional");
30
- const prepare_1 = require("../extensions/prepare");
31
30
  exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
32
31
  async function prepare(context, options, payload) {
33
32
  var _a, _b;
@@ -56,13 +55,6 @@ async function prepare(context, options, payload) {
56
55
  }
57
56
  context.codebaseDeployEvents = {};
58
57
  const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
59
- if (Object.values(wantBuilds).some((b) => b.extensions)) {
60
- const extContext = {};
61
- const extPayload = {};
62
- await (0, prepare_1.prepareDynamicExtensions)(extContext, options, extPayload, wantBuilds);
63
- context.extensions = extContext;
64
- payload.extensions = extPayload;
65
- }
66
58
  const codebaseUsesEnvs = [];
67
59
  const wantBackends = {};
68
60
  for (const [codebase, wantBuild] of Object.entries(wantBuilds)) {
@@ -207,7 +207,8 @@ function checkForIllegalUpdate(want, have) {
207
207
  exports.checkForIllegalUpdate = checkForIllegalUpdate;
208
208
  function checkForV2Upgrade(want, have) {
209
209
  if (want.platform === "gcfv2" && have.platform === "gcfv1") {
210
- throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.`);
210
+ throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from 1st Gen to 2nd Gen is not yet supported. ` +
211
+ "See https://firebase.google.com/docs/functions/2nd-gen-upgrade before migrating to 2nd Gen.");
211
212
  }
212
213
  }
213
214
  exports.checkForV2Upgrade = checkForV2Upgrade;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppHostingEmulator = void 0;
4
+ const emulatorLogger_1 = require("../emulatorLogger");
5
+ const types_1 = require("../types");
6
+ const serve_1 = require("./serve");
7
+ class AppHostingEmulator {
8
+ constructor(args) {
9
+ this.args = args;
10
+ this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
11
+ }
12
+ async start() {
13
+ this.args.options.host = this.args.host;
14
+ this.args.options.port = this.args.port;
15
+ this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "starting apphosting emulator");
16
+ const { port } = await (0, serve_1.start)(this.args.options);
17
+ this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, `serving on port ${port}`);
18
+ }
19
+ connect() {
20
+ this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "connecting apphosting emulator");
21
+ return Promise.resolve();
22
+ }
23
+ stop() {
24
+ this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "stopping apphosting emulator");
25
+ return Promise.resolve();
26
+ }
27
+ getInfo() {
28
+ return {
29
+ name: types_1.Emulators.APPHOSTING,
30
+ host: this.args.host,
31
+ port: this.args.port,
32
+ };
33
+ }
34
+ getName() {
35
+ return types_1.Emulators.APPHOSTING;
36
+ }
37
+ }
38
+ exports.AppHostingEmulator = AppHostingEmulator;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serve = exports.start = void 0;
4
+ const net_1 = require("net");
5
+ const portUtils_1 = require("../portUtils");
6
+ const spawn_1 = require("../../init/spawn");
7
+ async function start(options) {
8
+ let port = options.port;
9
+ while (!(await availablePort(options.host, port))) {
10
+ port += 1;
11
+ }
12
+ serve(options, port);
13
+ return { port };
14
+ }
15
+ exports.start = start;
16
+ function availablePort(host, port) {
17
+ return (0, portUtils_1.checkListenable)({
18
+ address: host,
19
+ port,
20
+ family: (0, net_1.isIPv4)(host) ? "IPv4" : "IPv6",
21
+ });
22
+ }
23
+ async function serve(options, port) {
24
+ await (0, spawn_1.wrapSpawn)("npm", ["run", "dev", "--", "-H", options.host, "-p", port], process.cwd());
25
+ }
26
+ exports.serve = serve;
@@ -131,6 +131,33 @@ function registerHandlers(app, getProjectStateByApiKey) {
131
131
  }
132
132
  }
133
133
  }
134
+ case "verifyAndChangeEmail": {
135
+ try {
136
+ const { newEmail } = (0, operations_1.setAccountInfoImpl)(state, { oobCode });
137
+ if (continueUrl) {
138
+ return res.redirect(303, continueUrl);
139
+ }
140
+ else {
141
+ return res.status(200).json({
142
+ authEmulator: { success: `The email has been successfully changed.`, newEmail },
143
+ });
144
+ }
145
+ }
146
+ catch (e) {
147
+ if (e instanceof errors_1.NotImplementedError ||
148
+ (e instanceof errors_1.BadRequestError && e.message === "INVALID_OOB_CODE")) {
149
+ return res.status(400).json({
150
+ authEmulator: {
151
+ error: `Your request to change your email has expired or the link has already been used.`,
152
+ instructions: `Try changing your email again.`,
153
+ },
154
+ });
155
+ }
156
+ else {
157
+ throw e;
158
+ }
159
+ }
160
+ }
134
161
  case "signIn": {
135
162
  if (!continueUrl) {
136
163
  return res.status(400).json({