diff --git a/app/scripts/lib/manifestFlags.ts b/app/scripts/lib/manifestFlags.ts index 5804c7391973..289e7f931635 100644 --- a/app/scripts/lib/manifestFlags.ts +++ b/app/scripts/lib/manifestFlags.ts @@ -1,17 +1,59 @@ import browser from 'webextension-polyfill'; +/** + * Flags that we use to control runtime behavior of the extension. Typically + * used for E2E tests. + * + * These flags are added to `manifest.json` for runtime querying. + */ export type ManifestFlags = { + /** + * CircleCI metadata for the current run + */ circleci?: { + /** + * Whether CircleCI manifest flags are enabled. + */ enabled: boolean; + /** + * The name of the branch that triggered the current run on CircleCI + */ branch?: string; + /** + * The current CircleCI build number + */ buildNum?: number; + /** + * The name of the CircleCI job currently running + */ job?: string; + /** + * For jobs with CircleCI parallelism enabled, this is the index of the current machine. + */ nodeIndex?: number; + /** + * The pull request number of the pull request the current run was triggered by + */ prNumber?: number; }; + /** + * Sentry flags + */ sentry?: { + /** + * Override the performance trace sample rate + */ tracesSampleRate?: number; - lazyLoadSubSampleRate?: number; // multiply by tracesSampleRate to get the actual probability + /** + * Sub-sample rate for lazy-loaded components. + * + * Multiply this rate by tracesSampleRate to get the actual probability of sampling the load + * time of for a lazy-loaded component. + */ + lazyLoadSubSampleRate?: number; + /** + * Force enable Sentry + */ forceEnable?: boolean; }; }; diff --git a/development/lib/get-manifest-flag.ts b/development/lib/get-manifest-flag.ts new file mode 100644 index 000000000000..d9cfb2a17439 --- /dev/null +++ b/development/lib/get-manifest-flag.ts @@ -0,0 +1,103 @@ +import path from 'node:path'; +import fs from 'node:fs/promises'; +import { promisify } from 'node:util'; +import { exec as callbackExec } from 'node:child_process'; + +import { hasProperty } from '@metamask/utils'; +import { merge } from 'lodash'; + +import type { ManifestFlags } from '../../app/scripts/lib/manifestFlags'; + +const exec = promisify(callbackExec); +const PR_BODY_FILEPATH = path.resolve( + __dirname, + '..', + '..', + 'changed-files', + 'pr-body.txt', +); + +/** + * Search a string for `flags = {...}` and return ManifestFlags if it exists + * + * @param str - The string to search + * @param errorType - The type of error to log if parsing fails + * @returns The ManifestFlags object if valid, otherwise undefined + */ +function regexSearchForFlags(str: string, errorType: string): ManifestFlags { + // Search str for `flags = {...}` + const flagsMatch = str.match(/flags\s*=\s*(\{.*\})/u); + + if (flagsMatch) { + try { + // Get 1st capturing group from regex + return JSON.parse(flagsMatch[1]); + } catch (error) { + console.error( + `Error parsing flags from ${errorType}, ignoring flags\n`, + error, + ); + } + } + + return {}; +} + +/** + * Get flags from the GitHub PR body if they are set + * + * To use this feature, add a line to your PR body like: + * `flags = {"sentry": {"tracesSampleRate": 0.1}}` + * (must be valid JSON) + * + * @returns Any manifest flags found in the PR body + */ +async function getFlagsFromPrBody(): Promise { + let body: string; + try { + body = await fs.readFile(PR_BODY_FILEPATH, 'utf8'); + } catch (error) { + if ( + error instanceof Error && + hasProperty(error, 'code') && + error.code === 'ENOENT' + ) { + console.debug('No pr-body.txt, ignoring flags'); + return {}; + } + throw error; + } + + return regexSearchForFlags(body, 'PR body'); +} + +/** + * Get flags from the Git message if they are set + * + * To use this feature, add a line to your commit message like: + * `flags = {"sentry": {"tracesSampleRate": 0.1}}` + * (must be valid JSON) + * + * @returns Any manifest flags found in the commit message + */ +async function getFlagsFromGitMessage(): Promise { + const gitMessage = ( + await exec(`git show --format='%B' --no-patch "HEAD"`) + ).toString(); + + return regexSearchForFlags(gitMessage, 'git message'); +} + +/** + * Get any manifest flags found in the PR body and git message. + * + * @returns Any manifest flags found + */ +export async function getManifestFlags(): Promise { + const [prBodyFlags, gitMessageFlags] = await Promise.all([ + getFlagsFromPrBody(), + getFlagsFromGitMessage(), + ]); + + return merge(prBodyFlags, gitMessageFlags); +} diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index b06c29b17acf..3ad0d1d149c5 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -184,7 +184,7 @@ async function withFixtures(options, testSuite) { } await mockServer.start(8000); - setManifestFlags(manifestFlags); + await setManifestFlags(manifestFlags); driver = (await buildWebDriver(driverOptions)).driver; webDriver = driver.driver; diff --git a/test/e2e/set-manifest-flags.ts b/test/e2e/set-manifest-flags.ts index 75339250506f..7cb5b0794df6 100644 --- a/test/e2e/set-manifest-flags.ts +++ b/test/e2e/set-manifest-flags.ts @@ -1,7 +1,7 @@ -import { execSync } from 'child_process'; import fs from 'fs'; import { merge } from 'lodash'; import { ManifestFlags } from '../../app/scripts/lib/manifestFlags'; +import { getManifestFlags } from '../../development/lib/get-manifest-flag'; export const folder = `dist/${process.env.SELENIUM_BROWSER}`; @@ -12,86 +12,8 @@ function parseIntOrUndefined(value: string | undefined): number | undefined { return value ? parseInt(value, 10) : undefined; } -/** - * Search a string for `flags = {...}` and return ManifestFlags if it exists - * - * @param str - The string to search - * @param errorType - The type of error to log if parsing fails - * @returns The ManifestFlags object if valid, otherwise undefined - */ -function regexSearchForFlags( - str: string, - errorType: string, -): ManifestFlags | undefined { - // Search str for `flags = {...}` - const flagsMatch = str.match(/flags\s*=\s*(\{.*\})/u); - - if (flagsMatch) { - try { - // Get 1st capturing group from regex - return JSON.parse(flagsMatch[1]); - } catch (error) { - console.error( - `Error parsing flags from ${errorType}, ignoring flags\n`, - error, - ); - } - } - - return undefined; -} - -/** - * Add flags from the GitHub PR body if they are set - * - * To use this feature, add a line to your PR body like: - * `flags = {"sentry": {"tracesSampleRate": 0.1}}` - * (must be valid JSON) - * - * @param flags - The flags object to add to - */ -function addFlagsFromPrBody(flags: ManifestFlags) { - let body; - - try { - body = fs.readFileSync('changed-files/pr-body.txt', 'utf8'); - } catch (error) { - console.debug('No pr-body.txt, ignoring flags'); - return; - } - - const newFlags = regexSearchForFlags(body, 'PR body'); - - if (newFlags) { - // Use lodash merge to do a deep merge (spread operator is shallow) - merge(flags, newFlags); - } -} - -/** - * Add flags from the Git message if they are set - * - * To use this feature, add a line to your commit message like: - * `flags = {"sentry": {"tracesSampleRate": 0.1}}` - * (must be valid JSON) - * - * @param flags - The flags object to add to - */ -function addFlagsFromGitMessage(flags: ManifestFlags) { - const gitMessage = execSync( - `git show --format='%B' --no-patch "HEAD"`, - ).toString(); - - const newFlags = regexSearchForFlags(gitMessage, 'git message'); - - if (newFlags) { - // Use lodash merge to do a deep merge (spread operator is shallow) - merge(flags, newFlags); - } -} - // Alter the manifest with CircleCI environment variables and custom flags -export function setManifestFlags(flags: ManifestFlags = {}) { +export async function setManifestFlags(flags: ManifestFlags = {}) { if (process.env.CIRCLECI) { flags.circleci = { enabled: true, @@ -104,8 +26,8 @@ export function setManifestFlags(flags: ManifestFlags = {}) { ), }; - addFlagsFromPrBody(flags); - addFlagsFromGitMessage(flags); + const additionalManifestFlags = await getManifestFlags(); + merge(flags, additionalManifestFlags); // Set `flags.sentry.forceEnable` to true by default if (flags.sentry === undefined) {