diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index ba776f257a3c..e1ffa0006089 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -179,6 +179,12 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: Archive desktop sourcemaps + uses: actions/upload-artifact@v3 + with: + name: desktop-sourcemap-${{ github.ref_name }} + path: desktop/dist/www/merged-source-map.js.map + iOS: name: Build and deploy iOS needs: validateActor @@ -348,6 +354,12 @@ jobs: env: S3_URL: s3://${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && '' || 'staging-' }}expensify-cash + - name: Archive web sourcemaps + uses: actions/upload-artifact@v3 + with: + name: web-sourcemap-${{ github.ref_name }} + path: dist/merged-source-map.js.map + - name: Purge Cloudflare cache run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && '' || 'staging.' }}new.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: diff --git a/README.md b/README.md index 6544e0e95486..c10c954a1864 100644 --- a/README.md +++ b/README.md @@ -230,19 +230,20 @@ Within Xcode head to the build phase - `Bundle React Native code and images`. ```jsx npm i && npm run pod-install ``` -7. Depending on the platform you are targeting, run your Android/iOS app in production mode. -8. Upon completion, the generated source map can be found at: +4. Depending on the platform you are targeting, run your Android/iOS app in production mode. +5. Upon completion, the generated source map can be found at: Android: `android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map` IOS: `main.jsbundle.map` + web: `dist/merged-source-map.js.map` ### Recording a Trace: 1. Ensure you have generated the source map as outlined above. 2. Launch the app in production mode. -2. Navigate to the feature you wish to profile. -3. Initiate the profiling session by tapping with four fingers to open the menu and selecting **`Use Profiling`**. -4. Close the menu and interact with the app. -5. After completing your interactions, tap with four fingers again and select to stop profiling. -6. You will be presented with a **`Share`** option to export the trace, which includes a trace file (`Profile.cpuprofile`) and build info (`AppInfo.json`). +3. Navigate to the feature you wish to profile. +4. Initiate the profiling session by tapping with four fingers (on mobile) or `cmd+d` (on web) to open the menu and selecting **`Use Profiling`**. +5. Close the menu and interact with the app. +6. After completing your interactions, tap with four fingers or `cmd+d` again and select to stop profiling. +7. You will be presented with a **`Share`** option to export the trace, which includes a trace file (`Profile.cpuprofile`) and build info (`AppInfo.json`). Build info: ```jsx @@ -265,6 +266,7 @@ Build info: 4. Use the following commands to symbolicate the trace for Android and iOS, respectively: Android: `npm run symbolicate-release:android` IOS: `npm run symbolicate-release:ios` +web: `npm run symbolicate-release:web` 5. A new file named `Profile_trace_for_-converted.json` will appear in your project's root folder. 6. Open this file in your tool of choice: - SpeedScope ([https://www.speedscope.app](https://www.speedscope.app/)) diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index 7a196da6b691..1be311d15c37 100644 --- a/config/webpack/webpack.dev.ts +++ b/config/webpack/webpack.dev.ts @@ -53,6 +53,10 @@ const getConfiguration = (environment: Environment): Promise => cert: path.join(__dirname, 'certificate.pem'), }, }, + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Document-Policy': 'js-profiling', + }, }, plugins: [ new DefinePlugin({ diff --git a/desktop/electron-serve.ts b/desktop/electron-serve.ts new file mode 100644 index 000000000000..42dec3e83615 --- /dev/null +++ b/desktop/electron-serve.ts @@ -0,0 +1,106 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ + +/* eslint-disable rulesdir/no-negated-variables */ + +/* eslint-disable @lwc/lwc/no-async-await */ + +/** + * This file is a modified version of the electron-serve package. + * We keep the same interface, but instead of file protocol we use buffer protocol (with support of JS self profiling). + */ +import type {BrowserWindow, Protocol} from 'electron'; +import {app, protocol, session} from 'electron'; +import fs from 'fs'; +import path from 'path'; + +type RegisterBufferProtocol = Protocol['registerBufferProtocol']; +type HandlerType = Parameters[1]; +type Optional = T | null | undefined; + +const FILE_NOT_FOUND = -6; + +const getPath = async (filePath: string): Promise> => { + try { + const result = await fs.promises.stat(filePath); + + if (result.isFile()) { + return filePath; + } + + if (result.isDirectory()) { + // eslint-disable-next-line @typescript-eslint/return-await + return getPath(path.join(filePath, 'index.html')); + } + } catch { + return null; + } +}; + +type ServeOptions = { + directory: string; + isCorsEnabled?: boolean; + scheme?: string; + hostname?: string; + file?: string; + partition?: string; +}; + +export default function electronServe(options: ServeOptions) { + const mandatoryOptions = { + isCorsEnabled: true, + scheme: 'app', + hostname: '-', + file: 'index', + ...options, + }; + + if (!mandatoryOptions.directory) { + throw new Error('The `directory` option is required'); + } + + mandatoryOptions.directory = path.resolve(app.getAppPath(), mandatoryOptions.directory); + + const handler: HandlerType = async (request, callback) => { + const filePath = path.join(mandatoryOptions.directory, decodeURIComponent(new URL(request.url).pathname)); + const resolvedPath = (await getPath(filePath)) ?? path.join(mandatoryOptions.directory, `${mandatoryOptions.file}.html`); + + try { + const data = await fs.promises.readFile(resolvedPath); + callback({ + mimeType: 'text/html', + data: Buffer.from(data), + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Document-Policy': 'js-profiling', + }, + }); + } catch (error) { + callback({error: FILE_NOT_FOUND}); + } + }; + + protocol.registerSchemesAsPrivileged([ + { + scheme: mandatoryOptions.scheme, + privileges: { + standard: true, + secure: true, + allowServiceWorkers: true, + supportFetchAPI: true, + corsEnabled: mandatoryOptions.isCorsEnabled, + }, + }, + ]); + + app.on('ready', () => { + const partitionSession = mandatoryOptions.partition ? session.fromPartition(mandatoryOptions.partition) : session.defaultSession; + + partitionSession.protocol.registerBufferProtocol(mandatoryOptions.scheme, handler); + }); + + // eslint-disable-next-line @typescript-eslint/naming-convention + return async (window_: BrowserWindow, searchParameters?: URLSearchParams) => { + const queryString = searchParameters ? `?${new URLSearchParams(searchParameters).toString()}` : ''; + await window_.loadURL(`${mandatoryOptions.scheme}://${mandatoryOptions.hostname}${queryString}`); + }; +} diff --git a/desktop/main.ts b/desktop/main.ts index d8c46bbbc89b..1221b05a8388 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -3,7 +3,6 @@ import type {BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, Web import contextMenu from 'electron-context-menu'; import log from 'electron-log'; import type {ElectronLog} from 'electron-log'; -import serve from 'electron-serve'; import {autoUpdater} from 'electron-updater'; import {machineId} from 'node-machine-id'; import checkForUpdates from '@libs/checkForUpdates'; @@ -14,6 +13,7 @@ import type {TranslationPaths} from '@src/languages/types'; import type PlatformSpecificUpdater from '@src/setup/platformSetup/types'; import type {Locale} from '@src/types/onyx'; import type {CreateDownloadQueueModule, DownloadItem} from './createDownloadQueue'; +import serve from './electron-serve'; import ELECTRON_EVENTS from './ELECTRON_EVENTS'; const createDownloadQueue = require('./createDownloadQueue').default; diff --git a/desktop/package-lock.json b/desktop/package-lock.json index efa8a25a0614..44017c9fda48 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -9,7 +9,6 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.8", - "electron-serve": "^1.3.0", "electron-updater": "^6.2.1", "node-machine-id": "^1.1.12" } @@ -144,17 +143,6 @@ "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, - "node_modules/electron-serve": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.3.0.tgz", - "integrity": "sha512-OEC/48ZBJxR6XNSZtCl4cKPyQ1lvsu8yp8GdCplMWwGS1eEyMcEmzML5BRs/io/RLDnpgyf+7rSL+X6ICifRIg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/electron-updater": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz", @@ -535,11 +523,6 @@ "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, - "electron-serve": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.3.0.tgz", - "integrity": "sha512-OEC/48ZBJxR6XNSZtCl4cKPyQ1lvsu8yp8GdCplMWwGS1eEyMcEmzML5BRs/io/RLDnpgyf+7rSL+X6ICifRIg==" - }, "electron-updater": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz", diff --git a/desktop/package.json b/desktop/package.json index 4249f3fcfba9..34343ad335d6 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -6,7 +6,6 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.8", - "electron-serve": "^1.3.0", "electron-updater": "^6.2.1", "node-machine-id": "^1.1.12" }, diff --git a/package-lock.json b/package-lock.json index 2ced5738cca8..5673bb1407c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -225,6 +225,7 @@ "eslint-plugin-testing-library": "^6.2.2", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", "html-webpack-plugin": "^5.5.0", + "http-server": "^14.1.1", "jest": "29.4.1", "jest-circus": "29.4.1", "jest-cli": "29.4.1", @@ -244,6 +245,7 @@ "reassure": "^0.10.1", "setimmediate": "^1.0.5", "shellcheck": "^1.1.0", + "source-map": "^0.7.4", "storybook": "^8.1.10", "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", @@ -10170,6 +10172,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/addon-docs/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/addon-essentials": { "version": "8.1.10", "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.1.10.tgz", @@ -10697,6 +10708,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/addon-essentials/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/addon-essentials/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11435,6 +11455,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/blocks/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/blocks/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12015,6 +12044,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/builder-manager/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/builder-manager/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12836,7 +12874,6 @@ "node_modules/@storybook/cli/node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "engines": { "node": ">=14.16" @@ -12845,8 +12882,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@storybook/cli/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/cli/node_modules/supports-color": { - "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, @@ -13261,6 +13305,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@storybook/codemod/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/codemod/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14359,6 +14412,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@storybook/core-server/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/core-server/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14618,6 +14680,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/csf-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/csf-tools": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-8.0.6.tgz", @@ -14669,6 +14740,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/csf-tools/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/csf/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -15873,6 +15953,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/react/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/react/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16475,6 +16564,15 @@ "node": ">= 4" } }, + "node_modules/@storybook/telemetry/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@storybook/telemetry/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -20521,6 +20619,18 @@ ], "license": "MIT" }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/batch": { "version": "0.6.1", "dev": true, @@ -21643,6 +21753,14 @@ "node": ">= 10.0" } }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "license": "MIT", @@ -22494,6 +22612,15 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "dev": true, @@ -22894,6 +23021,14 @@ "node": ">=8.0.0" } }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "license": "BSD-2-Clause", @@ -24415,6 +24550,15 @@ "source-map": "~0.6.1" } }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", @@ -27469,6 +27613,15 @@ "uglify-js": "^3.1.4" } }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has": { "version": "1.0.3", "license": "MIT", @@ -27756,14 +27909,6 @@ "node": ">=8" } }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/hmac-drbg": { "version": "1.0.1", "license": "MIT", @@ -27812,6 +27957,18 @@ "wbuf": "^1.1.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-entities": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", @@ -28015,6 +28172,121 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/http-server/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/http-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, "node_modules/http2-wrapper": { "version": "1.0.3", "dev": true, @@ -29499,6 +29771,14 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-reports": { "version": "3.1.5", "license": "BSD-3-Clause", @@ -30256,16 +30536,6 @@ "node": ">=14" } }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-encoding": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": { "version": "3.0.0", "license": "MIT", @@ -30789,6 +31059,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jest-runner/node_modules/source-map-support": { "version": "0.5.13", "license": "MIT", @@ -37141,14 +37419,6 @@ "node": ">=8" } }, - "node_modules/react-native-release-profiler/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/react-native-render-html": { "version": "6.3.1", "license": "BSD-2-Clause", @@ -38407,6 +38677,14 @@ "node": ">=4" } }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -39067,6 +39345,12 @@ "version": "0.4.1", "license": "MIT" }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, "node_modules/seedrandom": { "version": "3.0.5", "dev": true, @@ -39875,10 +40159,11 @@ "license": "MIT" }, "node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, "node_modules/source-map-generator": { @@ -39917,6 +40202,14 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-url": { "version": "0.4.1", "license": "MIT", @@ -41823,6 +42116,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/union-value": { "version": "1.0.1", "license": "MIT", @@ -43208,6 +43513,14 @@ "source-map": "~0.6.1" } }, + "node_modules/webpack-sources/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-virtual-modules": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", @@ -43282,6 +43595,17 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.2", "license": "MIT" diff --git a/package.json b/package.json index 81e963e12671..8fdf22df7cb4 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "web": "scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", "web-proxy": "ts-node web/proxy.ts", "web-server": "webpack-dev-server --open --config config/webpack/webpack.dev.ts", - "build": "webpack --config config/webpack/webpack.common.ts --env file=.env.production", - "build-staging": "webpack --config config/webpack/webpack.common.ts --env file=.env.staging", - "build-adhoc": "webpack --config config/webpack/webpack.common.ts --env file=.env.adhoc", + "build": "webpack --config config/webpack/webpack.common.ts --env file=.env.production && ts-node scripts/combine-web-sourcemaps.ts", + "build-staging": "webpack --config config/webpack/webpack.common.ts --env file=.env.staging && ts-node scripts/combine-web-sourcemaps.ts", + "build-adhoc": "webpack --config config/webpack/webpack.common.ts --env file=.env.adhoc && ts-node scripts/combine-web-sourcemaps.ts", "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.ts", "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", @@ -53,7 +53,9 @@ "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", "symbolicate-release:ios": "scripts/release-profile.ts --platform=ios", "symbolicate-release:android": "scripts/release-profile.ts --platform=android", + "symbolicate-release:web": "scripts/release-profile.ts --platform=web", "symbolicate-profile": "scripts/symbolicate-profile.ts", + "combine-web-sourcemaps": "scripts/combine-web-sourcemaps.ts", "test:e2e": "ts-node tests/e2e/testRunner.ts --config ./config.local.ts", "test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.ts", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", @@ -62,7 +64,8 @@ "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1", "e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/", "react-compiler-healthcheck": "react-compiler-healthcheck --verbose", - "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy " + "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy ", + "web:prod": "http-server ./dist --cors" }, "dependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", @@ -280,6 +283,7 @@ "eslint-plugin-testing-library": "^6.2.2", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", "html-webpack-plugin": "^5.5.0", + "http-server": "^14.1.1", "jest": "29.4.1", "jest-circus": "29.4.1", "jest-cli": "29.4.1", @@ -299,6 +303,7 @@ "reassure": "^0.10.1", "setimmediate": "^1.0.5", "shellcheck": "^1.1.0", + "source-map": "^0.7.4", "storybook": "^8.1.10", "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", diff --git a/patches/http-server+14.1.1.patch b/patches/http-server+14.1.1.patch new file mode 100644 index 000000000000..4f294f930630 --- /dev/null +++ b/patches/http-server+14.1.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/http-server/lib/http-server.js b/node_modules/http-server/lib/http-server.js +index dfe4c47..b1c3089 100644 +--- a/node_modules/http-server/lib/http-server.js ++++ b/node_modules/http-server/lib/http-server.js +@@ -98,6 +98,8 @@ function HttpServer(options) { + }); + } + ++ this.headers['Document-Policy'] = 'js-profiling'; ++ + if (options.cors) { + this.headers['Access-Control-Allow-Origin'] = '*'; + this.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Range'; diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index 791f59d73330..55cb416c1112 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -32,6 +32,10 @@ info " • ENV file: $ENV_FILE" info "" npx webpack --config config/webpack/webpack.desktop.ts --env file=$ENV_FILE +title "Combining web sourcemaps" +info "" +ts-node scripts/combine-web-sourcemaps.ts --path="desktop/dist/www" + title "Building Desktop App Archive Using Electron" info "" shift 1 diff --git a/scripts/combine-web-sourcemaps.ts b/scripts/combine-web-sourcemaps.ts new file mode 100755 index 000000000000..4a5972ed79c5 --- /dev/null +++ b/scripts/combine-web-sourcemaps.ts @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/await-thenable */ +import fs from 'fs'; +import path from 'path'; +import type {RawSourceMap} from 'source-map'; +import {SourceMapConsumer, SourceMapGenerator} from 'source-map'; +import parseCommandLineArguments from './utils/parseCommandLineArguments'; + +const argsMap = parseCommandLineArguments(); + +const distDir = path.resolve(__dirname, '..', argsMap.path ?? 'dist'); +const outputFile = path.join(distDir, 'merged-source-map.js.map'); + +async function mergeSourceMaps() { + // Read all .map files in the dist directory + const sourceMapFiles = fs + .readdirSync(distDir) + .filter((file) => file.endsWith('.map')) + .map((file) => path.join(distDir, file)); + + const mergedGenerator = new SourceMapGenerator(); + + for (const file of sourceMapFiles) { + const sourceMapContent = JSON.parse(fs.readFileSync(file, 'utf8')) as RawSourceMap; + const consumer = await new SourceMapConsumer(sourceMapContent); + + consumer.eachMapping((mapping) => { + if (!mapping.source) { + return; + } + + mergedGenerator.addMapping({ + generated: { + line: mapping.generatedLine, + column: mapping.generatedColumn, + }, + original: { + line: mapping.originalLine, + column: mapping.originalColumn, + }, + source: mapping.source, + name: mapping.name, + }); + }); + + // Add the sources content + consumer.sources.forEach((sourceFile: string) => { + const content = consumer.sourceContentFor(sourceFile); + if (content) { + mergedGenerator.setSourceContent(sourceFile, content); + } + }); + + consumer.destroy(); + } + + // Write the merged source map to a file + fs.writeFileSync(outputFile, mergedGenerator.toString()); + + console.log(`Merged source map written to ${outputFile}`); +} + +mergeSourceMaps().catch(console.error); diff --git a/scripts/release-profile.ts b/scripts/release-profile.ts index cfc7e2cb8838..a83fb55fa5ff 100755 --- a/scripts/release-profile.ts +++ b/scripts/release-profile.ts @@ -19,6 +19,8 @@ if (argsMap.platform === 'ios') { sourcemapPath = 'main.jsbundle.map'; } else if (argsMap.platform === 'android') { sourcemapPath = 'android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map'; +} else if (argsMap.platform === 'web') { + sourcemapPath = 'dist/merged-source-map.js.map'; } else { console.error('Please specify the platform using --platform=ios or --platform=android'); process.exit(1); @@ -35,7 +37,7 @@ if (cpuProfiles.length === 0) { } else { // Construct the command const cpuprofileName = cpuProfiles[0]; - const command = `npx react-native-release-profiler --local ${cpuprofileName} --sourcemap-path ${sourcemapPath}`; + const command = `npx react-native-release-profiler --local "${cpuprofileName}" --sourcemap-path "${sourcemapPath}"`; console.log(`Executing: ${command}`); diff --git a/src/components/ClientSideLoggingToolMenu/BaseClientSideLoggingToolMenu.tsx b/src/components/ClientSideLoggingToolMenu/BaseClientSideLoggingToolMenu.tsx index 7c4c669e2154..379668e4850b 100644 --- a/src/components/ClientSideLoggingToolMenu/BaseClientSideLoggingToolMenu.tsx +++ b/src/components/ClientSideLoggingToolMenu/BaseClientSideLoggingToolMenu.tsx @@ -21,9 +21,14 @@ type BaseClientSideLoggingToolMenuOnyxProps = { shouldStoreLogs: OnyxEntry; }; +type File = { + path: string; + newFileName: string; + size: number; +}; type BaseClientSideLoggingToolProps = { /** Locally created file */ - file?: {path: string; newFileName: string; size: number}; + file?: File; /** Action to run when pressing Share button */ onShareLogs?: () => void; /** Action to run when disabling the switch */ @@ -96,3 +101,4 @@ export default withOnyx([]); + const [file, setFile] = useState(undefined); const downloadFile = (logs: Log[]) => { - localFileDownload('logs', JSON.stringify(logs, null, 2)); + const data = JSON.stringify(logs, null, 2); + setFile({ + path: './logs', + newFileName: 'logs', + size: data.length, + }); + setLocalLogs(logs); + localFileDownload('logs', data); + }; + const hideShareButton = () => { + setFile(undefined); + }; + const shareLogs = () => { + downloadFile(localLogs); }; - return ; + return ( + + ); } ClientSideLoggingToolMenu.displayName = 'ClientSideLoggingToolMenu'; diff --git a/src/components/ProfilingToolMenu/BaseProfilingToolMenu.tsx b/src/components/ProfilingToolMenu/BaseProfilingToolMenu.tsx index 5593ad627e92..12282bf64445 100644 --- a/src/components/ProfilingToolMenu/BaseProfilingToolMenu.tsx +++ b/src/components/ProfilingToolMenu/BaseProfilingToolMenu.tsx @@ -1,10 +1,8 @@ import React, {useCallback, useEffect, useState} from 'react'; import DeviceInfo from 'react-native-device-info'; -import RNFS from 'react-native-fs'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import {startProfiling, stopProfiling} from 'react-native-release-profiler'; -import Share from 'react-native-share'; import Button from '@components/Button'; import Switch from '@components/Switch'; import TestToolRow from '@components/TestToolRow'; @@ -18,6 +16,8 @@ import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import pkg from '../../../package.json'; +import RNFS from './RNFS'; +import Share from './Share'; type BaseProfilingToolMenuOnyxProps = { isProfilingInProgress: OnyxEntry; @@ -28,6 +28,8 @@ type BaseProfilingToolMenuProps = { pathToBeUsed: string; /** Path used to display location of saved file */ displayPath: string; + /** Whether to show the share button */ + showShareButton?: boolean; } & BaseProfilingToolMenuOnyxProps; function formatBytes(bytes: number, decimals = 2) { @@ -47,9 +49,9 @@ function formatBytes(bytes: number, decimals = 2) { // WARNING: When changing this name make sure that the "scripts/symbolicate-profile.ts" script is still working! const newFileName = `Profile_trace_for_${pkg.version}.cpuprofile`; -function BaseProfilingToolMenu({isProfilingInProgress = false, pathToBeUsed, displayPath}: BaseProfilingToolMenuProps) { +function BaseProfilingToolMenu({isProfilingInProgress = false, showShareButton = false, pathToBeUsed, displayPath}: BaseProfilingToolMenuProps) { const styles = useThemeStyles(); - const [pathIOS, setPathIOS] = useState(''); + const [filePath, setFilePath] = useState(''); const [sharePath, setSharePath] = useState(''); const [totalMemory, setTotalMemory] = useState(0); const [usedMemory, setUsedMemory] = useState(0); @@ -57,8 +59,8 @@ function BaseProfilingToolMenu({isProfilingInProgress = false, pathToBeUsed, dis // eslint-disable-next-line @lwc/lwc/no-async-await const stop = useCallback(async () => { - const path = await stopProfiling(getPlatform() === CONST.PLATFORM.IOS); - setPathIOS(path); + const path = await stopProfiling(getPlatform() === CONST.PLATFORM.IOS || getPlatform() === CONST.PLATFORM.WEB, newFileName); + setFilePath(path); const amountOfTotalMemory = await DeviceInfo.getTotalMemory(); const amountOfUsedMemory = await DeviceInfo.getUsedMemory(); @@ -92,7 +94,7 @@ function BaseProfilingToolMenu({isProfilingInProgress = false, pathToBeUsed, dis ); useEffect(() => { - if (!pathIOS) { + if (!filePath) { return; } @@ -112,7 +114,7 @@ function BaseProfilingToolMenu({isProfilingInProgress = false, pathToBeUsed, dis } // Copy the file to a new location with the desired filename - await RNFS.copyFile(pathIOS, newFilePath) + await RNFS.copyFile(filePath, newFilePath) .then(() => { Log.hmmm('[ProfilingToolMenu] file copied successfully'); }) @@ -124,7 +126,7 @@ function BaseProfilingToolMenu({isProfilingInProgress = false, pathToBeUsed, dis }; rename(); - }, [pathIOS, pathToBeUsed]); + }, [filePath, pathToBeUsed]); const onDownloadProfiling = useCallback(() => { // eslint-disable-next-line @lwc/lwc/no-async-await @@ -158,7 +160,7 @@ function BaseProfilingToolMenu({isProfilingInProgress = false, pathToBeUsed, dis onToggle={onToggleProfiling} /> - {!!pathIOS && ( + {!!filePath && showShareButton && ( <> {`path: ${displayPath}/${newFileName}`} diff --git a/src/components/ProfilingToolMenu/RNFS/index.ts b/src/components/ProfilingToolMenu/RNFS/index.ts new file mode 100644 index 000000000000..374ef87f13a4 --- /dev/null +++ b/src/components/ProfilingToolMenu/RNFS/index.ts @@ -0,0 +1,3 @@ +import RNFS from 'react-native-fs'; + +export default RNFS; diff --git a/src/components/ProfilingToolMenu/RNFS/index.web.ts b/src/components/ProfilingToolMenu/RNFS/index.web.ts new file mode 100644 index 000000000000..93d2d84d6fe7 --- /dev/null +++ b/src/components/ProfilingToolMenu/RNFS/index.web.ts @@ -0,0 +1,18 @@ +const RNFS = { + exists: () => Promise.resolve(false), + unlink: () => Promise.resolve(true), + copyFile: () => Promise.resolve(true), + DocumentDirectoryPath: '', + writeFile: (path: string, data: string, encoding: string) => { + const dataStr = `data:text/json;charset=${encoding},${encodeURIComponent(JSON.stringify(data))}`; + const downloadAnchorNode = document.createElement('a'); + + downloadAnchorNode.setAttribute('href', dataStr); + downloadAnchorNode.setAttribute('download', path); + document.body.appendChild(downloadAnchorNode); // required for Firefox + downloadAnchorNode.click(); + downloadAnchorNode.remove(); + }, +}; + +export default RNFS; diff --git a/src/components/ProfilingToolMenu/Share/index.ts b/src/components/ProfilingToolMenu/Share/index.ts new file mode 100644 index 000000000000..e08a046855a5 --- /dev/null +++ b/src/components/ProfilingToolMenu/Share/index.ts @@ -0,0 +1,3 @@ +import Share from 'react-native-share'; + +export default Share; diff --git a/src/components/ProfilingToolMenu/Share/index.web.ts b/src/components/ProfilingToolMenu/Share/index.web.ts new file mode 100644 index 000000000000..6572b8a14440 --- /dev/null +++ b/src/components/ProfilingToolMenu/Share/index.web.ts @@ -0,0 +1,5 @@ +const Share = { + open: () => Promise.resolve(), +}; + +export default Share; diff --git a/src/components/ProfilingToolMenu/index.android.tsx b/src/components/ProfilingToolMenu/index.android.tsx index 7ca3cc24c9a1..33c7354f199e 100644 --- a/src/components/ProfilingToolMenu/index.android.tsx +++ b/src/components/ProfilingToolMenu/index.android.tsx @@ -8,6 +8,7 @@ function ProfilingToolMenu() { ); } diff --git a/src/components/ProfilingToolMenu/index.ios.tsx b/src/components/ProfilingToolMenu/index.ios.tsx index 45400b6a6959..03298eccf034 100644 --- a/src/components/ProfilingToolMenu/index.ios.tsx +++ b/src/components/ProfilingToolMenu/index.ios.tsx @@ -12,6 +12,7 @@ function ProfilingToolMenu() { ); } diff --git a/src/components/ProfilingToolMenu/index.tsx b/src/components/ProfilingToolMenu/index.tsx index ab12fff27ef8..3a94ed7f2b16 100644 --- a/src/components/ProfilingToolMenu/index.tsx +++ b/src/components/ProfilingToolMenu/index.tsx @@ -1,5 +1,13 @@ +import React from 'react'; +import BaseProfilingToolMenu from './BaseProfilingToolMenu'; + function ProfilingToolMenu() { - return null; + return ( + + ); } ProfilingToolMenu.displayName = 'ProfilingToolMenu'; diff --git a/web/proxy.ts b/web/proxy.ts index 6660db3f5e87..a72c428afe75 100644 --- a/web/proxy.ts +++ b/web/proxy.ts @@ -27,6 +27,18 @@ const server = http.createServer((request: IncomingMessage, response: ServerResp let hostname = host; let requestPath = request.url; + // Add CORS headers + response.setHeader('Access-Control-Allow-Origin', '*'); + response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + response.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With'); + + // Handle preflight requests + if (request.method === 'OPTIONS') { + response.writeHead(200); + response.end(); + return; + } + /** * When a request is matching a proxy config path we might direct it to a different host (e.g. staging) * For requests matching proxy config patterns we replace the mapping url (prefix) with the actual path. diff --git a/workflow_tests/assertions/platformDeployAssertions.ts b/workflow_tests/assertions/platformDeployAssertions.ts index af80e9f2beb3..f16964de265f 100644 --- a/workflow_tests/assertions/platformDeployAssertions.ts +++ b/workflow_tests/assertions/platformDeployAssertions.ts @@ -133,6 +133,14 @@ function assertDesktopJobExecuted(workflowResult: Step[], didExecute = true, isP ); } + steps.push( + createStepAssertion('Archive desktop sourcemaps', true, null, 'DESKTOP', 'Archiving desktop sourcemaps', [ + // Note 1.2.3 comes from the ref name that we are on, which is the version we are deploying + {key: 'name', value: 'desktop-sourcemap-1.2.3'}, + {key: 'path', value: 'desktop/dist/www/merged-source-map.js.map'}, + ]), + ); + steps.forEach((expectedStep) => { if (didExecute) { expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); @@ -241,6 +249,11 @@ function assertWebJobExecuted(workflowResult: Step[], didExecute = true) { createStepAssertion('Build web', true, null, 'WEB', 'Building web'), createStepAssertion('Build storybook docs', true, null, 'WEB', 'Build storybook docs'), createStepAssertion('Deploy to S3', true, null, 'WEB', 'Deploying to S3'), + createStepAssertion('Archive web sourcemaps', true, null, 'WEB', 'Archiving web sourcemaps', null, [ + // Note 1.2.3 comes from the ref name that we are on, which is the version we are deploying + {key: 'name', value: 'web-sourcemap-1.2.3'}, + {key: 'path', value: 'dist/merged-source-map.js.map'}, + ]), createStepAssertion('Purge Cloudflare cache', true, null, 'WEB', 'Purging Cloudflare cache', null, [{key: 'CF_API_KEY', value: '***'}]), ); diff --git a/workflow_tests/mocks/platformDeployMocks.ts b/workflow_tests/mocks/platformDeployMocks.ts index 8889e9dc27db..aec3f465f885 100644 --- a/workflow_tests/mocks/platformDeployMocks.ts +++ b/workflow_tests/mocks/platformDeployMocks.ts @@ -96,6 +96,7 @@ const PLATFORM_DEPLOY__DESKTOP__UPLOAD_WORKFLOW__STEP_MOCK = createMockStep('Upl const PLATFORM_DEPLOY__DESKTOP__UPLOAD_GH_RELEASE__STEP_MOCK = createMockStep('Upload desktop build to GitHub Release', 'Uploading desktop build to GitHub Release', 'DESKTOP', null, [ 'GITHUB_TOKEN', ]); +const PLATFORM_DEPLOY__DESKTOP__ARCHIVE_SOURCEMAPS__STEP_MOCK = createMockStep('Archive desktop sourcemaps', 'Archiving desktop sourcemaps', 'DESKTOP', ['name', 'path']); const PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS = [ PLATFORM_DEPLOY__DESKTOP__CHECKOUT__STEP_MOCK, PLATFORM_DEPLOY__DESKTOP__SETUP_NODE__STEP_MOCK, @@ -103,6 +104,7 @@ const PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS = [ PLATFORM_DEPLOY__DESKTOP__BUILD__STEP_MOCK, PLATFORM_DEPLOY__DESKTOP__UPLOAD_WORKFLOW__STEP_MOCK, PLATFORM_DEPLOY__DESKTOP__UPLOAD_GH_RELEASE__STEP_MOCK, + PLATFORM_DEPLOY__DESKTOP__ARCHIVE_SOURCEMAPS__STEP_MOCK, ]; // ios @@ -181,6 +183,7 @@ const PLATFORM_DEPLOY__WEB__BUILD__STEP_MOCK = createMockStep('Build web', 'Buil const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS__STEP_MOCK = createMockStep('Build storybook docs', 'Build storybook docs', 'WEB'); const PLATFORM_DEPLOY__WEB__DEPLOY_S3__STEP_MOCK = createMockStep('Deploy to S3', 'Deploying to S3', 'WEB'); const PLATFORM_DEPLOY__WEB__PURGE_CLOUDFLARE_CACHE__STEP_MOCK = createMockStep('Purge Cloudflare cache', 'Purging Cloudflare cache', 'WEB', null, ['CF_API_KEY']); +const PLATFORM_DEPLOY__WEB__ARCHIVE_SOURCEMAPS__STEP_MOCK = createMockStep('Archive web sourcemaps', 'Archiving web sourcemaps', 'WEB', ['name', 'path']); const PLATFORM_DEPLOY__WEB__STEP_MOCKS = [ PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK, PLATFORM_DEPLOY__WEB__SETUP_NODE__STEP_MOCK, @@ -189,6 +192,7 @@ const PLATFORM_DEPLOY__WEB__STEP_MOCKS = [ PLATFORM_DEPLOY__WEB__BUILD__STEP_MOCK, PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS__STEP_MOCK, PLATFORM_DEPLOY__WEB__DEPLOY_S3__STEP_MOCK, + PLATFORM_DEPLOY__WEB__ARCHIVE_SOURCEMAPS__STEP_MOCK, PLATFORM_DEPLOY__WEB__PURGE_CLOUDFLARE_CACHE__STEP_MOCK, ];