firebase-tools 13.13.3 → 13.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/lib/api.js +4 -2
  2. package/lib/apphosting/githubConnections.js +108 -34
  3. package/lib/commands/dataconnect-sdk-generate.js +2 -7
  4. package/lib/commands/firestore-backups-schedules-create.js +11 -14
  5. package/lib/commands/firestore-backups-schedules-list.js +1 -1
  6. package/lib/commands/firestore-backups-schedules-update.js +4 -3
  7. package/lib/commands/firestore-databases-create.js +5 -6
  8. package/lib/commands/firestore-databases-restore.js +4 -4
  9. package/lib/commands/firestore-databases-update.js +5 -6
  10. package/lib/commands/help.js +2 -0
  11. package/lib/commands/init.js +2 -2
  12. package/lib/dataconnect/fileUtils.js +2 -2
  13. package/lib/dataconnect/filters.js +12 -1
  14. package/lib/dataconnect/load.js +6 -5
  15. package/lib/dataconnect/schemaMigration.js +22 -0
  16. package/lib/deploy/dataconnect/prepare.js +16 -2
  17. package/lib/emulator/constants.js +1 -0
  18. package/lib/emulator/controller.js +2 -5
  19. package/lib/emulator/dataconnectEmulator.js +3 -2
  20. package/lib/emulator/downloadableEmulators.js +12 -12
  21. package/lib/emulator/eventarcEmulator.js +68 -18
  22. package/lib/emulator/functionsEmulator.js +79 -0
  23. package/lib/emulator/functionsEmulatorShared.js +4 -0
  24. package/lib/emulator/pubsubEmulator.js +1 -1
  25. package/lib/experiments.js +5 -0
  26. package/lib/frameworks/angular/utils.js +13 -5
  27. package/lib/functions/events/v2.js +2 -1
  28. package/lib/gcp/devConnect.js +13 -1
  29. package/lib/init/features/dataconnect/index.js +69 -19
  30. package/lib/init/features/dataconnect/sdk.js +2 -3
  31. package/lib/init/features/hosting/github.js +44 -21
  32. package/package.json +8 -8
  33. package/templates/init/dataconnect/dataconnect.yaml +1 -1
package/lib/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serviceUsageOrigin = 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.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 = void 0;
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;
5
5
  const constants_1 = require("./emulator/constants");
6
6
  const logger_1 = require("./logger");
7
7
  const scopes = require("./scopes");
@@ -33,6 +33,8 @@ const apphostingOrigin = () => utils.envOverride("FIREBASE_APPHOSTING_URL", "htt
33
33
  exports.apphostingOrigin = apphostingOrigin;
34
34
  const apphostingP4SADomain = () => utils.envOverride("FIREBASE_APPHOSTING_P4SA_DOMAIN", "gcp-sa-firebaseapphosting.iam.gserviceaccount.com");
35
35
  exports.apphostingP4SADomain = apphostingP4SADomain;
36
+ const apphostingGitHubAppInstallationURL = () => utils.envOverride("FIREBASE_APPHOSTING_GITHUB_INSTALLATION_URL", "https://github.com/apps/firebase-app-hosting/installations/new");
37
+ exports.apphostingGitHubAppInstallationURL = apphostingGitHubAppInstallationURL;
36
38
  const authOrigin = () => utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com");
37
39
  exports.authOrigin = authOrigin;
38
40
  const consoleOrigin = () => utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fetchAllRepositories = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.linkGitHubRepository = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
3
+ exports.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
4
4
  const clc = require("colorette");
5
5
  const devConnect = require("../gcp/devConnect");
6
6
  const rm = require("../gcp/resourceManager");
@@ -51,39 +51,49 @@ function generateConnectionId() {
51
51
  const randomHash = Math.random().toString(36).slice(6);
52
52
  return `apphosting-github-conn-${randomHash}`;
53
53
  }
54
- const ADD_CONN_CHOICE = "@ADD_CONN";
54
+ const ADD_ACCOUNT_CHOICE = "@ADD_ACCOUNT";
55
+ const MANAGE_INSTALLATION_CHOICE = "@MANAGE_INSTALLATION";
55
56
  async function linkGitHubRepository(projectId, location) {
56
57
  var _a, _b;
57
58
  utils.logBullet(clc.bold(`${clc.yellow("===")} Import a GitHub repository`));
58
59
  const oauthConn = await getOrCreateOauthConnection(projectId, location);
59
- const existingConns = await listAppHostingConnections(projectId, location);
60
- if (existingConns.length === 0) {
61
- existingConns.push(await createFullyInstalledConnection(projectId, location, generateConnectionId(), oauthConn));
60
+ let installationId = await promptGitHubInstallation(projectId, location, oauthConn);
61
+ while (installationId === ADD_ACCOUNT_CHOICE) {
62
+ utils.logBullet("Install the Firebase App Hosting GitHub app on a new account to enable access to those repositories");
63
+ const apphostingGitHubInstallationURL = (0, api_1.apphostingGitHubAppInstallationURL)();
64
+ utils.logBullet(apphostingGitHubInstallationURL);
65
+ await utils.openInBrowser(apphostingGitHubInstallationURL);
66
+ await (0, prompt_1.promptOnce)({
67
+ type: "input",
68
+ message: "Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.",
69
+ });
70
+ installationId = await promptGitHubInstallation(projectId, location, oauthConn);
71
+ }
72
+ let connectionMatchingInstallation = await getConnectionForInstallation(projectId, location, installationId);
73
+ if (!connectionMatchingInstallation) {
74
+ connectionMatchingInstallation = await createFullyInstalledConnection(projectId, location, generateConnectionId(), oauthConn, installationId);
62
75
  }
63
76
  let repoCloneUri;
64
- let connection;
65
77
  do {
66
- if (repoCloneUri === ADD_CONN_CHOICE) {
67
- existingConns.push(await createFullyInstalledConnection(projectId, location, generateConnectionId(), oauthConn, true));
78
+ if (repoCloneUri === MANAGE_INSTALLATION_CHOICE) {
79
+ await manageInstallation(connectionMatchingInstallation);
68
80
  }
69
- const selection = await promptCloneUri(projectId, existingConns);
70
- repoCloneUri = selection.cloneUri;
71
- connection = selection.connection;
72
- } while (repoCloneUri === ADD_CONN_CHOICE);
73
- const { id: connectionId } = parseConnectionName(connection.name);
81
+ repoCloneUri = await promptCloneUri(projectId, connectionMatchingInstallation);
82
+ } while (repoCloneUri === MANAGE_INSTALLATION_CHOICE);
83
+ const { id: connectionId } = parseConnectionName(connectionMatchingInstallation.name);
74
84
  await getOrCreateConnection(projectId, location, connectionId, {
75
- authorizerCredential: (_a = connection.githubConfig) === null || _a === void 0 ? void 0 : _a.authorizerCredential,
76
- appInstallationId: (_b = connection.githubConfig) === null || _b === void 0 ? void 0 : _b.appInstallationId,
85
+ authorizerCredential: (_a = connectionMatchingInstallation.githubConfig) === null || _a === void 0 ? void 0 : _a.authorizerCredential,
86
+ appInstallationId: (_b = connectionMatchingInstallation.githubConfig) === null || _b === void 0 ? void 0 : _b.appInstallationId,
77
87
  });
78
88
  const repo = await getOrCreateRepository(projectId, location, connectionId, repoCloneUri);
79
89
  return repo;
80
90
  }
81
91
  exports.linkGitHubRepository = linkGitHubRepository;
82
- async function createFullyInstalledConnection(projectId, location, connectionId, oauthConn, withNewInstallation = false) {
83
- var _a, _b;
92
+ async function createFullyInstalledConnection(projectId, location, connectionId, oauthConn, installationId) {
93
+ var _a;
84
94
  let conn = await createConnection(projectId, location, connectionId, {
85
- appInstallationId: withNewInstallation ? undefined : (_a = oauthConn.githubConfig) === null || _a === void 0 ? void 0 : _a.appInstallationId,
86
- authorizerCredential: (_b = oauthConn.githubConfig) === null || _b === void 0 ? void 0 : _b.authorizerCredential,
95
+ appInstallationId: installationId,
96
+ authorizerCredential: (_a = oauthConn.githubConfig) === null || _a === void 0 ? void 0 : _a.authorizerCredential,
87
97
  });
88
98
  while (conn.installationState.stage !== "COMPLETE") {
89
99
  utils.logBullet("Install the Firebase App Hosting GitHub app to enable access to GitHub repositories");
@@ -98,6 +108,75 @@ async function createFullyInstalledConnection(projectId, location, connectionId,
98
108
  }
99
109
  return conn;
100
110
  }
111
+ async function manageInstallation(connection) {
112
+ var _a;
113
+ utils.logBullet("Manage the Firebase App Hosting GitHub app to enable access to GitHub repositories");
114
+ const targetUri = (_a = connection.githubConfig) === null || _a === void 0 ? void 0 : _a.installationUri;
115
+ if (!targetUri) {
116
+ throw new error_1.FirebaseError("Failed to get Installation URI. Please try again.");
117
+ }
118
+ utils.logBullet(targetUri);
119
+ await utils.openInBrowser(targetUri);
120
+ await (0, prompt_1.promptOnce)({
121
+ type: "input",
122
+ message: "Press Enter once you have installed or configured the Firebase App Hosting GitHub app to access your GitHub repo.",
123
+ });
124
+ }
125
+ async function getConnectionForInstallation(projectId, location, installationId) {
126
+ const connections = await listAppHostingConnections(projectId, location);
127
+ const connectionsMatchingInstallation = connections.filter((conn) => { var _a; return ((_a = conn.githubConfig) === null || _a === void 0 ? void 0 : _a.appInstallationId) === installationId; });
128
+ if (connectionsMatchingInstallation.length === 0) {
129
+ return null;
130
+ }
131
+ if (connectionsMatchingInstallation.length > 1) {
132
+ const sorted = devConnect.sortConnectionsByCreateTime(connectionsMatchingInstallation);
133
+ return sorted[0];
134
+ }
135
+ return connectionsMatchingInstallation[0];
136
+ }
137
+ exports.getConnectionForInstallation = getConnectionForInstallation;
138
+ async function promptGitHubInstallation(projectId, location, connection) {
139
+ const installations = await listValidInstallations(projectId, location, connection);
140
+ const installationName = await (0, prompt_1.promptOnce)({
141
+ type: "autocomplete",
142
+ name: "installation",
143
+ message: "Which GitHub account do you want to use?",
144
+ source: (_, input = "") => {
145
+ return new Promise((resolve) => resolve([
146
+ new inquirer.Separator(),
147
+ {
148
+ name: "Missing an account? Select this option to add a GitHub account",
149
+ value: ADD_ACCOUNT_CHOICE,
150
+ },
151
+ new inquirer.Separator(),
152
+ ...fuzzy
153
+ .filter(input, installations, {
154
+ extract: (installation) => installation.name || "",
155
+ })
156
+ .map((result) => {
157
+ return {
158
+ name: result.original.name || "",
159
+ value: result.original.id,
160
+ };
161
+ }),
162
+ ]));
163
+ },
164
+ });
165
+ return installationName;
166
+ }
167
+ exports.promptGitHubInstallation = promptGitHubInstallation;
168
+ async function listValidInstallations(projectId, location, connection) {
169
+ const { id: connId } = parseConnectionName(connection.name);
170
+ let installations = await devConnect.fetchGitHubInstallations(projectId, location, connId);
171
+ installations = installations.filter((installation) => {
172
+ var _a, _b;
173
+ return ((installation.type === "user" &&
174
+ installation.name === ((_b = (_a = connection.githubConfig) === null || _a === void 0 ? void 0 : _a.authorizerCredential) === null || _b === void 0 ? void 0 : _b.username)) ||
175
+ installation.type === "organization");
176
+ });
177
+ return installations;
178
+ }
179
+ exports.listValidInstallations = listValidInstallations;
101
180
  async function getOrCreateOauthConnection(projectId, location) {
102
181
  let conn;
103
182
  try {
@@ -128,8 +207,8 @@ async function getOrCreateOauthConnection(projectId, location) {
128
207
  return conn;
129
208
  }
130
209
  exports.getOrCreateOauthConnection = getOrCreateOauthConnection;
131
- async function promptCloneUri(projectId, connections) {
132
- const { cloneUris, cloneUriToConnection } = await fetchAllRepositories(projectId, connections);
210
+ async function promptCloneUri(projectId, connection) {
211
+ const cloneUris = await fetchRepositoryCloneUris(projectId, connection);
133
212
  const cloneUri = await (0, prompt_1.promptOnce)({
134
213
  type: "autocomplete",
135
214
  name: "cloneUri",
@@ -139,7 +218,7 @@ async function promptCloneUri(projectId, connections) {
139
218
  new inquirer.Separator(),
140
219
  {
141
220
  name: "Missing a repo? Select this option to configure your GitHub connection settings",
142
- value: ADD_CONN_CHOICE,
221
+ value: MANAGE_INSTALLATION_CHOICE,
143
222
  },
144
223
  new inquirer.Separator(),
145
224
  ...fuzzy
@@ -155,7 +234,7 @@ async function promptCloneUri(projectId, connections) {
155
234
  ]));
156
235
  },
157
236
  });
158
- return { cloneUri, connection: cloneUriToConnection[cloneUri] };
237
+ return cloneUri;
159
238
  }
160
239
  async function promptGitHubBranch(repoLink) {
161
240
  const branches = await devConnect.listAllBranches(repoLink.name);
@@ -260,15 +339,10 @@ async function listAppHostingConnections(projectId, location) {
260
339
  !conn.disabled);
261
340
  }
262
341
  exports.listAppHostingConnections = listAppHostingConnections;
263
- async function fetchAllRepositories(projectId, connections) {
264
- const cloneUriToConnection = {};
265
- for (const conn of connections) {
266
- const { location, id } = parseConnectionName(conn.name);
267
- const connectionRepos = await devConnect.listAllLinkableGitRepositories(projectId, location, id);
268
- connectionRepos.forEach((repo) => {
269
- cloneUriToConnection[repo.cloneUri] = conn;
270
- });
271
- }
272
- return { cloneUris: Object.keys(cloneUriToConnection), cloneUriToConnection };
342
+ async function fetchRepositoryCloneUris(projectId, connection) {
343
+ const { location, id } = parseConnectionName(connection.name);
344
+ const connectionRepos = await devConnect.listAllLinkableGitRepositories(projectId, location, id);
345
+ const cloneUris = connectionRepos.map((conn) => conn.cloneUri);
346
+ return cloneUris;
273
347
  }
274
- exports.fetchAllRepositories = fetchAllRepositories;
348
+ exports.fetchRepositoryCloneUris = fetchRepositoryCloneUris;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
- const path = require("path");
5
4
  const clc = require("colorette");
6
5
  const command_1 = require("../command");
7
6
  const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
@@ -15,12 +14,8 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
15
14
  const projectId = (0, projectUtils_1.needProjectId)(options);
16
15
  const services = (0, fileUtils_1.readFirebaseJson)(options.config);
17
16
  for (const service of services) {
18
- let configDir = service.source;
19
- if (!path.isAbsolute(configDir)) {
20
- const cwd = options.cwd || process.cwd();
21
- configDir = path.resolve(path.join(cwd), configDir);
22
- }
23
- const serviceInfo = await (0, load_1.load)(projectId, configDir);
17
+ const configDir = service.source;
18
+ const serviceInfo = await (0, load_1.load)(projectId, options.config, configDir);
24
19
  const hasGeneratables = serviceInfo.connectorInfo.some((c) => {
25
20
  var _a, _b, _c;
26
21
  return (((_a = c.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) ||
@@ -11,45 +11,42 @@ const requirePermissions_1 = require("../requirePermissions");
11
11
  const types_1 = require("../emulator/types");
12
12
  const commandUtils_1 = require("../emulator/commandUtils");
13
13
  const pretty_print_1 = require("../firestore/pretty-print");
14
+ const error_1 = require("../error");
14
15
  exports.command = new command_1.Command("firestore:backups:schedules:create")
15
16
  .description("Create a backup schedule under your Cloud Firestore database.")
16
- .option("-db, --database <databaseId>", "Database under which you want to create a schedule. Defaults to the (default) database")
17
- .option("-rt, --retention <duration>", "duration string (e.g. 12h or 30d) for backup retention")
18
- .option("-rc, --recurrence <recurrence>", "Recurrence settings; either DAILY or WEEKLY")
19
- .option("-dw, --day-of-week <dayOfWeek>", "On which day of the week to perform backups; one of MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, or SUNDAY")
17
+ .option("-d, --database <databaseId>", "Database under which you want to create a schedule. Defaults to the (default) database")
18
+ .option("--retention <duration>", "duration string (e.g. 12h or 30d) for backup retention")
19
+ .option("--recurrence <recurrence>", "Recurrence settings; either DAILY or WEEKLY")
20
+ .option("--day-of-week <dayOfWeek>", "On which day of the week to perform backups; one of MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, or SUNDAY")
20
21
  .before(requirePermissions_1.requirePermissions, ["datastore.backupSchedules.create"])
21
22
  .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
22
23
  .action(async (options) => {
23
24
  const printer = new pretty_print_1.PrettyPrint();
25
+ const helpCommandText = "See firebase firestore:backups:schedules:create --help for more info.";
24
26
  const databaseId = options.database || "(default)";
25
27
  if (!options.retention) {
26
- logger_1.logger.error("Missing required flag --retention. See firebase firestore:backups:schedules:create --help for more info");
27
- return;
28
+ throw new error_1.FirebaseError(`Missing required flag --retention. ${helpCommandText}`);
28
29
  }
29
30
  const retention = (0, backupUtils_1.calculateRetention)(options.retention);
30
31
  if (!options.recurrence) {
31
- logger_1.logger.error("Missing required flag --recurrence. See firebase firestore:backups:schedules:create --help for more info");
32
- return;
32
+ throw new error_1.FirebaseError(`Missing required flag --recurrence. ${helpCommandText}`);
33
33
  }
34
34
  const recurrenceType = options.recurrence;
35
35
  if (recurrenceType !== types.RecurrenceType.DAILY &&
36
36
  recurrenceType !== types.RecurrenceType.WEEKLY) {
37
- logger_1.logger.error("Invalid value for flag --recurrence. See firebase firestore:backups:schedules:create --help for more info");
38
- return;
37
+ throw new error_1.FirebaseError(`Invalid value for flag --recurrence. ${helpCommandText}`);
39
38
  }
40
39
  let dailyRecurrence;
41
40
  let weeklyRecurrence;
42
41
  if (options.recurrence === types.RecurrenceType.DAILY) {
43
42
  dailyRecurrence = {};
44
43
  if (options.dayOfWeek) {
45
- logger_1.logger.error("--day-of-week should not be provided if --recurrence is DAILY");
46
- return;
44
+ throw new error_1.FirebaseError(`--day-of-week should not be provided if --recurrence is DAILY. ${helpCommandText}`);
47
45
  }
48
46
  }
49
47
  else if (options.recurrence === types.RecurrenceType.WEEKLY) {
50
48
  if (!options.dayOfWeek) {
51
- logger_1.logger.error("If --recurrence is WEEKLY, --day-of-week must be provided. See firebase firestore:backups:schedules:create --help for more info");
52
- return;
49
+ throw new error_1.FirebaseError(`If --recurrence is WEEKLY, --day-of-week must be provided. ${helpCommandText}`);
53
50
  }
54
51
  const day = options.dayOfWeek;
55
52
  weeklyRecurrence = {
@@ -10,7 +10,7 @@ const firestore_1 = require("../gcp/firestore");
10
10
  const pretty_print_1 = require("../firestore/pretty-print");
11
11
  exports.command = new command_1.Command("firestore:backups:schedules:list")
12
12
  .description("List backup schedules under your Cloud Firestore database.")
13
- .option("-db, --database <databaseId>", "Database whose schedules you wish to list. Defaults to the (default) database.")
13
+ .option("-d, --database <databaseId>", "Database whose schedules you wish to list. Defaults to the (default) database.")
14
14
  .before(requirePermissions_1.requirePermissions, ["datastore.backupSchedules.list"])
15
15
  .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
16
16
  .action(async (options) => {
@@ -10,16 +10,17 @@ const requirePermissions_1 = require("../requirePermissions");
10
10
  const types_1 = require("../emulator/types");
11
11
  const commandUtils_1 = require("../emulator/commandUtils");
12
12
  const pretty_print_1 = require("../firestore/pretty-print");
13
+ const error_1 = require("../error");
13
14
  exports.command = new command_1.Command("firestore:backups:schedules:update <backupSchedule>")
14
15
  .description("Update a backup schedule under your Cloud Firestore database.")
15
- .option("-rt, --retention <duration>", "duration string (e.g. 12h or 30d) for backup retention")
16
+ .option("--retention <duration>", "duration string (e.g. 12h or 30d) for backup retention")
16
17
  .before(requirePermissions_1.requirePermissions, ["datastore.backupSchedules.update"])
17
18
  .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
18
19
  .action(async (backupScheduleName, options) => {
19
20
  const printer = new pretty_print_1.PrettyPrint();
21
+ const helpCommandText = "See firebase firestore:backups:schedules:update --help for more info.";
20
22
  if (!options.retention) {
21
- logger_1.logger.error("Missing required flag --retention. See firebase firestore:backups:schedules:update --help for more info");
22
- return;
23
+ throw new error_1.FirebaseError(`Missing required flag --retention. ${helpCommandText}`);
23
24
  }
24
25
  const retention = (0, backupUtils_1.calculateRetention)(options.retention);
25
26
  const backupSchedule = await (0, firestore_1.updateBackupSchedule)(backupScheduleName, retention);
@@ -10,6 +10,7 @@ const requirePermissions_1 = require("../requirePermissions");
10
10
  const types_1 = require("../emulator/types");
11
11
  const commandUtils_1 = require("../emulator/commandUtils");
12
12
  const pretty_print_1 = require("../firestore/pretty-print");
13
+ const error_1 = require("../error");
13
14
  exports.command = new command_1.Command("firestore:databases:create <database>")
14
15
  .description("Create a database in your Firebase project.")
15
16
  .option("--location <locationId>", "Region to create database, for example 'nam5'. Run 'firebase firestore:locations' to get a list of eligible locations. (required)")
@@ -20,16 +21,15 @@ exports.command = new command_1.Command("firestore:databases:create <database>")
20
21
  .action(async (database, options) => {
21
22
  const api = new fsi.FirestoreApi();
22
23
  const printer = new pretty_print_1.PrettyPrint();
24
+ const helpCommandText = "See firebase firestore:databases:create --help for more info.";
23
25
  if (!options.location) {
24
- logger_1.logger.error("Missing required flag --location. See firebase firestore:databases:create --help for more info.");
25
- return;
26
+ throw new error_1.FirebaseError(`Missing required flag --location. ${helpCommandText}`);
26
27
  }
27
28
  const type = types.DatabaseType.FIRESTORE_NATIVE;
28
29
  if (options.deleteProtection &&
29
30
  options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.ENABLED &&
30
31
  options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.DISABLED) {
31
- logger_1.logger.error("Invalid value for flag --delete-protection. See firebase firestore:databases:create --help for more info.");
32
- return;
32
+ throw new error_1.FirebaseError(`Invalid value for flag --delete-protection. ${helpCommandText}`);
33
33
  }
34
34
  const deleteProtectionState = options.deleteProtection === types.DatabaseDeleteProtectionStateOption.ENABLED
35
35
  ? types.DatabaseDeleteProtectionState.ENABLED
@@ -37,8 +37,7 @@ exports.command = new command_1.Command("firestore:databases:create <database>")
37
37
  if (options.pointInTimeRecovery &&
38
38
  options.pointInTimeRecovery !== types.PointInTimeRecoveryEnablementOption.ENABLED &&
39
39
  options.pointInTimeRecovery !== types.PointInTimeRecoveryEnablementOption.DISABLED) {
40
- logger_1.logger.error("Invalid value for flag --point-in-time-recovery. See firebase firestore:databases:create --help for more info.");
41
- return;
40
+ throw new error_1.FirebaseError(`Invalid value for flag --point-in-time-recovery. ${helpCommandText}`);
42
41
  }
43
42
  const pointInTimeRecoveryEnablement = options.pointInTimeRecovery === types.PointInTimeRecoveryEnablementOption.ENABLED
44
43
  ? types.PointInTimeRecoveryEnablement.ENABLED
@@ -9,6 +9,7 @@ const requirePermissions_1 = require("../requirePermissions");
9
9
  const types_1 = require("../emulator/types");
10
10
  const commandUtils_1 = require("../emulator/commandUtils");
11
11
  const pretty_print_1 = require("../firestore/pretty-print");
12
+ const error_1 = require("../error");
12
13
  exports.command = new command_1.Command("firestore:databases:restore")
13
14
  .description("Restore a Firestore database in your Firebase project.")
14
15
  .option("-d, --database <databaseID>", "ID of the database to restore into")
@@ -18,14 +19,13 @@ exports.command = new command_1.Command("firestore:databases:restore")
18
19
  .action(async (options) => {
19
20
  const api = new fsi.FirestoreApi();
20
21
  const printer = new pretty_print_1.PrettyPrint();
22
+ const helpCommandText = "See firebase firestore:databases:restore --help for more info.";
21
23
  if (!options.database) {
22
- logger_1.logger.error("Missing required flag --database. See firebase firestore:databases:restore --help for more info");
23
- return;
24
+ throw new error_1.FirebaseError(`Missing required flag --database. ${helpCommandText}`);
24
25
  }
25
26
  const databaseId = options.database;
26
27
  if (!options.backup) {
27
- logger_1.logger.error("Missing required flag --backup. See firebase firestore:databases:restore --help for more info");
28
- return;
28
+ throw new error_1.FirebaseError(`Missing required flag --backup. ${helpCommandText}`);
29
29
  }
30
30
  const backupName = options.backup;
31
31
  const databaseResp = await api.restoreDatabase(options.project, databaseId, backupName);
@@ -10,6 +10,7 @@ const requirePermissions_1 = require("../requirePermissions");
10
10
  const types_1 = require("../emulator/types");
11
11
  const commandUtils_1 = require("../emulator/commandUtils");
12
12
  const pretty_print_1 = require("../firestore/pretty-print");
13
+ const error_1 = require("../error");
13
14
  exports.command = new command_1.Command("firestore:databases:update <database>")
14
15
  .description("Update a database in your Firebase project. Must specify at least one property to update.")
15
16
  .option("--json", "Prints raw json response of the create API call if specified")
@@ -20,15 +21,14 @@ exports.command = new command_1.Command("firestore:databases:update <database>")
20
21
  .action(async (database, options) => {
21
22
  const api = new fsi.FirestoreApi();
22
23
  const printer = new pretty_print_1.PrettyPrint();
24
+ const helpCommandText = "See firebase firestore:databases:update --help for more info.";
23
25
  if (!options.deleteProtection && !options.pointInTimeRecovery) {
24
- logger_1.logger.error("Missing properties to update. See firebase firestore:databases:update --help for more info.");
25
- return;
26
+ throw new error_1.FirebaseError(`Missing properties to update. ${helpCommandText}`);
26
27
  }
27
28
  if (options.deleteProtection &&
28
29
  options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.ENABLED &&
29
30
  options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.DISABLED) {
30
- logger_1.logger.error("Invalid value for flag --delete-protection. See firebase firestore:databases:update --help for more info.");
31
- return;
31
+ throw new error_1.FirebaseError(`Invalid value for flag --delete-protection. ${helpCommandText}`);
32
32
  }
33
33
  let deleteProtectionState;
34
34
  if (options.deleteProtection === types.DatabaseDeleteProtectionStateOption.ENABLED) {
@@ -40,8 +40,7 @@ exports.command = new command_1.Command("firestore:databases:update <database>")
40
40
  if (options.pointInTimeRecovery &&
41
41
  options.pointInTimeRecovery !== types.PointInTimeRecoveryEnablementOption.ENABLED &&
42
42
  options.pointInTimeRecovery !== types.PointInTimeRecoveryEnablementOption.DISABLED) {
43
- logger_1.logger.error("Invalid value for flag --point-in-time-recovery. See firebase firestore:databases:update --help for more info.");
44
- return;
43
+ throw new error_1.FirebaseError(`Invalid value for flag --point-in-time-recovery. ${helpCommandText}`);
45
44
  }
46
45
  let pointInTimeRecoveryEnablement;
47
46
  if (options.pointInTimeRecovery === types.PointInTimeRecoveryEnablementOption.ENABLED) {
@@ -24,4 +24,6 @@ exports.command = new command_1.Command("help [command]")
24
24
  logger_1.logger.info(" To get help with a specific command, type", clc.bold("firebase help [command_name]"));
25
25
  logger_1.logger.info();
26
26
  }
27
+ logger_1.logger.info(" Privacy Policy: https://firebase.google.com/support/privacy");
28
+ logger_1.logger.info();
27
29
  });
@@ -44,7 +44,7 @@ const choices = [
44
44
  },
45
45
  {
46
46
  value: "hosting:github",
47
- name: "Hosting: Set up GitHub Action deploys",
47
+ name: " └── Hosting: Set up GitHub Action deploys",
48
48
  checked: false,
49
49
  },
50
50
  {
@@ -82,7 +82,7 @@ choices.push({
82
82
  });
83
83
  choices.push({
84
84
  value: "dataconnect:sdk",
85
- name: "Data Connect: Set up a generated SDK for your Firebase Data Connect service",
85
+ name: " └── Data Connect: Set up a generated SDK for your Firebase Data Connect service",
86
86
  checked: false,
87
87
  });
88
88
  const featureNames = choices.map((choice) => choice.value);
@@ -76,13 +76,13 @@ async function pickService(projectId, config, serviceId) {
76
76
  throw new error_1.FirebaseError("No Data Connect services found in firebase.json.");
77
77
  }
78
78
  else if (serviceCfgs.length === 1) {
79
- serviceInfo = await (0, load_1.load)(projectId, serviceCfgs[0].source);
79
+ serviceInfo = await (0, load_1.load)(projectId, config, serviceCfgs[0].source);
80
80
  }
81
81
  else {
82
82
  if (!serviceId) {
83
83
  throw new error_1.FirebaseError("Multiple Data Connect services found in firebase.json. Please specify a service ID to use.");
84
84
  }
85
- const infos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, c.source)));
85
+ const infos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, config, c.source)));
86
86
  const maybe = infos.find((i) => i.dataConnectYaml.serviceId === serviceId);
87
87
  if (!maybe) {
88
88
  throw new error_1.FirebaseError(`No service named ${serviceId} declared in firebase.json.`);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getResourceFilters = void 0;
3
+ exports.toString = exports.getResourceFilters = void 0;
4
4
  const error_1 = require("../error");
5
5
  function getResourceFilters(options) {
6
6
  if (!options.only) {
@@ -43,3 +43,14 @@ function parseSelector(selector) {
43
43
  }
44
44
  return filter;
45
45
  }
46
+ function toString(rf) {
47
+ const base = `dataconnect:${rf.serviceId}`;
48
+ if (rf.connectorId) {
49
+ return `${base}:${rf.connectorId}`;
50
+ }
51
+ if (rf.schemaOnly) {
52
+ return `${base}:schema`;
53
+ }
54
+ return base;
55
+ }
56
+ exports.toString = toString;
@@ -4,13 +4,14 @@ exports.load = void 0;
4
4
  const path = require("path");
5
5
  const fileUtils = require("./fileUtils");
6
6
  const types_1 = require("./types");
7
- async function load(projectId, sourceDirectory) {
8
- const dataConnectYaml = await fileUtils.readDataConnectYaml(sourceDirectory);
7
+ async function load(projectId, config, sourceDirectory) {
8
+ const resolvedDir = config.path(sourceDirectory);
9
+ const dataConnectYaml = await fileUtils.readDataConnectYaml(resolvedDir);
9
10
  const serviceName = `projects/${projectId}/locations/${dataConnectYaml.location}/services/${dataConnectYaml.serviceId}`;
10
- const schemaDir = path.join(sourceDirectory, dataConnectYaml.schema.source);
11
+ const schemaDir = path.join(resolvedDir, dataConnectYaml.schema.source);
11
12
  const schemaGQLs = await fileUtils.readGQLFiles(schemaDir);
12
13
  const connectorInfo = await Promise.all(dataConnectYaml.connectorDirs.map(async (dir) => {
13
- const connectorDir = path.join(sourceDirectory, dir);
14
+ const connectorDir = path.join(resolvedDir, dir);
14
15
  const connectorYaml = await fileUtils.readConnectorYaml(connectorDir);
15
16
  const connectorGqls = await fileUtils.readGQLFiles(connectorDir);
16
17
  return {
@@ -26,7 +27,7 @@ async function load(projectId, sourceDirectory) {
26
27
  }));
27
28
  return {
28
29
  serviceName,
29
- sourceDirectory,
30
+ sourceDirectory: resolvedDir,
30
31
  schema: {
31
32
  name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
32
33
  primaryDatasource: (0, types_1.toDatasource)(projectId, dataConnectYaml.location, dataConnectYaml.schema.datasource),
@@ -11,10 +11,12 @@ const logger_1 = require("../logger");
11
11
  const error_1 = require("../error");
12
12
  const projectUtils_1 = require("../projectUtils");
13
13
  const utils_1 = require("../utils");
14
+ const experiments = require("../experiments");
14
15
  const errors = require("./errors");
15
16
  async function diffSchema(schema) {
16
17
  const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
17
18
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
19
+ setCompatibleMode(schema, databaseId, instanceName);
18
20
  try {
19
21
  await (0, client_1.upsertSchema)(schema, true);
20
22
  (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
@@ -43,6 +45,7 @@ async function migrateSchema(args) {
43
45
  const { options, schema, validateOnly } = args;
44
46
  const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
45
47
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
48
+ setCompatibleMode(schema, databaseId, instanceName);
46
49
  try {
47
50
  await (0, client_1.upsertSchema)(schema, validateOnly);
48
51
  logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
@@ -79,6 +82,25 @@ async function migrateSchema(args) {
79
82
  return [];
80
83
  }
81
84
  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
+ };
101
+ }
102
+ }
103
+ }
82
104
  function getIdentifiers(schema) {
83
105
  var _a, _b;
84
106
  const databaseId = (_a = schema.primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.database;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const path = require("path");
3
+ const clc = require("colorette");
4
4
  const load_1 = require("../../dataconnect/load");
5
5
  const fileUtils_1 = require("../../dataconnect/fileUtils");
6
6
  const logger_1 = require("../../logger");
@@ -11,6 +11,7 @@ const build_1 = require("../../dataconnect/build");
11
11
  const ensureApis_1 = require("../../dataconnect/ensureApis");
12
12
  const requireTosAcceptance_1 = require("../../requireTosAcceptance");
13
13
  const firedata_1 = require("../../gcp/firedata");
14
+ const error_1 = require("../../error");
14
15
  async function default_1(context, options) {
15
16
  const projectId = (0, projectUtils_1.needProjectId)(options);
16
17
  await (0, ensureApis_1.ensureApis)(projectId);
@@ -18,10 +19,23 @@ async function default_1(context, options) {
18
19
  const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
19
20
  utils.logLabeledBullet("dataconnect", `Preparing to deploy`);
20
21
  const filters = (0, filters_1.getResourceFilters)(options);
21
- const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, path.join(options.cwd || process.cwd(), c.source))));
22
+ const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, options.config, c.source)));
22
23
  for (const si of serviceInfos) {
23
24
  si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory);
24
25
  }
26
+ const unmatchedFilters = filters === null || filters === void 0 ? void 0 : filters.filter((f) => {
27
+ const serviceMatched = serviceInfos.some((s) => s.dataConnectYaml.serviceId === f.serviceId);
28
+ const connectorMatched = f.connectorId
29
+ ? serviceInfos.some((s) => {
30
+ return (s.dataConnectYaml.serviceId === f.serviceId &&
31
+ s.connectorInfo.some((c) => c.connectorYaml.connectorId === f.connectorId));
32
+ })
33
+ : true;
34
+ return !serviceMatched || !connectorMatched;
35
+ });
36
+ if (unmatchedFilters === null || unmatchedFilters === void 0 ? void 0 : unmatchedFilters.length) {
37
+ throw new error_1.FirebaseError(`The following filters were specified in --only but didn't match anything in this project: ${unmatchedFilters.map(filters_1.toString).map(clc.bold).join(", ")}`);
38
+ }
25
39
  context.dataconnect = {
26
40
  serviceInfos,
27
41
  filters,