@stoker-platform/cli 0.5.12
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/LICENSE.md +102 -0
- package/init-files/.##gitignore## +102 -0
- package/init-files/.devcontainer/devcontainer.json +14 -0
- package/init-files/.env/.env +70 -0
- package/init-files/.eslintrc.cjs +35 -0
- package/init-files/.firebaserc +6 -0
- package/init-files/.prettierignore +86 -0
- package/init-files/.prettierrc +5 -0
- package/init-files/bin/build.js +221 -0
- package/init-files/bin/shim.js +159 -0
- package/init-files/extensions/firestore-send-email.env +7 -0
- package/init-files/external.package.json +4 -0
- package/init-files/firebase-rules/database.rules.json +9 -0
- package/init-files/firebase-rules/firestore.custom.rules +19 -0
- package/init-files/firebase-rules/firestore.indexes.json +0 -0
- package/init-files/firebase-rules/firestore.rules +0 -0
- package/init-files/firebase-rules/storage.rules +0 -0
- package/init-files/firebase.hosting.json +122 -0
- package/init-files/firebase.json +52 -0
- package/init-files/functions/.##gitignore## +14 -0
- package/init-files/functions/.eslintrc.cjs +28 -0
- package/init-files/functions/package.json +46 -0
- package/init-files/functions/prompts/chat.prompt +17 -0
- package/init-files/functions/src/index.ts +457 -0
- package/init-files/functions/tsconfig.dev.json +3 -0
- package/init-files/functions/tsconfig.json +17 -0
- package/init-files/icons/logo-large.png +0 -0
- package/init-files/icons/logo-small.png +0 -0
- package/init-files/ops.js +25 -0
- package/init-files/package.json +53 -0
- package/init-files/project-data.json +5 -0
- package/init-files/remoteconfig.template.json +1 -0
- package/init-files/src/collections/Inbox.ts +444 -0
- package/init-files/src/collections/Outbox.ts +270 -0
- package/init-files/src/collections/Settings.ts +44 -0
- package/init-files/src/collections/Users.ts +138 -0
- package/init-files/src/main.ts +245 -0
- package/init-files/src/utils.ts +3 -0
- package/init-files/src/vite-env.d.ts +1 -0
- package/init-files/test/test.ts +5 -0
- package/init-files/tsconfig.json +23 -0
- package/init-files/vitest.config.ts +9 -0
- package/lib/package.json +45 -0
- package/lib/src/data/exportToBigQuery.js +41 -0
- package/lib/src/data/seedData.js +347 -0
- package/lib/src/deploy/applySchema.js +43 -0
- package/lib/src/deploy/cloud-functions/getFunctionsData.js +18 -0
- package/lib/src/deploy/deployProject.js +116 -0
- package/lib/src/deploy/firestore-export/exportFirestoreData.js +29 -0
- package/lib/src/deploy/firestore-ttl/deployTTLs.js +127 -0
- package/lib/src/deploy/live-update/liveUpdate.js +22 -0
- package/lib/src/deploy/maintenance/activateMaintenanceMode.js +9 -0
- package/lib/src/deploy/maintenance/disableMaintenanceMode.js +9 -0
- package/lib/src/deploy/maintenance/setDeploymentStatus.js +22 -0
- package/lib/src/deploy/rules-indexes/generateFirestoreIndexes.js +23 -0
- package/lib/src/deploy/rules-indexes/generateFirestoreRules.js +35 -0
- package/lib/src/deploy/rules-indexes/generateStorageRules.js +23 -0
- package/lib/src/deploy/schema/generateSchema.js +184 -0
- package/lib/src/deploy/schema/persistSchema.js +14 -0
- package/lib/src/lint/lintSchema.js +1491 -0
- package/lib/src/lint/securityReport.js +223 -0
- package/lib/src/main.js +460 -0
- package/lib/src/migration/firestore/migrateFirestore.js +8 -0
- package/lib/src/migration/firestore/operations/deleteField.js +58 -0
- package/lib/src/migration/migrateAll.js +30 -0
- package/lib/src/ops/auditDenormalized.js +124 -0
- package/lib/src/ops/auditPermissions.js +92 -0
- package/lib/src/ops/auditRelations.js +186 -0
- package/lib/src/ops/explainPreloadQueries.js +65 -0
- package/lib/src/ops/getUser.js +10 -0
- package/lib/src/ops/getUserPermissions.js +19 -0
- package/lib/src/ops/getUserRecord.js +20 -0
- package/lib/src/ops/listProjects.js +8 -0
- package/lib/src/ops/setUserCollection.js +14 -0
- package/lib/src/ops/setUserDocument.js +11 -0
- package/lib/src/ops/setUserRole.js +14 -0
- package/lib/src/project/addProject.js +935 -0
- package/lib/src/project/addRecord.js +9 -0
- package/lib/src/project/addRecordPrompt.js +205 -0
- package/lib/src/project/addTenant.js +59 -0
- package/lib/src/project/buildWebApp.js +10 -0
- package/lib/src/project/customDomain.js +157 -0
- package/lib/src/project/deleteProject.js +51 -0
- package/lib/src/project/deleteRecord.js +11 -0
- package/lib/src/project/deleteTenant.js +49 -0
- package/lib/src/project/getOne.js +25 -0
- package/lib/src/project/getSome.js +28 -0
- package/lib/src/project/initProject.js +16 -0
- package/lib/src/project/prepareEmulatorData.js +125 -0
- package/lib/src/project/setProject.js +13 -0
- package/lib/src/project/startEmulators.js +30 -0
- package/lib/src/project/updateRecord.js +9 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
import { runChildProcess } from "@stoker-platform/node-client";
|
|
2
|
+
import { writeFile, unlink, readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import { retryOperation } from "@stoker-platform/utils";
|
|
6
|
+
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
|
|
7
|
+
import { addTenant } from "./addTenant.js";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export const addProject = async (options) => {
|
|
11
|
+
if (!process.env.GCP_BILLING_ACCOUNT) {
|
|
12
|
+
throw new Error("GCP_BILLING_ACCOUNT not set.");
|
|
13
|
+
}
|
|
14
|
+
if (!process.env.FB_FIRESTORE_REGION) {
|
|
15
|
+
throw new Error("FB_FIRESTORE_REGION not set.");
|
|
16
|
+
}
|
|
17
|
+
if (!process.env.FB_STORAGE_REGION) {
|
|
18
|
+
throw new Error("FB_STORAGE_REGION not set.");
|
|
19
|
+
}
|
|
20
|
+
if (!process.env.FB_DATABASE_REGION) {
|
|
21
|
+
throw new Error("FB_DATABASE_REGION not set.");
|
|
22
|
+
}
|
|
23
|
+
if (!process.env.MAIL_SMTP_PASSWORD) {
|
|
24
|
+
throw new Error("MAIL_SMTP_PASSWORD not set.");
|
|
25
|
+
}
|
|
26
|
+
if (!process.env.FB_AUTH_PASSWORD_POLICY) {
|
|
27
|
+
throw new Error("FB_AUTH_PASSWORD_POLICY not set.");
|
|
28
|
+
}
|
|
29
|
+
if (!process.env.FB_AUTH_PASSWORD_POLICY_UPGRADE) {
|
|
30
|
+
throw new Error("FB_AUTH_PASSWORD_POLICY_UPGRADE not set.");
|
|
31
|
+
}
|
|
32
|
+
if (!process.env.FB_FUNCTIONS_REGION) {
|
|
33
|
+
throw new Error("FB_FUNCTIONS_REGION not set.");
|
|
34
|
+
}
|
|
35
|
+
if (process.env.GCP_PROJECT) {
|
|
36
|
+
throw new Error("GCP_PROJECT should not be set for project creation.");
|
|
37
|
+
}
|
|
38
|
+
const projectEnvFile = join(process.cwd(), ".env", `.env.project.${options.name}`);
|
|
39
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
40
|
+
if (existsSync(projectEnvFile)) {
|
|
41
|
+
dotenv.config({ path: projectEnvFile, override: true, quiet: true });
|
|
42
|
+
}
|
|
43
|
+
if (options.development) {
|
|
44
|
+
dotenv.config({ path: join(process.cwd(), ".env", ".env.dev"), override: true, quiet: true });
|
|
45
|
+
}
|
|
46
|
+
const projectId = options.name;
|
|
47
|
+
process.env.GCP_PROJECT = projectId;
|
|
48
|
+
let projectData = JSON.parse(await readFile(join(process.cwd(), "project-data.json"), "utf8"));
|
|
49
|
+
if (Array.isArray(projectData.projects)) {
|
|
50
|
+
const currentProjects = {};
|
|
51
|
+
for (const project of projectData.projects) {
|
|
52
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
53
|
+
currentProjects[project] = {
|
|
54
|
+
setup_progress: 1000,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
projectData.projects = currentProjects;
|
|
58
|
+
}
|
|
59
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
60
|
+
if (!projectData.projects[projectId]) {
|
|
61
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
62
|
+
projectData.projects[projectId] = {
|
|
63
|
+
setup_progress: 0,
|
|
64
|
+
setup_date: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
if (!options.testMode) {
|
|
67
|
+
await writeFile(join(process.cwd(), "project-data.json"), JSON.stringify(projectData, null, 4));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
72
|
+
const currentProjectData = await readFile(join(process.cwd(), "project-data.json"), "utf8");
|
|
73
|
+
projectData = JSON.parse(currentProjectData);
|
|
74
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
75
|
+
if (projectData.projects[projectId].setup_progress === 1000) {
|
|
76
|
+
console.log("Project already set up.");
|
|
77
|
+
process.exit();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const projectDataObject = projectData;
|
|
81
|
+
const updateProjectData = async (progress) => {
|
|
82
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
83
|
+
projectDataObject.projects[projectId].setup_progress = progress;
|
|
84
|
+
await writeFile(join(process.cwd(), "project-data.json"), JSON.stringify(projectData, null, 4));
|
|
85
|
+
};
|
|
86
|
+
const getProgress = () => {
|
|
87
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
88
|
+
return projectDataObject.projects[projectId].setup_progress;
|
|
89
|
+
};
|
|
90
|
+
const projectArgs = ["projects:create", projectId];
|
|
91
|
+
if (process.env.GCP_ORGANIZATION)
|
|
92
|
+
projectArgs.push("--organization", process.env.GCP_ORGANIZATION);
|
|
93
|
+
if (process.env.GCP_FOLDER)
|
|
94
|
+
projectArgs.push("--folder", process.env.GCP_FOLDER);
|
|
95
|
+
if (getProgress() < 1) {
|
|
96
|
+
await runChildProcess("firebase", projectArgs).catch(async () => {
|
|
97
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
98
|
+
delete projectDataObject.projects[projectId];
|
|
99
|
+
await writeFile(join(process.cwd(), "project-data.json"), JSON.stringify(projectDataObject, null, 4));
|
|
100
|
+
throw new Error("Error creating Google Cloud Project.");
|
|
101
|
+
});
|
|
102
|
+
await updateProjectData(1);
|
|
103
|
+
}
|
|
104
|
+
if (getProgress() < 2) {
|
|
105
|
+
await runChildProcess("gcloud", [
|
|
106
|
+
"billing",
|
|
107
|
+
"projects",
|
|
108
|
+
"link",
|
|
109
|
+
projectId,
|
|
110
|
+
`--billing-account=${process.env.GCP_BILLING_ACCOUNT}`,
|
|
111
|
+
"--quiet",
|
|
112
|
+
]).catch(() => {
|
|
113
|
+
throw new Error("Error enabling billing on project.");
|
|
114
|
+
});
|
|
115
|
+
await updateProjectData(2);
|
|
116
|
+
}
|
|
117
|
+
if (getProgress() < 3) {
|
|
118
|
+
await runChildProcess("gcloud", [
|
|
119
|
+
"services",
|
|
120
|
+
"enable",
|
|
121
|
+
"firestore.googleapis.com",
|
|
122
|
+
"firebasedatabase.googleapis.com",
|
|
123
|
+
"firebasestorage.googleapis.com",
|
|
124
|
+
"secretmanager.googleapis.com",
|
|
125
|
+
"cloudfunctions.googleapis.com",
|
|
126
|
+
"deploymentmanager.googleapis.com",
|
|
127
|
+
"artifactregistry.googleapis.com",
|
|
128
|
+
"containerregistry.googleapis.com",
|
|
129
|
+
"cloudbuild.googleapis.com",
|
|
130
|
+
"firebaseextensions.googleapis.com",
|
|
131
|
+
"eventarc.googleapis.com",
|
|
132
|
+
"eventarcpublishing.googleapis.com",
|
|
133
|
+
"run.googleapis.com",
|
|
134
|
+
"compute.googleapis.com",
|
|
135
|
+
"maps-backend.googleapis.com",
|
|
136
|
+
"geocoding-backend.googleapis.com",
|
|
137
|
+
"firebaseappcheck.googleapis.com",
|
|
138
|
+
"recaptchaenterprise.googleapis.com",
|
|
139
|
+
"cloudscheduler.googleapis.com",
|
|
140
|
+
`--project=${projectId}`,
|
|
141
|
+
"--quiet",
|
|
142
|
+
]).catch(() => {
|
|
143
|
+
throw new Error("Error enabling Google Cloud APIs.");
|
|
144
|
+
});
|
|
145
|
+
await updateProjectData(3);
|
|
146
|
+
}
|
|
147
|
+
if (getProgress() < 4) {
|
|
148
|
+
await runChildProcess("gcloud", [
|
|
149
|
+
"services",
|
|
150
|
+
"enable",
|
|
151
|
+
"aiplatform.googleapis.com",
|
|
152
|
+
"monitoring.googleapis.com",
|
|
153
|
+
"logging.googleapis.com",
|
|
154
|
+
"cloudtrace.googleapis.com",
|
|
155
|
+
`--project=${projectId}`,
|
|
156
|
+
"--quiet",
|
|
157
|
+
]).catch(() => {
|
|
158
|
+
throw new Error("Error enabling Google Cloud APIs.");
|
|
159
|
+
});
|
|
160
|
+
await updateProjectData(4);
|
|
161
|
+
}
|
|
162
|
+
if (getProgress() < 5) {
|
|
163
|
+
await runChildProcess("firebase", ["apps:create", "WEB", projectId, "--project", projectId]).catch(() => {
|
|
164
|
+
throw new Error("Error creating Firebase web app.");
|
|
165
|
+
});
|
|
166
|
+
await updateProjectData(5);
|
|
167
|
+
}
|
|
168
|
+
const webAppResult = await runChildProcess("firebase", [
|
|
169
|
+
"apps:sdkconfig",
|
|
170
|
+
"WEB",
|
|
171
|
+
"--project",
|
|
172
|
+
projectId,
|
|
173
|
+
"--json",
|
|
174
|
+
]).catch(() => {
|
|
175
|
+
throw new Error("Error getting Firebase web app config.");
|
|
176
|
+
});
|
|
177
|
+
const webAppResultJson = JSON.parse(webAppResult);
|
|
178
|
+
const webAppConfig = webAppResultJson.result.sdkConfig;
|
|
179
|
+
const appId = webAppConfig.appId;
|
|
180
|
+
const projectNumber = webAppConfig.messagingSenderId;
|
|
181
|
+
const token = await runChildProcess("gcloud", ["auth", "print-access-token"]).catch(() => {
|
|
182
|
+
throw new Error("Error getting Google Cloud identity token.");
|
|
183
|
+
});
|
|
184
|
+
if (getProgress() < 6) {
|
|
185
|
+
if (process.env.FB_GOOGLE_ANALYTICS_ACCOUNT_ID && !options.development) {
|
|
186
|
+
const analyticsResponse = await fetch(`https://firebase.googleapis.com/v1beta1/projects/${projectId}:addGoogleAnalytics`, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: {
|
|
189
|
+
Authorization: `Bearer ${token}`,
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
"X-Goog-User-Project": projectId,
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
analyticsAccountId: process.env.FB_GOOGLE_ANALYTICS_ACCOUNT_ID,
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
197
|
+
const analyticsResponseJson = await analyticsResponse.json();
|
|
198
|
+
console.log(analyticsResponseJson);
|
|
199
|
+
if (!analyticsResponse.ok) {
|
|
200
|
+
throw new Error("Error adding Google Analytics to Firebase project.");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
await updateProjectData(6);
|
|
204
|
+
}
|
|
205
|
+
if (getProgress() < 7) {
|
|
206
|
+
const hostingResponse = await fetch(`https://firebasehosting.googleapis.com/v1beta1/projects/${projectId}/sites/${projectId}?updateMask=appId`, {
|
|
207
|
+
method: "PATCH",
|
|
208
|
+
headers: {
|
|
209
|
+
Authorization: `Bearer ${token}`,
|
|
210
|
+
"Content-Type": "application/json",
|
|
211
|
+
"X-Goog-User-Project": projectId,
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
appId,
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
const hostingResponseJson = await hostingResponse.json();
|
|
218
|
+
console.log(hostingResponseJson);
|
|
219
|
+
if (!hostingResponse.ok) {
|
|
220
|
+
throw new Error("Error updating Firebase Hosting site web app association.");
|
|
221
|
+
}
|
|
222
|
+
await updateProjectData(7);
|
|
223
|
+
}
|
|
224
|
+
if (getProgress() < 8) {
|
|
225
|
+
const hostingConfig = {};
|
|
226
|
+
if (process.env.FB_HOSTING_ENABLE_CLOUD_LOGGING === "true") {
|
|
227
|
+
hostingConfig.cloudLoggingEnabled = true;
|
|
228
|
+
}
|
|
229
|
+
if (process.env.FB_HOSTING_MAX_VERSIONS) {
|
|
230
|
+
hostingConfig.maxVersions = process.env.FB_HOSTING_MAX_VERSIONS;
|
|
231
|
+
}
|
|
232
|
+
if (Object.keys(hostingConfig).length > 0) {
|
|
233
|
+
const hostingConfigResponse = await fetch(`https://firebasehosting.googleapis.com/v1beta1/projects/${projectId}/sites/${projectId}/config?updateMask=cloudLoggingEnabled,maxVersions`, {
|
|
234
|
+
method: "PATCH",
|
|
235
|
+
headers: {
|
|
236
|
+
Authorization: `Bearer ${token}`,
|
|
237
|
+
"Content-Type": "application/json",
|
|
238
|
+
"X-Goog-User-Project": projectId,
|
|
239
|
+
},
|
|
240
|
+
body: JSON.stringify(hostingConfig),
|
|
241
|
+
});
|
|
242
|
+
const hostingConfigResponseJson = await hostingConfigResponse.json();
|
|
243
|
+
console.log(hostingConfigResponseJson);
|
|
244
|
+
if (!hostingConfigResponse.ok) {
|
|
245
|
+
throw new Error("Error updating Firebase Hosting site web app config.");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await updateProjectData(8);
|
|
249
|
+
}
|
|
250
|
+
if (getProgress() < 9) {
|
|
251
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
252
|
+
await retryOperation(async () => {
|
|
253
|
+
const rtdb = await fetch(`https://firebasedatabase.googleapis.com/v1beta/projects/${projectId}/locations/${process.env.FB_DATABASE_REGION}/instances?key=${webAppConfig.apiKey}`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: {
|
|
256
|
+
Authorization: `Bearer ${token}`,
|
|
257
|
+
Accept: "application/json",
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
"X-Goog-User-Project": projectId,
|
|
260
|
+
},
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
type: "DEFAULT_DATABASE",
|
|
263
|
+
}),
|
|
264
|
+
}).catch(() => {
|
|
265
|
+
throw new Error("Error creating Firebase Realtime Database instance.");
|
|
266
|
+
});
|
|
267
|
+
const rtdbResponse = await rtdb.json();
|
|
268
|
+
console.log(rtdbResponse);
|
|
269
|
+
if (!rtdb.ok) {
|
|
270
|
+
throw new Error("Error creating Firebase Realtime Database instance.");
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
webAppConfig.databaseURL = rtdbResponse.databaseUrl;
|
|
274
|
+
}
|
|
275
|
+
}, [], undefined, 5000);
|
|
276
|
+
await updateProjectData(9);
|
|
277
|
+
}
|
|
278
|
+
if (getProgress() < 10) {
|
|
279
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
280
|
+
await retryOperation(async () => {
|
|
281
|
+
await runChildProcess("gcloud", [
|
|
282
|
+
"firestore",
|
|
283
|
+
"databases",
|
|
284
|
+
"create",
|
|
285
|
+
`--location=${process.env.FB_FIRESTORE_REGION}`,
|
|
286
|
+
"--type=firestore-native",
|
|
287
|
+
"--delete-protection",
|
|
288
|
+
options.pitr && process.env.FB_FIRESTORE_ENABLE_PITR !== "false"
|
|
289
|
+
? "--enable-pitr"
|
|
290
|
+
: "--no-enable-pitr",
|
|
291
|
+
`--project=${projectId}`,
|
|
292
|
+
"--quiet",
|
|
293
|
+
]).catch(() => {
|
|
294
|
+
throw new Error("Error creating Firestore database.");
|
|
295
|
+
});
|
|
296
|
+
}, [], undefined, 5000);
|
|
297
|
+
await updateProjectData(10);
|
|
298
|
+
}
|
|
299
|
+
if (getProgress() < 11) {
|
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
301
|
+
const backupArguments = [
|
|
302
|
+
"firestore",
|
|
303
|
+
"backups",
|
|
304
|
+
"schedules",
|
|
305
|
+
"create",
|
|
306
|
+
options.backupRecurrence
|
|
307
|
+
? `--recurrence=${options.backupRecurrence}`
|
|
308
|
+
: process.env.FB_FIRESTORE_BACKUP_RECURRENCE
|
|
309
|
+
? `--recurrence=${process.env.FB_FIRESTORE_BACKUP_RECURRENCE}`
|
|
310
|
+
: "--recurrence=daily",
|
|
311
|
+
options.backupRetention
|
|
312
|
+
? `--retention=${options.backupRetention}`
|
|
313
|
+
: process.env.FB_FIRESTORE_BACKUP_RETENTION
|
|
314
|
+
? `--retention=${process.env.FB_FIRESTORE_BACKUP_RETENTION}`
|
|
315
|
+
: "--retention=7d",
|
|
316
|
+
"--database=(default)",
|
|
317
|
+
`--project=${projectId}`,
|
|
318
|
+
"--quiet",
|
|
319
|
+
];
|
|
320
|
+
if (process.env.FB_FIRESTORE_BACKUP_RECURRENCE === "weekly" || options.backupRecurrence === "weekly")
|
|
321
|
+
backupArguments.push("--day-of-week=SUN");
|
|
322
|
+
await retryOperation(async () => {
|
|
323
|
+
await runChildProcess("gcloud", backupArguments).catch(() => {
|
|
324
|
+
throw new Error("Error creating Firestore database.");
|
|
325
|
+
});
|
|
326
|
+
}, [], undefined, 5000);
|
|
327
|
+
await updateProjectData(11);
|
|
328
|
+
}
|
|
329
|
+
if (getProgress() < 12) {
|
|
330
|
+
await runChildProcess("gcloud", [
|
|
331
|
+
"storage",
|
|
332
|
+
"buckets",
|
|
333
|
+
"create",
|
|
334
|
+
`gs://${projectId}`,
|
|
335
|
+
"--default-storage-class=standard",
|
|
336
|
+
`--location=${process.env.FB_STORAGE_REGION}`,
|
|
337
|
+
`--project=${projectId}`,
|
|
338
|
+
"--public-access-prevention",
|
|
339
|
+
options.softDelete
|
|
340
|
+
? `--soft-delete-duration=${options.softDelete}`
|
|
341
|
+
: process.env.FB_STORAGE_SOFT_DELETE_DURATION
|
|
342
|
+
? `--soft-delete-duration=${process.env.FB_STORAGE_SOFT_DELETE_DURATION}`
|
|
343
|
+
: "--soft-delete-duration=30d",
|
|
344
|
+
"--quiet",
|
|
345
|
+
]).catch(() => {
|
|
346
|
+
throw new Error("Error creating Cloud Storage Bucket.");
|
|
347
|
+
});
|
|
348
|
+
await updateProjectData(12);
|
|
349
|
+
}
|
|
350
|
+
if (getProgress() < 13) {
|
|
351
|
+
if (options.versioning && process.env.FB_STORAGE_ENABLE_VERSIONING !== "false") {
|
|
352
|
+
await runChildProcess("gcloud", [
|
|
353
|
+
"storage",
|
|
354
|
+
"buckets",
|
|
355
|
+
"update",
|
|
356
|
+
`gs://${projectId}`,
|
|
357
|
+
"--versioning",
|
|
358
|
+
`--project=${projectId}`,
|
|
359
|
+
"--quiet",
|
|
360
|
+
]).catch(() => {
|
|
361
|
+
throw new Error("Error enabling Cloud Storage versioning.");
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
await updateProjectData(13);
|
|
365
|
+
}
|
|
366
|
+
if (getProgress() < 14) {
|
|
367
|
+
await writeFile("cors.json", process.env.FB_STORAGE_CORS
|
|
368
|
+
? process.env.FB_STORAGE_CORS
|
|
369
|
+
: JSON.stringify([
|
|
370
|
+
{
|
|
371
|
+
origin: [`https://${projectId}.web.app`, `https://${projectId}.firebaseapp.com`],
|
|
372
|
+
method: ["GET"],
|
|
373
|
+
maxAgeSeconds: 3600,
|
|
374
|
+
},
|
|
375
|
+
]));
|
|
376
|
+
await runChildProcess("gcloud", [
|
|
377
|
+
"storage",
|
|
378
|
+
"buckets",
|
|
379
|
+
"update",
|
|
380
|
+
`gs://${projectId}`,
|
|
381
|
+
"--cors-file",
|
|
382
|
+
options.customCors || "cors.json",
|
|
383
|
+
`--project=${projectId}`,
|
|
384
|
+
"--quiet",
|
|
385
|
+
]).catch(() => {
|
|
386
|
+
throw new Error("Error enabling Cloud Storage CORS.");
|
|
387
|
+
});
|
|
388
|
+
await unlink(join(process.cwd(), "cors.json"));
|
|
389
|
+
await updateProjectData(14);
|
|
390
|
+
}
|
|
391
|
+
if (getProgress() < 15) {
|
|
392
|
+
await runChildProcess("gcloud", [
|
|
393
|
+
"storage",
|
|
394
|
+
"buckets",
|
|
395
|
+
"create",
|
|
396
|
+
`gs://${projectId}-export`,
|
|
397
|
+
"--default-storage-class=standard",
|
|
398
|
+
`--location=${process.env.FB_STORAGE_REGION}`,
|
|
399
|
+
`--project=${projectId}`,
|
|
400
|
+
"--public-access-prevention",
|
|
401
|
+
options.softDelete
|
|
402
|
+
? `--soft-delete-duration=${options.softDelete}`
|
|
403
|
+
: process.env.FB_STORAGE_SOFT_DELETE_DURATION
|
|
404
|
+
? `--soft-delete-duration=${process.env.FB_STORAGE_SOFT_DELETE_DURATION}`
|
|
405
|
+
: "--soft-delete-duration=30d",
|
|
406
|
+
"--quiet",
|
|
407
|
+
]).catch(() => {
|
|
408
|
+
throw new Error("Error creating Cloud Storage export Bucket.");
|
|
409
|
+
});
|
|
410
|
+
await updateProjectData(15);
|
|
411
|
+
}
|
|
412
|
+
if (getProgress() < 16) {
|
|
413
|
+
const storageResponse = await fetch(`https://firebasestorage.googleapis.com/v1beta/projects/${projectId}/buckets/${projectId}:addFirebase`, {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: {
|
|
416
|
+
Authorization: `Bearer ${token}`,
|
|
417
|
+
"Content-Type": "application/json",
|
|
418
|
+
"X-Goog-User-Project": projectId,
|
|
419
|
+
},
|
|
420
|
+
body: "{}",
|
|
421
|
+
});
|
|
422
|
+
const storageResponseJson = await storageResponse.json();
|
|
423
|
+
console.log(storageResponseJson);
|
|
424
|
+
if (!storageResponse.ok) {
|
|
425
|
+
throw new Error("Error adding Firebase to Cloud Storage.");
|
|
426
|
+
}
|
|
427
|
+
await updateProjectData(16);
|
|
428
|
+
}
|
|
429
|
+
if (getProgress() < 17) {
|
|
430
|
+
if (process.platform === "win32") {
|
|
431
|
+
const firebasercPath = join(process.cwd(), ".firebaserc");
|
|
432
|
+
await runChildProcess("attrib", ["-H", firebasercPath]);
|
|
433
|
+
}
|
|
434
|
+
await runChildProcess("firebase", ["target:apply", "storage", "default", projectId, "--project", projectId]);
|
|
435
|
+
await updateProjectData(17);
|
|
436
|
+
}
|
|
437
|
+
if (getProgress() < 18) {
|
|
438
|
+
await retryOperation(async () => {
|
|
439
|
+
await runChildProcess("gcloud", [
|
|
440
|
+
"projects",
|
|
441
|
+
"add-iam-policy-binding",
|
|
442
|
+
projectId,
|
|
443
|
+
"--member",
|
|
444
|
+
`serviceAccount:service-${projectNumber}@gcp-sa-firebasestorage.iam.gserviceaccount.com`,
|
|
445
|
+
"--role",
|
|
446
|
+
"roles/firebaserules.firestoreServiceAgent",
|
|
447
|
+
"--quiet",
|
|
448
|
+
]).catch(() => {
|
|
449
|
+
throw new Error("Error attaching Firebase Storage Rules permissions.");
|
|
450
|
+
});
|
|
451
|
+
}, [], undefined, 5000);
|
|
452
|
+
await updateProjectData(18);
|
|
453
|
+
}
|
|
454
|
+
if (getProgress() < 19) {
|
|
455
|
+
await retryOperation(async () => {
|
|
456
|
+
await runChildProcess("gcloud", [
|
|
457
|
+
"projects",
|
|
458
|
+
"add-iam-policy-binding",
|
|
459
|
+
projectId,
|
|
460
|
+
"--member",
|
|
461
|
+
`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`,
|
|
462
|
+
"--role",
|
|
463
|
+
"roles/monitoring.metricWriter",
|
|
464
|
+
"--quiet",
|
|
465
|
+
]).catch(() => {
|
|
466
|
+
throw new Error("Error attaching GenKit permissions.");
|
|
467
|
+
});
|
|
468
|
+
}, [], undefined, 5000);
|
|
469
|
+
await updateProjectData(19);
|
|
470
|
+
}
|
|
471
|
+
if (getProgress() < 20) {
|
|
472
|
+
await retryOperation(async () => {
|
|
473
|
+
await runChildProcess("gcloud", [
|
|
474
|
+
"projects",
|
|
475
|
+
"add-iam-policy-binding",
|
|
476
|
+
projectId,
|
|
477
|
+
"--member",
|
|
478
|
+
`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`,
|
|
479
|
+
"--role",
|
|
480
|
+
"roles/cloudtrace.agent",
|
|
481
|
+
"--quiet",
|
|
482
|
+
]).catch(() => {
|
|
483
|
+
throw new Error("Error attaching GenKit permissions.");
|
|
484
|
+
});
|
|
485
|
+
}, [], undefined, 5000);
|
|
486
|
+
await updateProjectData(20);
|
|
487
|
+
}
|
|
488
|
+
if (getProgress() < 21) {
|
|
489
|
+
await retryOperation(async () => {
|
|
490
|
+
await runChildProcess("gcloud", [
|
|
491
|
+
"projects",
|
|
492
|
+
"add-iam-policy-binding",
|
|
493
|
+
projectId,
|
|
494
|
+
"--member",
|
|
495
|
+
`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`,
|
|
496
|
+
"--role",
|
|
497
|
+
"roles/logging.logWriter",
|
|
498
|
+
"--quiet",
|
|
499
|
+
]).catch(() => {
|
|
500
|
+
throw new Error("Error attaching GenKit permissions.");
|
|
501
|
+
});
|
|
502
|
+
}, [], undefined, 5000);
|
|
503
|
+
await updateProjectData(21);
|
|
504
|
+
}
|
|
505
|
+
if (getProgress() < 22) {
|
|
506
|
+
await retryOperation(async () => {
|
|
507
|
+
await runChildProcess("gcloud", [
|
|
508
|
+
"projects",
|
|
509
|
+
"add-iam-policy-binding",
|
|
510
|
+
projectId,
|
|
511
|
+
"--member",
|
|
512
|
+
`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`,
|
|
513
|
+
"--role",
|
|
514
|
+
"roles/iam.serviceAccountTokenCreator",
|
|
515
|
+
"--quiet",
|
|
516
|
+
]).catch(() => {
|
|
517
|
+
throw new Error("Error attaching GenKit permissions.");
|
|
518
|
+
});
|
|
519
|
+
}, [], undefined, 5000);
|
|
520
|
+
await updateProjectData(22);
|
|
521
|
+
}
|
|
522
|
+
webAppConfig.storageBucket = `gs://${projectId}`;
|
|
523
|
+
if (getProgress() < 23) {
|
|
524
|
+
const identity = await fetch(`https://identitytoolkit.googleapis.com/v2/projects/${projectId}/identityPlatform:initializeAuth`, {
|
|
525
|
+
method: "POST",
|
|
526
|
+
headers: {
|
|
527
|
+
Authorization: `Bearer ${token}`,
|
|
528
|
+
Accept: "application/json",
|
|
529
|
+
"Content-Type": "application/json",
|
|
530
|
+
"X-Goog-User-Project": projectId,
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
const identityResponse = await identity.json();
|
|
534
|
+
console.log(identityResponse);
|
|
535
|
+
if (!identity.ok) {
|
|
536
|
+
throw new Error("Error setting up Firebase Auth.");
|
|
537
|
+
}
|
|
538
|
+
await updateProjectData(23);
|
|
539
|
+
}
|
|
540
|
+
if (getProgress() < 24) {
|
|
541
|
+
const auth = await fetch(`https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config`, {
|
|
542
|
+
method: "PATCH",
|
|
543
|
+
headers: {
|
|
544
|
+
Authorization: `Bearer ${token}`,
|
|
545
|
+
Accept: "application/json",
|
|
546
|
+
"Content-Type": "application/json",
|
|
547
|
+
"X-Goog-User-Project": projectId,
|
|
548
|
+
},
|
|
549
|
+
body: JSON.stringify({
|
|
550
|
+
signIn: {
|
|
551
|
+
email: {
|
|
552
|
+
enabled: true,
|
|
553
|
+
passwordRequired: true,
|
|
554
|
+
},
|
|
555
|
+
allowDuplicateEmails: false,
|
|
556
|
+
},
|
|
557
|
+
authorizedDomains: [`${projectId}.web.app`, `${projectId}.firebaseapp.com`],
|
|
558
|
+
client: {
|
|
559
|
+
permissions: {
|
|
560
|
+
disabledUserSignup: true,
|
|
561
|
+
disabledUserDeletion: true,
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
monitoring: {
|
|
565
|
+
requestLogging: {
|
|
566
|
+
enabled: true,
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
mfa: {
|
|
570
|
+
providerConfigs: [
|
|
571
|
+
{
|
|
572
|
+
state: "ENABLED",
|
|
573
|
+
totpProviderConfig: {
|
|
574
|
+
adjacentIntervals: 5,
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
},
|
|
579
|
+
emailPrivacyConfig: {
|
|
580
|
+
enableImprovedEmailPrivacy: true,
|
|
581
|
+
},
|
|
582
|
+
passwordPolicyConfig: {
|
|
583
|
+
passwordPolicyEnforcementState: "ENFORCE",
|
|
584
|
+
forceUpgradeOnSignin: JSON.parse(process.env.FB_AUTH_PASSWORD_POLICY_UPGRADE),
|
|
585
|
+
passwordPolicyVersions: [
|
|
586
|
+
{
|
|
587
|
+
customStrengthOptions: JSON.parse(process.env.FB_AUTH_PASSWORD_POLICY),
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
},
|
|
591
|
+
autodeleteAnonymousUsers: true,
|
|
592
|
+
}),
|
|
593
|
+
});
|
|
594
|
+
const authResponse = await auth.json();
|
|
595
|
+
console.log(authResponse);
|
|
596
|
+
if (!auth.ok) {
|
|
597
|
+
throw new Error("Error setting up Firebase Auth.");
|
|
598
|
+
}
|
|
599
|
+
await updateProjectData(24);
|
|
600
|
+
}
|
|
601
|
+
const secretManager = new SecretManagerServiceClient();
|
|
602
|
+
if (getProgress() < 25) {
|
|
603
|
+
if (process.env.ALGOLIA_ADMIN_KEY) {
|
|
604
|
+
const [algoliaSecret] = await secretManager.createSecret({
|
|
605
|
+
parent: `projects/${projectId}`,
|
|
606
|
+
secret: {
|
|
607
|
+
name: "ALGOLIA_ADMIN_KEY",
|
|
608
|
+
replication: {
|
|
609
|
+
automatic: {},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
secretId: "ALGOLIA_ADMIN_KEY",
|
|
613
|
+
});
|
|
614
|
+
const [algoliaSecretVersion] = await secretManager.addSecretVersion({
|
|
615
|
+
parent: algoliaSecret.name,
|
|
616
|
+
payload: {
|
|
617
|
+
data: Buffer.from(process.env.ALGOLIA_ADMIN_KEY, "utf8"),
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
console.log(algoliaSecretVersion);
|
|
621
|
+
}
|
|
622
|
+
await updateProjectData(25);
|
|
623
|
+
}
|
|
624
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
625
|
+
let smtpPasswordSecret;
|
|
626
|
+
if (getProgress() < 26) {
|
|
627
|
+
;
|
|
628
|
+
[smtpPasswordSecret] = await secretManager.createSecret({
|
|
629
|
+
parent: `projects/${projectId}`,
|
|
630
|
+
secret: {
|
|
631
|
+
name: "firestore-send-email-SMTP_PASSWORD",
|
|
632
|
+
replication: {
|
|
633
|
+
automatic: {},
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
secretId: "firestore-send-email-SMTP_PASSWORD",
|
|
637
|
+
});
|
|
638
|
+
await updateProjectData(26);
|
|
639
|
+
}
|
|
640
|
+
if (getProgress() < 27) {
|
|
641
|
+
const [smtpPasswordSecretVersion] = await secretManager.addSecretVersion({
|
|
642
|
+
parent: smtpPasswordSecret.name,
|
|
643
|
+
payload: {
|
|
644
|
+
data: Buffer.from(process.env.MAIL_SMTP_PASSWORD, "utf8"),
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
console.log(smtpPasswordSecretVersion);
|
|
648
|
+
await updateProjectData(27);
|
|
649
|
+
}
|
|
650
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
651
|
+
let twilioPasswordSecret;
|
|
652
|
+
if (getProgress() < 28) {
|
|
653
|
+
if (process.env.TWILIO_AUTH_TOKEN) {
|
|
654
|
+
;
|
|
655
|
+
[twilioPasswordSecret] = await secretManager.createSecret({
|
|
656
|
+
parent: `projects/${projectId}`,
|
|
657
|
+
secret: {
|
|
658
|
+
name: "TWILIO_AUTH_TOKEN",
|
|
659
|
+
replication: {
|
|
660
|
+
automatic: {},
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
secretId: "TWILIO_AUTH_TOKEN",
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
await updateProjectData(28);
|
|
667
|
+
}
|
|
668
|
+
if (getProgress() < 29) {
|
|
669
|
+
if (process.env.TWILIO_AUTH_TOKEN) {
|
|
670
|
+
const [twilioPasswordSecretVersion] = await secretManager.addSecretVersion({
|
|
671
|
+
parent: twilioPasswordSecret.name,
|
|
672
|
+
payload: {
|
|
673
|
+
data: Buffer.from(process.env.TWILIO_AUTH_TOKEN, "utf8"),
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
console.log(twilioPasswordSecretVersion);
|
|
677
|
+
}
|
|
678
|
+
await updateProjectData(29);
|
|
679
|
+
}
|
|
680
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
681
|
+
let twilioAccountSidSecret;
|
|
682
|
+
if (getProgress() < 30) {
|
|
683
|
+
if (process.env.TWILIO_ACCOUNT_SID) {
|
|
684
|
+
;
|
|
685
|
+
[twilioAccountSidSecret] = await secretManager.createSecret({
|
|
686
|
+
parent: `projects/${projectId}`,
|
|
687
|
+
secret: {
|
|
688
|
+
name: "TWILIO_ACCOUNT_SID",
|
|
689
|
+
replication: {
|
|
690
|
+
automatic: {},
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
secretId: "TWILIO_ACCOUNT_SID",
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
await updateProjectData(30);
|
|
697
|
+
}
|
|
698
|
+
if (getProgress() < 31) {
|
|
699
|
+
if (process.env.TWILIO_ACCOUNT_SID) {
|
|
700
|
+
const [twilioAccountSidSecretVersion] = await secretManager.addSecretVersion({
|
|
701
|
+
parent: twilioAccountSidSecret.name,
|
|
702
|
+
payload: {
|
|
703
|
+
data: Buffer.from(process.env.TWILIO_ACCOUNT_SID, "utf8"),
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
console.log(twilioAccountSidSecretVersion);
|
|
707
|
+
}
|
|
708
|
+
await updateProjectData(31);
|
|
709
|
+
}
|
|
710
|
+
if (getProgress() < 32) {
|
|
711
|
+
if (process.env.TWILIO_PHONE_NUMBER) {
|
|
712
|
+
const [twilioPhoneNumberSecret] = await secretManager.createSecret({
|
|
713
|
+
parent: `projects/${projectId}`,
|
|
714
|
+
secret: {
|
|
715
|
+
name: "TWILIO_PHONE_NUMBER",
|
|
716
|
+
replication: {
|
|
717
|
+
automatic: {},
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
secretId: "TWILIO_PHONE_NUMBER",
|
|
721
|
+
});
|
|
722
|
+
const [twilioPhoneNumberSecretVersion] = await secretManager.addSecretVersion({
|
|
723
|
+
parent: twilioPhoneNumberSecret.name,
|
|
724
|
+
payload: {
|
|
725
|
+
data: Buffer.from(process.env.TWILIO_PHONE_NUMBER, "utf8"),
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
console.log(twilioPhoneNumberSecretVersion);
|
|
729
|
+
}
|
|
730
|
+
await updateProjectData(32);
|
|
731
|
+
}
|
|
732
|
+
if (getProgress() < 33) {
|
|
733
|
+
const externalSecrets = JSON.parse(process.env.EXTERNAL_SECRETS || "{}");
|
|
734
|
+
for (const [secretName, secretValue] of Object.entries(externalSecrets)) {
|
|
735
|
+
const [externalSecret] = await secretManager.createSecret({
|
|
736
|
+
parent: `projects/${projectId}`,
|
|
737
|
+
secret: {
|
|
738
|
+
name: secretName,
|
|
739
|
+
replication: {
|
|
740
|
+
automatic: {},
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
secretId: secretName,
|
|
744
|
+
});
|
|
745
|
+
const [secretVersion] = await secretManager.addSecretVersion({
|
|
746
|
+
parent: externalSecret.name,
|
|
747
|
+
payload: {
|
|
748
|
+
data: Buffer.from(secretValue, "utf8"),
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
console.log(secretVersion);
|
|
752
|
+
}
|
|
753
|
+
await updateProjectData(33);
|
|
754
|
+
}
|
|
755
|
+
if (getProgress() < 34) {
|
|
756
|
+
const apiKeys = await runChildProcess("gcloud", [
|
|
757
|
+
"services",
|
|
758
|
+
"api-keys",
|
|
759
|
+
"list",
|
|
760
|
+
`--project=${projectId}`,
|
|
761
|
+
"--quiet",
|
|
762
|
+
"--format=json",
|
|
763
|
+
]);
|
|
764
|
+
const apiKeysJson = JSON.parse(apiKeys);
|
|
765
|
+
const apiKey = apiKeysJson[0];
|
|
766
|
+
if (!apiKey) {
|
|
767
|
+
throw new Error("Error getting Firebase API key.");
|
|
768
|
+
}
|
|
769
|
+
let allowedReferrers = `--allowed-referrers=https://${projectId}.web.app,https://${projectId}.firebaseapp.com`;
|
|
770
|
+
if (options.development) {
|
|
771
|
+
allowedReferrers = `--allowed-referrers=https://${projectId}.web.app,https://${projectId}.firebaseapp.com,http://localhost`;
|
|
772
|
+
}
|
|
773
|
+
const apiKeyUpdateArgs = [
|
|
774
|
+
"services",
|
|
775
|
+
"api-keys",
|
|
776
|
+
"update",
|
|
777
|
+
apiKey.uid,
|
|
778
|
+
"--api-target=service=maps-backend.googleapis.com",
|
|
779
|
+
"--api-target=service=geocoding-backend.googleapis.com",
|
|
780
|
+
allowedReferrers,
|
|
781
|
+
`--project=${projectId}`,
|
|
782
|
+
"--quiet",
|
|
783
|
+
];
|
|
784
|
+
apiKey.restrictions.apiTargets.forEach((target) => apiKeyUpdateArgs.push(`--api-target=service=${target.service}`));
|
|
785
|
+
await runChildProcess("gcloud", apiKeyUpdateArgs);
|
|
786
|
+
await updateProjectData(34);
|
|
787
|
+
}
|
|
788
|
+
const recaptchaResponse = await fetch(`https://recaptchaenterprise.googleapis.com/v1/projects/${projectId}/keys`, {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: {
|
|
791
|
+
Authorization: `Bearer ${token}`,
|
|
792
|
+
"Content-Type": "application/json",
|
|
793
|
+
"X-Goog-User-Project": projectId,
|
|
794
|
+
},
|
|
795
|
+
body: JSON.stringify({
|
|
796
|
+
displayName: "Firebase App Check",
|
|
797
|
+
webSettings: {
|
|
798
|
+
allowedDomains: [`${projectId}.web.app`, `${projectId}.firebaseapp.com`],
|
|
799
|
+
integrationType: "SCORE",
|
|
800
|
+
},
|
|
801
|
+
}),
|
|
802
|
+
});
|
|
803
|
+
const recaptchaResponseJson = await recaptchaResponse.json();
|
|
804
|
+
console.log(recaptchaResponseJson);
|
|
805
|
+
if (!recaptchaResponse.ok) {
|
|
806
|
+
throw new Error("Failed to create Recaptcha key");
|
|
807
|
+
}
|
|
808
|
+
const recaptchaKey = recaptchaResponseJson.name;
|
|
809
|
+
const recaptchaKeyId = recaptchaKey.split("/").pop();
|
|
810
|
+
if (getProgress() < 35) {
|
|
811
|
+
const recaptchaEnterpriseConfig = await fetch(`https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}/recaptchaEnterpriseConfig?updateMask=siteKey,tokenTtl,riskAnalysis`, {
|
|
812
|
+
method: "PATCH",
|
|
813
|
+
headers: {
|
|
814
|
+
Authorization: `Bearer ${token}`,
|
|
815
|
+
"Content-Type": "application/json",
|
|
816
|
+
"X-Goog-User-Project": projectId,
|
|
817
|
+
},
|
|
818
|
+
body: JSON.stringify({
|
|
819
|
+
siteKey: recaptchaKeyId,
|
|
820
|
+
tokenTtl: process.env.FB_APP_CHECK_TOKEN_TTL || "3600s",
|
|
821
|
+
riskAnalysis: {
|
|
822
|
+
minValidScore: process.env.FB_APP_CHECK_MIN_VALID_SCORE
|
|
823
|
+
? parseFloat(process.env.FB_APP_CHECK_MIN_VALID_SCORE)
|
|
824
|
+
: 0.5,
|
|
825
|
+
},
|
|
826
|
+
}),
|
|
827
|
+
});
|
|
828
|
+
const recaptchaEnterpriseConfigJson = await recaptchaEnterpriseConfig.json();
|
|
829
|
+
console.log(recaptchaEnterpriseConfigJson);
|
|
830
|
+
if (!recaptchaEnterpriseConfig.ok) {
|
|
831
|
+
throw new Error("Failed to create Recaptcha Enterprise config");
|
|
832
|
+
}
|
|
833
|
+
await updateProjectData(35);
|
|
834
|
+
}
|
|
835
|
+
if (getProgress() < 36) {
|
|
836
|
+
if (process.env.FB_ENABLE_APP_CHECK === "true") {
|
|
837
|
+
const servicesResponse = await fetch(`https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/services:batchUpdate`, {
|
|
838
|
+
method: "POST",
|
|
839
|
+
headers: {
|
|
840
|
+
Authorization: `Bearer ${token}`,
|
|
841
|
+
"Content-Type": "application/json",
|
|
842
|
+
"X-Goog-User-Project": projectId,
|
|
843
|
+
},
|
|
844
|
+
body: JSON.stringify({
|
|
845
|
+
requests: [
|
|
846
|
+
{
|
|
847
|
+
service: {
|
|
848
|
+
name: `projects/${projectId}/services/identitytoolkit.googleapis.com`,
|
|
849
|
+
enforcementMode: "ENFORCED",
|
|
850
|
+
},
|
|
851
|
+
updateMask: "enforcementMode",
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
service: {
|
|
855
|
+
name: `projects/${projectId}/services/firestore.googleapis.com`,
|
|
856
|
+
enforcementMode: "ENFORCED",
|
|
857
|
+
},
|
|
858
|
+
updateMask: "enforcementMode",
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
service: {
|
|
862
|
+
name: `projects/${projectId}/services/firebasedatabase.googleapis.com`,
|
|
863
|
+
enforcementMode: "ENFORCED",
|
|
864
|
+
},
|
|
865
|
+
updateMask: "enforcementMode",
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
service: {
|
|
869
|
+
name: `projects/${projectId}/services/firebasestorage.googleapis.com`,
|
|
870
|
+
enforcementMode: "ENFORCED",
|
|
871
|
+
},
|
|
872
|
+
updateMask: "enforcementMode",
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
service: {
|
|
876
|
+
name: `projects/${projectId}/services/maps-backend.googleapis.com`,
|
|
877
|
+
enforcementMode: "ENFORCED",
|
|
878
|
+
},
|
|
879
|
+
updateMask: "enforcementMode",
|
|
880
|
+
},
|
|
881
|
+
],
|
|
882
|
+
}),
|
|
883
|
+
});
|
|
884
|
+
const servicesResponseJson = await servicesResponse.json();
|
|
885
|
+
console.log(servicesResponseJson);
|
|
886
|
+
if (!servicesResponse.ok) {
|
|
887
|
+
throw new Error("Failed to update App Check services");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
await updateProjectData(36);
|
|
891
|
+
}
|
|
892
|
+
const firebaseJson = JSON.parse(await readFile(join(process.cwd(), "firebase.json"), "utf8"));
|
|
893
|
+
const authPort = firebaseJson.emulators.auth.port;
|
|
894
|
+
const databasePort = firebaseJson.emulators.database.port;
|
|
895
|
+
const firestorePort = firebaseJson.emulators.firestore.port;
|
|
896
|
+
const storagePort = firebaseJson.emulators.storage.port;
|
|
897
|
+
const functionsPort = firebaseJson.emulators.functions.port;
|
|
898
|
+
const envDir = join(process.cwd(), ".env");
|
|
899
|
+
const envFile = join(envDir, `.env.${projectId}`);
|
|
900
|
+
let envContent = `STOKER_FB_WEB_APP_CONFIG='${JSON.stringify(webAppConfig)}'
|
|
901
|
+
STOKER_FB_ENABLE_APP_CHECK=${process.env.FB_ENABLE_APP_CHECK}
|
|
902
|
+
STOKER_FB_APP_CHECK_KEY="${recaptchaKeyId}"
|
|
903
|
+
STOKER_ALGOLIA_ID="${process.env.ALGOLIA_ID || ""}"
|
|
904
|
+
STOKER_FB_FUNCTIONS_REGION="${process.env.FB_FUNCTIONS_REGION}"
|
|
905
|
+
FB_DATABASE="${projectId}-default-rtdb"
|
|
906
|
+
FB_FIRESTORE_EXPORT_BUCKET="${projectId}-export"`;
|
|
907
|
+
if (process.env.SENTRY_DSN) {
|
|
908
|
+
envContent += `\nSTOKER_SENTRY_DSN="${process.env.SENTRY_DSN}"`;
|
|
909
|
+
}
|
|
910
|
+
if (process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN && process.env.TWILIO_PHONE_NUMBER) {
|
|
911
|
+
envContent += `\nSTOKER_SMS_ENABLED=true`;
|
|
912
|
+
}
|
|
913
|
+
if (process.env.FULLCALENDAR_KEY) {
|
|
914
|
+
envContent += `\nSTOKER_FULLCALENDAR_KEY="${process.env.FULLCALENDAR_KEY}"`;
|
|
915
|
+
}
|
|
916
|
+
if (options.development) {
|
|
917
|
+
envContent += `
|
|
918
|
+
STOKER_FB_EMULATOR_AUTH_PORT=${authPort}
|
|
919
|
+
STOKER_FB_EMULATOR_DATABASE_PORT=${databasePort}
|
|
920
|
+
STOKER_FB_EMULATOR_FIRESTORE_PORT=${firestorePort}
|
|
921
|
+
STOKER_FB_EMULATOR_STORAGE_PORT=${storagePort}
|
|
922
|
+
STOKER_FB_EMULATOR_FUNCTIONS_PORT=${functionsPort}`;
|
|
923
|
+
}
|
|
924
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
925
|
+
await writeFile(envFile, envContent);
|
|
926
|
+
dotenv.config({ path: join(process.cwd(), ".env", `.env.${projectId}`), quiet: true });
|
|
927
|
+
if (getProgress() < 37) {
|
|
928
|
+
await runChildProcess("npx", ["stoker", "deploy", "--initial", "--retry"]);
|
|
929
|
+
await updateProjectData(37);
|
|
930
|
+
}
|
|
931
|
+
await runChildProcess("npx", ["stoker", "set-project"]);
|
|
932
|
+
await addTenant();
|
|
933
|
+
await updateProjectData(1000);
|
|
934
|
+
process.exit();
|
|
935
|
+
};
|