"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HubExport = void 0; const path = require("path"); const fs = require("fs"); const fse = require("fs-extra"); const http = require("http"); const logger_1 = require("../logger"); const types_1 = require("./types"); const registry_1 = require("./registry"); const error_1 = require("../error"); const hub_1 = require("./hub"); const downloadableEmulators_1 = require("./downloadableEmulators"); const rimraf = require("rimraf"); const track_1 = require("../track"); class HubExport { constructor(projectId, options) { this.projectId = projectId; this.options = options; this.exportPath = options.path; this.tmpDir = fs.mkdtempSync(`firebase-export-${new Date().getTime()}`); } static readMetadata(exportPath) { const metadataPath = path.join(exportPath, this.METADATA_FILE_NAME); if (!fs.existsSync(metadataPath)) { return undefined; } return JSON.parse(fs.readFileSync(metadataPath, "utf8").toString()); } async exportAll() { const toExport = types_1.ALL_EMULATORS.filter(shouldExport); if (toExport.length === 0) { throw new error_1.FirebaseError("No running emulators support import/export."); } const metadata = { version: hub_1.EmulatorHub.CLI_VERSION, }; if (shouldExport(types_1.Emulators.FIRESTORE)) { metadata.firestore = { version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.FIRESTORE).version, path: "firestore_export", metadata_file: "firestore_export/firestore_export.overall_export_metadata", }; await this.exportFirestore(metadata); } if (shouldExport(types_1.Emulators.DATABASE)) { metadata.database = { version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.DATABASE).version, path: "database_export", }; await this.exportDatabase(metadata); } if (shouldExport(types_1.Emulators.AUTH)) { metadata.auth = { version: hub_1.EmulatorHub.CLI_VERSION, path: "auth_export", }; await this.exportAuth(metadata); } if (shouldExport(types_1.Emulators.STORAGE)) { metadata.storage = { version: hub_1.EmulatorHub.CLI_VERSION, path: "storage_export", }; await this.exportStorage(metadata); } if (!fs.existsSync(this.exportPath)) { fs.mkdirSync(this.exportPath); } void (0, track_1.trackEmulator)("emulator_export", { initiated_by: this.options.initiatedBy, emulator_name: types_1.Emulators.HUB, }); const metadataPath = path.join(this.tmpDir, HubExport.METADATA_FILE_NAME); fs.writeFileSync(metadataPath, JSON.stringify(metadata, undefined, 2)); logger_1.logger.debug(`hubExport: swapping ${this.tmpDir} with ${this.exportPath}`); rimraf.sync(this.exportPath); fse.moveSync(this.tmpDir, this.exportPath); } async exportFirestore(metadata) { void (0, track_1.trackEmulator)("emulator_export", { initiated_by: this.options.initiatedBy, emulator_name: types_1.Emulators.FIRESTORE, }); const firestoreExportBody = { database: `projects/${this.projectId}/databases/(default)`, export_directory: this.tmpDir, export_name: metadata.firestore.path, }; await registry_1.EmulatorRegistry.client(types_1.Emulators.FIRESTORE).post(`/emulator/v1/projects/${this.projectId}:export`, firestoreExportBody); } async exportDatabase(metadata) { const databaseEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE); const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE, { auth: true }); const inspectURL = `/.inspect/databases.json`; const inspectRes = await client.get(inspectURL, { queryParams: { ns: this.projectId }, }); const namespaces = inspectRes.body.map((instance) => instance.name); const namespacesToExport = []; for (const ns of namespaces) { const checkDataPath = `/.json`; const checkDataRes = await client.get(checkDataPath, { queryParams: { ns, shallow: "true", limitToFirst: 1, }, }); if (checkDataRes.body !== null) { namespacesToExport.push(ns); } else { logger_1.logger.debug(`Namespace ${ns} contained null data, not exporting`); } } for (const ns of databaseEmulator.getImportedNamespaces()) { if (!namespacesToExport.includes(ns)) { logger_1.logger.debug(`Namespace ${ns} was imported, exporting.`); namespacesToExport.push(ns); } } void (0, track_1.trackEmulator)("emulator_export", { initiated_by: this.options.initiatedBy, emulator_name: types_1.Emulators.DATABASE, count: namespacesToExport.length, }); const dbExportPath = path.join(this.tmpDir, metadata.database.path); if (!fs.existsSync(dbExportPath)) { fs.mkdirSync(dbExportPath); } const { host, port } = databaseEmulator.getInfo(); for (const ns of namespacesToExport) { const exportFile = path.join(dbExportPath, `${ns}.json`); logger_1.logger.debug(`Exporting database instance: ${ns} to ${exportFile}`); await fetchToFile({ host, port, path: `/.json?ns=${ns}&format=export`, headers: { Authorization: "Bearer owner" }, }, exportFile); } } async exportAuth(metadata) { void (0, track_1.trackEmulator)("emulator_export", { initiated_by: this.options.initiatedBy, emulator_name: types_1.Emulators.AUTH, }); const { host, port } = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH).getInfo(); const authExportPath = path.join(this.tmpDir, metadata.auth.path); if (!fs.existsSync(authExportPath)) { fs.mkdirSync(authExportPath); } const accountsFile = path.join(authExportPath, "accounts.json"); logger_1.logger.debug(`Exporting auth users in Project ${this.projectId} to ${accountsFile}`); await fetchToFile({ host, port, path: `/identitytoolkit.googleapis.com/v1/projects/${this.projectId}/accounts:batchGet?maxResults=-1`, headers: { Authorization: "Bearer owner" }, }, accountsFile); const configFile = path.join(authExportPath, "config.json"); logger_1.logger.debug(`Exporting project config in Project ${this.projectId} to ${accountsFile}`); await fetchToFile({ host, port, path: `/emulator/v1/projects/${this.projectId}/config`, headers: { Authorization: "Bearer owner" }, }, configFile); } async exportStorage(metadata) { const storageExportPath = path.join(this.tmpDir, metadata.storage.path); if (fs.existsSync(storageExportPath)) { fse.removeSync(storageExportPath); } fs.mkdirSync(storageExportPath, { recursive: true }); const storageExportBody = { path: storageExportPath, initiatedBy: this.options.initiatedBy, }; const res = await registry_1.EmulatorRegistry.client(types_1.Emulators.STORAGE).request({ method: "POST", path: "/internal/export", headers: { "Content-Type": "application/json" }, body: storageExportBody, responseType: "stream", resolveOnHTTPError: true, }); if (res.status >= 400) { throw new error_1.FirebaseError(`Failed to export storage: ${await res.response.text()}`); } } } exports.HubExport = HubExport; HubExport.METADATA_FILE_NAME = "firebase-export-metadata.json"; function fetchToFile(options, path) { const writeStream = fs.createWriteStream(path); return new Promise((resolve, reject) => { http .get(options, (response) => { response.pipe(writeStream, { end: true }).once("close", resolve); }) .on("error", reject); }); } function shouldExport(e) { return types_1.IMPORT_EXPORT_EMULATORS.includes(e) && registry_1.EmulatorRegistry.isRunning(e); }