firebase-tools 13.13.3 → 13.14.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.
- package/lib/api.js +4 -2
- package/lib/apphosting/githubConnections.js +108 -34
- package/lib/commands/dataconnect-sdk-generate.js +2 -7
- package/lib/commands/firestore-backups-schedules-create.js +11 -14
- package/lib/commands/firestore-backups-schedules-list.js +1 -1
- package/lib/commands/firestore-backups-schedules-update.js +4 -3
- package/lib/commands/firestore-databases-create.js +5 -6
- package/lib/commands/firestore-databases-restore.js +4 -4
- package/lib/commands/firestore-databases-update.js +5 -6
- package/lib/commands/init.js +2 -2
- package/lib/dataconnect/fileUtils.js +2 -2
- package/lib/dataconnect/filters.js +12 -1
- package/lib/dataconnect/load.js +6 -5
- package/lib/dataconnect/schemaMigration.js +22 -0
- package/lib/deploy/dataconnect/prepare.js +16 -2
- package/lib/emulator/constants.js +1 -0
- package/lib/emulator/controller.js +2 -5
- package/lib/emulator/dataconnectEmulator.js +3 -2
- package/lib/emulator/downloadableEmulators.js +12 -12
- package/lib/emulator/eventarcEmulator.js +68 -18
- package/lib/emulator/functionsEmulator.js +79 -0
- package/lib/emulator/functionsEmulatorShared.js +4 -0
- package/lib/emulator/pubsubEmulator.js +1 -1
- package/lib/experiments.js +5 -0
- package/lib/frameworks/angular/utils.js +13 -5
- package/lib/functions/events/v2.js +2 -1
- package/lib/gcp/devConnect.js +13 -1
- package/lib/init/features/dataconnect/index.js +69 -19
- package/lib/init/features/dataconnect/sdk.js +2 -3
- package/lib/init/features/hosting/github.js +44 -21
- package/package.json +8 -8
- 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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 ===
|
|
67
|
-
|
|
78
|
+
if (repoCloneUri === MANAGE_INSTALLATION_CHOICE) {
|
|
79
|
+
await manageInstallation(connectionMatchingInstallation);
|
|
68
80
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 =
|
|
76
|
-
appInstallationId: (_b =
|
|
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,
|
|
83
|
-
var _a
|
|
92
|
+
async function createFullyInstalledConnection(projectId, location, connectionId, oauthConn, installationId) {
|
|
93
|
+
var _a;
|
|
84
94
|
let conn = await createConnection(projectId, location, connectionId, {
|
|
85
|
-
appInstallationId:
|
|
86
|
-
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,
|
|
132
|
-
const
|
|
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:
|
|
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
|
|
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
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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.
|
|
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
|
-
|
|
19
|
-
|
|
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("-
|
|
17
|
-
.option("
|
|
18
|
-
.option("
|
|
19
|
-
.option("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("-
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/lib/commands/init.js
CHANGED
|
@@ -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;
|
package/lib/dataconnect/load.js
CHANGED
|
@@ -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
|
|
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(
|
|
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(
|
|
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
|
|
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,
|
|
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,
|
|
@@ -106,6 +106,7 @@ Constants.SERVICE_FIRESTORE = "firestore.googleapis.com";
|
|
|
106
106
|
Constants.SERVICE_REALTIME_DATABASE = "firebaseio.com";
|
|
107
107
|
Constants.SERVICE_PUBSUB = "pubsub.googleapis.com";
|
|
108
108
|
Constants.SERVICE_EVENTARC = "eventarc.googleapis.com";
|
|
109
|
+
Constants.SERVICE_FIREALERTS = "firebasealerts.googleapis.com";
|
|
109
110
|
Constants.SERVICE_ANALYTICS = "app-measurement.com";
|
|
110
111
|
Constants.SERVICE_AUTH = "firebaseauth.googleapis.com";
|
|
111
112
|
Constants.SERVICE_CRASHLYTICS = "fabric.io";
|