diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index 64ce78541..3b3a914c9 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -162,10 +162,18 @@ export interface FSStatsLike { birthtimeMs: number; } +export interface DeviceDebugFile { + path: string; + data: string; +} +export interface DeviceDebugCommand { + command: string; + result: string; +} export interface DeviceDebugData { serial: string; appId: string; - data: ({path: string; data: string} | {command: string; result: string})[]; + data: (DeviceDebugFile | DeviceDebugCommand)[]; } export type FlipperServerCommands = { diff --git a/desktop/flipper-ui-core/package.json b/desktop/flipper-ui-core/package.json index 7964cb2ea..c9602bfc3 100644 --- a/desktop/flipper-ui-core/package.json +++ b/desktop/flipper-ui-core/package.json @@ -28,6 +28,7 @@ "hotkeys-js": "^3.9.3", "immer": "^9.0.12", "js-base64": "^3.7.2", + "jszip": "^3.10.1", "lodash": "^4.17.21", "lodash.memoize": "^4.1.2", "p-map": "^4.0.0", diff --git a/desktop/flipper-ui-core/src/utils/__tests__/safeFilename.node.tsx b/desktop/flipper-ui-core/src/utils/__tests__/safeFilename.node.tsx new file mode 100644 index 000000000..251636bfb --- /dev/null +++ b/desktop/flipper-ui-core/src/utils/__tests__/safeFilename.node.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {safeFilename} from '../safeFilename'; + +describe('safeFilename', () => { + test('replaces special chars in a string to make it a safe file name', async () => { + expect(safeFilename('/data/data/0/files/sonar/spec!al file name%')).toBe( + '-data-data-0-files-sonar-spec-al-file-name-', + ); + }); +}); diff --git a/desktop/flipper-ui-core/src/utils/exportData.tsx b/desktop/flipper-ui-core/src/utils/exportData.tsx index 81aa4cc52..f78415a1a 100644 --- a/desktop/flipper-ui-core/src/utils/exportData.tsx +++ b/desktop/flipper-ui-core/src/utils/exportData.tsx @@ -8,7 +8,7 @@ */ import * as React from 'react'; -import {getLogger} from 'flipper-common'; +import {getLogger, DeviceDebugFile, DeviceDebugCommand} from 'flipper-common'; import {Store, MiddlewareAPI} from '../reducers'; import {DeviceExport} from 'flipper-frontend-core'; import {selectedPlugins, State as PluginsState} from '../reducers/plugins'; @@ -33,6 +33,8 @@ import ExportDataPluginSheet from '../chrome/ExportDataPluginSheet'; import {getRenderHostInstance} from 'flipper-frontend-core'; import {uploadFlipperMedia} from '../fb-stubs/user'; import {exportLogs} from '../chrome/ConsoleLogs'; +import JSZip from 'jszip'; +import {safeFilename} from './safeFilename'; export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; @@ -612,25 +614,50 @@ export async function startFlipperLogsExport() { await getRenderHostInstance().exportFile?.(serializedLogs); } -export async function startClientLogsExport() { - const _clientLogs = await getRenderHostInstance().flipperServer.exec( - 'fetch-debug-data', - ); - - // TODO: Save all log files +async function startDeviceFlipperFolderExport() { + return await getRenderHostInstance().flipperServer.exec('fetch-debug-data'); } export async function exportEverythingEverywhereAllAtOnce( store: MiddlewareAPI, ) { // TODO: Show a progress dialog - // TODO: Pack all files in a single archive + const zip = new JSZip(); // Step 1: Export Flipper logs - await startFlipperLogsExport(); + const serializedLogs = exportLogs + .map((item) => JSON.stringify(item)) + .join('\n'); + + zip.file('flipper_logs.txt', serializedLogs); // Step 2: Export device logs - await startClientLogsExport(); + const flipperFolderContent = await startDeviceFlipperFolderExport(); + + const deviceFlipperFolder = zip.folder('device_flipper_folder')!; + flipperFolderContent.forEach((deviceDebugItem) => { + const deviceAppFolder = deviceFlipperFolder.folder( + safeFilename(`${deviceDebugItem.serial}__${deviceDebugItem.appId}`), + )!; + + deviceDebugItem.data.forEach((appDebugItem) => { + const appDebugItemIsFile = ( + item: DeviceDebugFile | DeviceDebugCommand, + ): item is DeviceDebugFile => !!(appDebugItem as DeviceDebugFile).path; + + if (appDebugItemIsFile(appDebugItem)) { + deviceAppFolder.file( + safeFilename(appDebugItem.path), + appDebugItem.data, + ); + } else { + deviceAppFolder.file( + safeFilename(appDebugItem.command), + appDebugItem.result, + ); + } + }); + }); // Step 3: Export Flipper State // TODO: Export all plugins automatically @@ -642,7 +669,12 @@ export async function exportEverythingEverywhereAllAtOnce( // need to be cleaned up later in combination with SupportForm store.dispatch(selectedPlugins(plugins)); const {serializedString} = await exportStore(store); - await getRenderHostInstance().exportFile?.(serializedString); + + zip.file('flipper_export', serializedString); + + const archiveData = await zip.generateAsync({type: 'uint8array'}); + + await getRenderHostInstance().exportFileBinary?.(archiveData); } export async function startFileExport(dispatch: Store['dispatch']) { diff --git a/desktop/flipper-ui-core/src/utils/safeFilename.tsx b/desktop/flipper-ui-core/src/utils/safeFilename.tsx new file mode 100644 index 000000000..287939820 --- /dev/null +++ b/desktop/flipper-ui-core/src/utils/safeFilename.tsx @@ -0,0 +1,11 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +export const safeFilename = (rawFilename: string) => + rawFilename.replace(/(\W+)/gi, '-'); diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 4f516459c..8a9abb426 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -9965,6 +9965,16 @@ jszip@^3.1.0: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"