diff --git a/e2e/tests/analysis.test.ts b/e2e/tests/analysis.test.ts index ef4f921a..9770fe2d 100644 --- a/e2e/tests/analysis.test.ts +++ b/e2e/tests/analysis.test.ts @@ -1,6 +1,14 @@ -import { describe, it, expect, beforeEach } from 'vitest'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; import { TestProject } from '../utils'; import { resetBundleIncrement } from '~/core/builders/vite/plugins'; +import open from 'open'; + +vi.mock('open'); +const openMock = vi.mocked(open); + +vi.mock('ci-info', () => ({ + isCI: false, +})); describe('Analysis', () => { beforeEach(() => { @@ -89,4 +97,24 @@ describe('Analysis', () => { expect(await project.fileExists('stats/bundle-0.json')).toBe(true); expect(await project.fileExists('stats/bundle-1.json')).toBe(true); }); + + it('should open the stats in the browser when requested', async () => { + const project = new TestProject(); + project.addFile('entrypoints/popup.html'); + project.addFile('entrypoints/options.html'); + project.addFile( + 'entrypoints/background.ts', + 'export default defineBackground(() => {});', + ); + + await project.build({ + analysis: { + enabled: true, + open: true, + }, + }); + + expect(openMock).toBeCalledTimes(1); + expect(openMock).toBeCalledWith(project.resolvePath('stats.html')); + }); }); diff --git a/package.json b/package.json index 22bfefb4..cb27bcbc 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "c12": "^1.5.1", "cac": "^6.7.14", "chokidar": "^3.5.3", + "ci-info": "^4.0.0", "consola": "^3.2.3", "defu": "^6.1.3", "dequal": "^2.0.3", @@ -134,6 +135,7 @@ "natural-compare": "^1.4.0", "normalize-path": "^3.0.0", "nypm": "^0.3.6", + "open": "^10.1.0", "ora": "^7.0.1", "picocolors": "^1.0.0", "prompts": "^2.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6470259..9e0733b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: chokidar: specifier: ^3.5.3 version: 3.6.0 + ci-info: + specifier: ^4.0.0 + version: 4.0.0 consola: specifier: ^3.2.3 version: 3.2.3 @@ -92,6 +95,9 @@ importers: nypm: specifier: ^0.3.6 version: 0.3.6 + open: + specifier: ^10.1.0 + version: 10.1.0 ora: specifier: ^7.0.1 version: 7.0.1 @@ -1747,6 +1753,13 @@ packages: run-applescript: 5.0.0 dev: false + /bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + dependencies: + run-applescript: 7.0.0 + dev: false + /bundle-require@4.0.1(esbuild@0.19.8): resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1894,6 +1907,11 @@ packages: engines: {node: '>=8'} dev: false + /ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + engines: {node: '>=8'} + dev: false + /citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} dependencies: @@ -2151,6 +2169,11 @@ packages: untildify: 4.0.0 dev: false + /default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} + dev: false + /default-browser@4.0.0: resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} engines: {node: '>=14.16'} @@ -2161,6 +2184,14 @@ packages: titleize: 3.0.0 dev: false + /default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + dev: false + /defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} dependencies: @@ -3928,6 +3959,16 @@ packages: dependencies: mimic-fn: 4.0.0 + /open@10.1.0: + resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} + engines: {node: '>=18'} + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 3.1.0 + dev: false + /open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} @@ -4455,6 +4496,11 @@ packages: execa: 5.1.1 dev: false + /run-applescript@7.0.0: + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + engines: {node: '>=18'} + dev: false + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: diff --git a/src/cli/commands.ts b/src/cli/commands.ts index b8133c10..d087c360 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -57,6 +57,7 @@ cli .option('--mv3', 'target manifest v3') .option('--mv2', 'target manifest v2') .option('--analyze', 'visualize extension bundle') + .option('--analyze-open', 'automatically open stats.html in browser') .action( wrapAction(async (root, flags) => { await build({ @@ -66,7 +67,12 @@ cli manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : undefined, configFile: flags.config, debug: flags.debug, - analysis: flags.analyze ? { enabled: true } : undefined, + analysis: flags.analyze + ? { + enabled: true, + open: flags.analyzeOpen, + } + : undefined, filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'), }); }), diff --git a/src/core/utils/building/internal-build.ts b/src/core/utils/building/internal-build.ts index 412694f1..f0fc0ee7 100644 --- a/src/core/utils/building/internal-build.ts +++ b/src/core/utils/building/internal-build.ts @@ -18,6 +18,7 @@ import { import consola from 'consola'; import { wxt } from '../../wxt'; import { mergeJsonOutputs } from '@aklinker1/rollup-plugin-visualizer'; +import { isCI } from 'ci-info'; /** * Builds the extension based on an internal config. No more config discovery is performed, the @@ -81,6 +82,15 @@ export async function internalBuild(): Promise { wxt.logger.info( `Analysis complete:\n ${pc.gray('└─')} ${pc.yellow(statsPath)}`, ); + if (wxt.config.analysis.open) { + if (isCI) { + wxt.logger.debug(`Skipped opening ${pc.yellow(statsPath)} in CI`); + } else { + wxt.logger.info(`Opening ${pc.yellow(statsPath)} in browser...`); + const { default: open } = await import('open'); + open(wxt.config.analysis.outputFile); + } + } } return output; diff --git a/src/core/utils/building/resolve-config.ts b/src/core/utils/building/resolve-config.ts index 9cfc090f..b2090812 100644 --- a/src/core/utils/building/resolve-config.ts +++ b/src/core/utils/building/resolve-config.ts @@ -149,6 +149,7 @@ export async function resolveConfig( }, analysis: { enabled: mergedConfig.analysis?.enabled ?? false, + open: mergedConfig.analysis?.open ?? false, template: mergedConfig.analysis?.template ?? 'treemap', outputFile: analysisOutputFile, outputDir: analysisOutputDir, diff --git a/src/core/utils/testing/fake-objects.ts b/src/core/utils/testing/fake-objects.ts index 222e9a45..12ffe57b 100644 --- a/src/core/utils/testing/fake-objects.ts +++ b/src/core/utils/testing/fake-objects.ts @@ -273,6 +273,7 @@ export const fakeResolvedConfig = fakeObjectCreator(() => { server: mock(), analysis: { enabled: false, + open: false, template: 'treemap', outputFile: fakeFile(), outputDir: fakeDir(), diff --git a/src/types/index.ts b/src/types/index.ts index 63023011..f24ad4f5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -229,6 +229,12 @@ export interface InlineConfig { * @default false */ enabled?: boolean; + /** + * Set to true to automatically open the `stats.html` file when the build is finished. When building in CI, the browser will never open. + * + * @default false + */ + open?: boolean; /** * When running `wxt build --analyze` or setting `analysis.enabled` to true, customize how the * bundle will be visualized. See @@ -1033,6 +1039,7 @@ export interface ResolvedConfig { transformManifest: (manifest: Manifest.WebExtensionManifest) => void; analysis: { enabled: boolean; + open: boolean; template: NonNullable; /** Absolute file path to the `stats.html` file */ outputFile: string;