happo 6.0.3 → 6.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{cancelJob-6IYR4YZT.js → cancelJob-GCTVJGYB.js} +3 -3
- package/dist/cli/{chunk-TXOXC6WO.js → chunk-DYCU4INF.js} +2 -2
- package/dist/cli/{chunk-TXOXC6WO.js.map → chunk-DYCU4INF.js.map} +1 -1
- package/dist/cli/{chunk-G63DXNAV.js → chunk-TCHC2O3E.js} +2 -2
- package/dist/cli/{chunk-VB6YWDBQ.js → chunk-TPF2272Y.js} +2 -2
- package/dist/cli/createAsyncComparison-5X5OBHF2.js +9 -0
- package/dist/cli/{createAsyncReport-WFZS224O.js → createAsyncReport-ZIO2Q6SS.js} +3 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +65 -28
- package/dist/cli/index.js.map +2 -2
- package/dist/cli/package-CB7WD7JO.js +7 -0
- package/dist/cli/{prepareSnapRequests-APQNWGR6.js → prepareSnapRequests-NPYZG2AI.js} +3 -3
- package/dist/cli/{startJob-ELIGC4S6.js → startJob-T3PWEYOP.js} +3 -3
- package/dist/cli/{wrapper-SK4CQB2Y.js → wrapper-WKKUKM7T.js} +7 -5
- package/dist/cli/{wrapper-SK4CQB2Y.js.map → wrapper-WKKUKM7T.js.map} +2 -2
- package/dist/config/loadConfig.d.ts +3 -1
- package/dist/config/loadConfig.d.ts.map +1 -1
- package/dist/cypress/task.js +55 -18
- package/dist/cypress/task.js.map +2 -2
- package/dist/e2e/wrapper.d.ts.map +1 -1
- package/dist/playwright/index.js +55 -18
- package/dist/playwright/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/cli/createAsyncComparison-37UCMTAK.js +0 -9
- package/dist/cli/package-LV7GRXKC.js +0 -7
- /package/dist/cli/{cancelJob-6IYR4YZT.js.map → cancelJob-GCTVJGYB.js.map} +0 -0
- /package/dist/cli/{chunk-G63DXNAV.js.map → chunk-TCHC2O3E.js.map} +0 -0
- /package/dist/cli/{chunk-VB6YWDBQ.js.map → chunk-TPF2272Y.js.map} +0 -0
- /package/dist/cli/{createAsyncComparison-37UCMTAK.js.map → createAsyncComparison-5X5OBHF2.js.map} +0 -0
- /package/dist/cli/{createAsyncReport-WFZS224O.js.map → createAsyncReport-ZIO2Q6SS.js.map} +0 -0
- /package/dist/cli/{package-LV7GRXKC.js.map → package-CB7WD7JO.js.map} +0 -0
- /package/dist/cli/{prepareSnapRequests-APQNWGR6.js.map → prepareSnapRequests-NPYZG2AI.js.map} +0 -0
- /package/dist/cli/{startJob-ELIGC4S6.js.map → startJob-T3PWEYOP.js.map} +0 -0
package/dist/cli/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/cli/index.ts", "../../src/config/loadConfig.ts", "../../src/environment/index.ts", "../../src/cli/telemetry.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport path from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport type { ConfigWithDefaults } from '../config/index.ts';\nimport { findConfigFile, loadConfigFile } from '../config/loadConfig.ts';\nimport type { EnvironmentResult } from '../environment/index.ts';\nimport resolveEnvironment from '../environment/index.ts';\nimport type { Logger } from '../isomorphic/types.ts';\nimport type { Reporter } from './telemetry.ts';\nimport { createReporter } from './telemetry.ts';\n\nasync function getVersion() {\n const packageJson = await import('../../package.json', {\n with: { type: 'json' },\n });\n return packageJson.default.version;\n}\n\nfunction parseDashdashCommandParts(\n rawArgs: Array<string>,\n): Array<string> | undefined {\n const dashdashIndex = rawArgs.indexOf('--');\n if (dashdashIndex === -1) {\n return undefined;\n }\n return rawArgs.slice(dashdashIndex + 1);\n}\n\nfunction parseRawArgs(rawArgs: Array<string>) {\n const parsedArgs = parseArgs({\n args: rawArgs,\n\n options: {\n version: {\n type: 'boolean',\n short: 'v',\n },\n\n help: {\n type: 'boolean',\n short: 'h',\n },\n\n config: {\n type: 'string',\n short: 'c',\n },\n\n baseBranch: {\n type: 'string',\n },\n\n link: {\n type: 'string',\n },\n\n message: {\n type: 'string',\n },\n\n authorEmail: {\n type: 'string',\n },\n\n afterSha: {\n type: 'string',\n },\n\n beforeSha: {\n type: 'string',\n },\n\n fallbackShas: {\n type: 'string',\n },\n\n fallbackShasCount: {\n type: 'string',\n },\n\n notify: {\n type: 'string',\n },\n\n nonce: {\n type: 'string',\n },\n\n githubToken: {\n type: 'string',\n },\n },\n\n allowPositionals: true,\n });\n\n return {\n ...parsedArgs,\n dashdashCommandParts: parseDashdashCommandParts(rawArgs),\n };\n}\n\nconst helpText = `Happo ${await getVersion()}\nUsage: happo [options]\n\nCommands:\n <default> Run happo tests\n finalize Finalize happo report for Cypress/Playwright tests running in parallel\n\nOptions:\n --config Path to happo config file\n --version Show version number\n --help Show help text\n --baseBranch <branch> Base branch to use for comparison (default: 'origin/main')\n --link <url> URL to contextualize the comparison (default: auto-detected from CI environment)\n --message <message> Message to associate with the comparison (default: auto-detected from CI environment)\n --authorEmail <email> Email address of the author of the comparison (default: auto-detected from CI environment)\n --afterSha <sha> \"After\" SHA to use for comparison (default: auto-detected from CI environment, or HEAD SHA if not set)\n --beforeSha <sha> \"Before\" SHA to use for comparison (default: auto-detected from CI environment)\n --beforeShaTagMatcher <matcher> git tag matcher to use for \"before\" SHA resolution\n --fallbackShas <shas> Space-, newline- or comma-separated list of fallback shas for compare calls (default: auto-detected from CI environment)\n --fallbackShasCount <count> Number of fallback shas to use for compare calls (default: 50)\n --notify <emails> One or more (comma-separated) email addresses to notify with results\n --nonce <nonce> Nonce to use for Cypress/Playwright comparison\n --githubToken <token> GitHub token to use for posting Happo statuses as comments. Use in combination with the \\`githubApiUrl\\` configuration option. (default: auto-detected from environment)\n\nExamples:\n happo\n\n happo --config path/to/happo.config.ts\n happo --baseBranch origin/long-lived-branch\n happo --link https://github.com/happo/happo/pull/123\n happo --message \"Add new feature\"\n happo --notify me@example.com,you@example.com\n happo --nonce my-unique-nonce\n happo --githubToken {{ secrets.GITHUB_TOKEN }}\n\n happo --version\n happo --help\n\n happo -- playwright test\n\n happo finalize\n happo finalize --nonce my-unique-nonce\n `;\n\nfunction makeAbsolute(configFilePath: string): string {\n if (configFilePath.startsWith('.')) {\n return path.resolve(process.cwd(), configFilePath);\n }\n return configFilePath;\n}\n\nfunction installErrorHandlers(reporter: Reporter, logger: Logger) {\n const unhandledRejectionHandler: NodeJS.UnhandledRejectionListener = (reason) => {\n if (reason instanceof Error) {\n reporter.captureException(reason);\n logger.error(reason.stack || reason.message || String(reason));\n } else {\n reporter.captureException(reason);\n logger.error(`Unhandled rejection (non-Error value): ${String(reason)}`);\n }\n\n process.exitCode = 1;\n return;\n };\n\n const uncaughtExceptionHandler: NodeJS.UncaughtExceptionListener = (error) => {\n reporter.captureException(error);\n logger.error(error.stack || error.message || String(error));\n process.exitCode = 1;\n };\n\n process.on('unhandledRejection', unhandledRejectionHandler);\n process.on('uncaughtException', uncaughtExceptionHandler);\n\n return () => {\n process.removeListener('unhandledRejection', unhandledRejectionHandler);\n process.removeListener('uncaughtException', uncaughtExceptionHandler);\n };\n}\n\nexport async function main(\n rawArgs: Array<string> = process.argv,\n logger: Logger = console,\n): Promise<void> {\n const reporter = createReporter();\n const uninstallErrorHandlers = installErrorHandlers(reporter, logger);\n\n try {\n const args = parseRawArgs(rawArgs.slice(2));\n\n if (args.values.version) {\n // --version\n logger.log(await getVersion());\n return;\n }\n\n if (args.values.help) {\n // --help\n logger.log(helpText);\n return;\n }\n\n // Get config file path (use --config if provided, otherwise find default)\n const configFilePath = makeAbsolute(args.values.config || findConfigFile());\n const config = await loadConfigFile(configFilePath);\n const environment = await resolveEnvironment(args.values);\n\n // Handle positional arguments (commands)\n const command = args.positionals[0];\n\n if (args.dashdashCommandParts) {\n await handleE2ECommand(\n config,\n environment,\n args.dashdashCommandParts,\n configFilePath,\n logger,\n );\n return;\n }\n\n if (command === 'finalize') {\n await handleFinalizeCommand(config, environment, logger);\n return;\n }\n\n if (command === undefined) {\n await handleDefaultCommand(config, environment, logger);\n return;\n }\n\n logger.error(`Unknown command: ${command}\\n`);\n logger.error(helpText);\n process.exitCode = 1;\n } catch (error) {\n await reporter.captureException(error);\n logger.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n } finally {\n uninstallErrorHandlers();\n }\n}\n\nasync function handleDefaultCommand(\n config: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n): Promise<void> {\n logger.log('Running happo tests...');\n\n const [startJob, createAsyncComparison, createAsyncReport, prepareSnapRequests] =\n await Promise.all([\n (await import('../network/startJob.ts')).default,\n (await import('../network/createAsyncComparison.ts')).default,\n (await import('../network/createAsyncReport.ts')).default,\n (await import('../network/prepareSnapRequests.ts')).default,\n ]);\n\n // Tell Happo that we are about to run a job\n await startJob(config, environment, logger);\n\n try {\n // Prepare the snap requests for the job. This includes bundling static\n // assets and uploading them.\n const snapRequestIds = await prepareSnapRequests(config);\n\n // Put together a report from the snap requests.\n const asyncReport = await createAsyncReport(\n snapRequestIds,\n config,\n environment,\n logger,\n );\n\n // Create an async comparison.\n logger.log(`[HAPPO] Async report URL: ${asyncReport.url}`);\n if (environment.beforeSha !== environment.afterSha) {\n const asyncComparison = await createAsyncComparison(\n config,\n environment,\n logger,\n );\n logger.log(`[HAPPO] Async comparison URL: ${asyncComparison.compareUrl}`);\n }\n } catch (e) {\n logger.error(e instanceof Error ? e.message : String(e), e);\n const cancelJob = (await import('../network/cancelJob.ts')).default;\n await cancelJob('failure', config, environment, logger);\n process.exitCode = 1;\n return;\n }\n}\n\nasync function handleFinalizeCommand(\n config: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n): Promise<void> {\n logger.log('Finalizing happo report...');\n logger.log('Config:', config);\n logger.log('Environment:', environment);\n\n try {\n const finalizeAll = (await import('../e2e/wrapper.ts')).finalizeAll;\n await finalizeAll({ happoConfig: config, environment, logger });\n } catch (e) {\n logger.error(e instanceof Error ? e.message : String(e), e);\n process.exitCode = 1;\n return;\n }\n process.exitCode = 0;\n return;\n}\n\nconst E2E_INTEGRATION_TYPES = ['cypress', 'playwright'];\n\nasync function handleE2ECommand(\n config: ConfigWithDefaults,\n environment: EnvironmentResult,\n dashdashCommandParts: Array<string>,\n configFilePath: string,\n logger: Logger,\n): Promise<void> {\n if (!E2E_INTEGRATION_TYPES.includes(config.integration.type)) {\n logger.error(\n `Unsupported integration type used for e2e command: ${config.integration.type}. Supported integration types for e2e are: ${E2E_INTEGRATION_TYPES.join(', ')}`,\n );\n process.exitCode = 1;\n return;\n }\n\n if (!dashdashCommandParts || dashdashCommandParts.length === 0) {\n logger.error('Missing command for e2e action');\n logger.error(helpText);\n process.exitCode = 1;\n return;\n }\n\n logger.log('Setting up happo wrapper for Cypress and Playwright...');\n logger.log('Config:', config);\n logger.log('Environment:', environment);\n logger.log('Dashdash command parts:', dashdashCommandParts);\n\n const runWithWrapper = (await import('../e2e/wrapper.ts')).default;\n const exitCode = await runWithWrapper(\n dashdashCommandParts,\n config,\n environment,\n logger,\n configFilePath,\n );\n process.exitCode = exitCode;\n}\n\nif (import.meta.main) {\n await main();\n}\n", "import fs from 'node:fs';\n\nimport { any as findAny } from 'empathic/find';\n\nimport type { ConfigWithDefaults, TargetWithDefaults } from './index.ts';\n\nconst CONFIG_FILENAMES = [\n 'happo.config.js',\n 'happo.config.mjs',\n 'happo.config.cjs',\n 'happo.config.ts',\n 'happo.config.mts',\n 'happo.config.cts',\n];\n\nexport function findConfigFile(): string {\n if (process.env.HAPPO_CONFIG_FILE) {\n return process.env.HAPPO_CONFIG_FILE;\n }\n\n const configFilePath = findAny(CONFIG_FILENAMES, { cwd: process.cwd() });\n\n if (!configFilePath) {\n throw new Error(\n 'Happo config file could not be found. Please create a config file in the root of your project.',\n );\n }\n\n return configFilePath;\n}\n\nfunction validateConfig(config: ConfigWithDefaults) {\n if (!config.apiKey) {\n throw new Error(\n 'Missing `apiKey` in your Happo config. Reference yours at https://happo.io/settings',\n );\n }\n\n if (!config.apiSecret) {\n throw new Error(\n 'Missing `apiSecret` in your Happo config. Reference yours at https://happo.io/settings',\n );\n }\n}\n\nexport async function loadConfigFile(\n configFilePath: string,\n): Promise<ConfigWithDefaults> {\n try {\n const stats = await fs.promises.stat(configFilePath);\n if (!stats.isFile()) {\n throw new Error(`Happo config file path is not a file: ${configFilePath}`);\n }\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n throw new Error(`Happo config file could not be found: ${configFilePath}`);\n }\n\n throw error;\n }\n\n const config = await import(configFilePath);\n\n if (!config.default.targets) {\n config.default.targets = {\n chrome: {\n type: 'chrome',\n viewport: '1024x768',\n },\n };\n }\n\n if (!config.default.integration) {\n config.default.integration = {\n type: 'storybook',\n };\n }\n\n const allTargets = Object.values(config.default.targets);\n for (const target of allTargets as Array<TargetWithDefaults>) {\n target.viewport = target.viewport || '1024x768';\n target.freezeAnimations = target.freezeAnimations || 'last-frame';\n target.prefersReducedMotion = target.prefersReducedMotion ?? true;\n }\n\n const configWithDefaults = {\n endpoint: 'https://happo.io',\n githubApiUrl: 'https://api.github.com',\n targets: allTargets,\n ...config.default,\n };\n\n validateConfig(configWithDefaults);\n\n return configWithDefaults;\n}\n", "import { spawnSync } from 'node:child_process';\nimport crypto, { randomBytes } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\n\ninterface GitHubEvent {\n pull_request?: {\n html_url: string;\n title: string;\n base: {\n sha: string;\n };\n head: {\n sha: string;\n };\n };\n head_commit?: {\n url: string;\n };\n merge_group?: {\n head_sha: string;\n base_sha: string;\n };\n repository?: {\n html_url: string;\n };\n before?: string;\n after?: string;\n}\n\ninterface CLIArgs {\n baseBranch?: string;\n afterSha?: string;\n beforeSha?: string;\n message?: string;\n link?: string;\n authorEmail?: string;\n beforeShaTagMatcher?: string;\n notify?: string;\n fallbackShas?: string;\n fallbackShasCount?: string;\n nonce?: string;\n githubToken?: string;\n}\n\nexport interface EnvironmentResult {\n link: string | undefined;\n message: string | undefined;\n authorEmail: string | undefined;\n beforeSha: string;\n afterSha: string;\n nonce: string | undefined;\n debugMode: boolean;\n notify: string | undefined;\n fallbackShas: Array<string> | undefined;\n githubToken: string | undefined;\n}\n\nconst envKeys: ReadonlyArray<string> = [\n 'BUILD_REPOSITORY_URI',\n 'BUILD_SOURCEVERSION',\n 'CIRCLE_PROJECT_REPONAME',\n 'CIRCLE_PROJECT_USERNAME',\n 'CIRCLE_SHA1',\n 'CI_PULL_REQUEST',\n 'GITHUB_EVENT_PATH',\n 'GITHUB_SERVER_URL',\n 'GITHUB_SHA',\n 'HAPPO_DEBUG',\n 'SYSTEM_PULLREQUEST_PULLREQUESTID',\n 'SYSTEM_PULLREQUEST_SOURCEBRANCH',\n 'SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI',\n 'SYSTEM_PULLREQUEST_TARGETBRANCH',\n 'TRAVIS_COMMIT',\n 'TRAVIS_COMMIT_RANGE',\n 'TRAVIS_PULL_REQUEST',\n 'TRAVIS_PULL_REQUEST_SHA',\n 'TRAVIS_REPO_SLUG',\n];\n\nasync function resolveGithubEvent(GITHUB_EVENT_PATH: string): Promise<GitHubEvent> {\n try {\n const fs = await import('node:fs/promises');\n const content = await fs.readFile(GITHUB_EVENT_PATH, 'utf8');\n return JSON.parse(content);\n } catch (e) {\n throw new Error(\n `Failed to load GitHub event from the GITHUB_EVENT_PATH environment variable: ${JSON.stringify(GITHUB_EVENT_PATH)}`,\n { cause: e },\n );\n }\n}\n\nasync function resolveLink(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n): Promise<string | undefined> {\n if (cliArgs.link) {\n // Validate the link\n let parsed: URL;\n try {\n parsed = new URL(cliArgs.link);\n } catch (e) {\n throw new TypeError(\n `link must be a valid http/https URL. Invalid URL: '${cliArgs.link}'`,\n { cause: e },\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new TypeError(\n `link must be a valid http/https URL. Invalid protocol: '${parsed.protocol}' (from '${cliArgs.link}')`,\n );\n }\n\n return cliArgs.link;\n }\n\n const {\n BUILD_REPOSITORY_URI,\n BUILD_SOURCEVERSION,\n\n // https://circleci.com/docs/reference/variables/\n CIRCLE_PROJECT_REPONAME,\n CIRCLE_PROJECT_USERNAME,\n CIRCLE_PULL_REQUEST,\n CIRCLE_SHA1,\n\n CI_PULL_REQUEST,\n\n // https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables\n GITHUB_EVENT_PATH,\n GITHUB_SHA,\n\n SYSTEM_PULLREQUEST_PULLREQUESTID,\n SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI,\n\n // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables\n TRAVIS_COMMIT,\n TRAVIS_PULL_REQUEST,\n TRAVIS_REPO_SLUG,\n } = env;\n\n if (CI_PULL_REQUEST) {\n // Circle CI\n return CI_PULL_REQUEST;\n }\n\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.html_url;\n }\n if (ghEvent.head_commit) {\n return ghEvent.head_commit.url;\n }\n if (ghEvent.merge_group && ghEvent.repository) {\n return `${ghEvent.repository.html_url}/commit/${ghEvent.merge_group.head_sha}`;\n }\n if (GITHUB_SHA && ghEvent.repository) {\n return `${ghEvent.repository.html_url}/commit/${GITHUB_SHA}`;\n }\n }\n\n if (SYSTEM_PULLREQUEST_PULLREQUESTID && SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI) {\n return `${SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI}/pullrequest/${SYSTEM_PULLREQUEST_PULLREQUESTID}`.replace(\n /[^/]+@/,\n '',\n );\n }\n\n if (BUILD_REPOSITORY_URI && BUILD_SOURCEVERSION) {\n return `${BUILD_REPOSITORY_URI}/commit/${BUILD_SOURCEVERSION}`.replace(\n /[^/]+@/,\n '',\n );\n }\n\n const githubBase = 'https://github.com';\n\n if (TRAVIS_REPO_SLUG && TRAVIS_PULL_REQUEST) {\n return `${githubBase}/${TRAVIS_REPO_SLUG}/pull/${TRAVIS_PULL_REQUEST}`;\n }\n\n if (TRAVIS_REPO_SLUG && TRAVIS_COMMIT) {\n return `${githubBase}/${TRAVIS_REPO_SLUG}/commit/${TRAVIS_COMMIT}`;\n }\n\n if (CIRCLE_PULL_REQUEST) {\n return CIRCLE_PULL_REQUEST;\n }\n\n if (CIRCLE_PROJECT_USERNAME && CIRCLE_PROJECT_REPONAME && CIRCLE_SHA1) {\n return `${githubBase}/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/commit/${CIRCLE_SHA1}`;\n }\n\n return undefined;\n}\n\nasync function resolveAuthorEmail(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n): Promise<string | undefined> {\n if (cliArgs.authorEmail) {\n return cliArgs.authorEmail;\n }\n\n const { GITHUB_EVENT_PATH } = env;\n\n if (GITHUB_EVENT_PATH) {\n // const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n // TODO: do something with the github event\n }\n\n const res = spawnSync('git', ['show', '-s', '--format=%ae'], {\n encoding: 'utf8',\n });\n\n if (res.status !== 0) {\n return undefined;\n }\n\n return res.stdout.trim();\n}\n\nasync function resolveMessage(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n afterSha: string,\n): Promise<string | undefined> {\n if (cliArgs.message) {\n return cliArgs.message;\n }\n\n const { GITHUB_EVENT_PATH } = env;\n\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.title;\n }\n }\n\n const res = spawnSync('git', ['log', '-1', '--pretty=%s', afterSha], {\n encoding: 'utf8',\n });\n\n if (res.status !== 0) {\n return undefined;\n }\n\n const message = res.stdout.split('\\n')[0];\n\n if (!message) {\n return undefined;\n }\n\n return message;\n}\n\nfunction resolveShaFromTagMatcher(tagMatcher: string): string | undefined {\n const res = spawnSync(\n 'git',\n ['tag', '--list', tagMatcher, '--sort', 'refname', '--no-contains'],\n {\n encoding: 'utf8',\n },\n );\n\n if (res.status !== 0) {\n throw new Error(\n `Failed to list git tags when matching against --beforeShaTagMatcher '${tagMatcher}'. Error: ${res.stderr}`,\n );\n }\n\n const rawAllTags = res.stdout.trim();\n if (!rawAllTags.length) {\n return undefined;\n }\n\n const allTags = rawAllTags.split('\\n');\n const tag = allTags.at(-1);\n\n if (!tag) {\n throw new Error('No tag found matching the pattern');\n }\n\n const commitRes = spawnSync('git', ['rev-list', '-n', '1', tag], {\n encoding: 'utf8',\n });\n\n if (commitRes.status !== 0) {\n throw new Error(\n `Failed to resolve commit sha from tag \"${tag}\". Error: ${res.stderr}`,\n );\n }\n\n return commitRes.stdout.trim();\n}\n\nasync function resolveBeforeSha(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n afterSha: string,\n): Promise<string | undefined> {\n if (cliArgs.beforeSha) {\n return cliArgs.beforeSha;\n }\n\n if (cliArgs.beforeShaTagMatcher) {\n const resolvedSha = resolveShaFromTagMatcher(cliArgs.beforeShaTagMatcher);\n if (resolvedSha) {\n return resolvedSha;\n }\n }\n\n const { TRAVIS_COMMIT_RANGE, GITHUB_EVENT_PATH, SYSTEM_PULLREQUEST_TARGETBRANCH } =\n env;\n\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.base.sha;\n }\n if (ghEvent.merge_group) {\n return ghEvent.merge_group.base_sha;\n }\n return ghEvent.before;\n }\n\n if (TRAVIS_COMMIT_RANGE) {\n const [first] = TRAVIS_COMMIT_RANGE.split('...');\n return first;\n }\n\n let baseAzureBranch;\n if (SYSTEM_PULLREQUEST_TARGETBRANCH) {\n baseAzureBranch = [\n 'origin',\n SYSTEM_PULLREQUEST_TARGETBRANCH.split('/').toReversed()[0],\n ].join('/');\n }\n\n const baseBranch = cliArgs.baseBranch || baseAzureBranch || 'origin/main';\n const res = spawnSync('git', ['merge-base', baseBranch, afterSha], {\n encoding: 'utf8',\n });\n if (res.status !== 0) {\n console.error(`[HAPPO] Ignored error when resolving base commit: ${res.stderr}`);\n return undefined;\n }\n return res.stdout.split('\\n')[0];\n}\n\nfunction getHeadShaWithLocalChanges(): {\n headSha: string;\n headShaWithLocalChanges: string;\n} {\n const randomSha = randomBytes(20).toString('hex');\n // Get the HEAD sha from the git repo, or if we have local changes, add them to the sha\n const res = spawnSync('git', ['rev-parse', 'HEAD'], {\n encoding: 'utf8',\n });\n if (res.status !== 0) {\n return { headSha: randomSha, headShaWithLocalChanges: randomSha };\n }\n const headSha = res.stdout.split('\\n')[0];\n if (!headSha) {\n return { headSha: randomSha, headShaWithLocalChanges: randomSha };\n }\n\n // Check for local changes\n const diffRes = spawnSync('git', ['diff', 'HEAD'], {\n encoding: 'utf8',\n });\n\n // If git diff fails, return HEAD sha\n if (diffRes.status !== 0) {\n return { headSha, headShaWithLocalChanges: headSha };\n }\n\n const lsRes = spawnSync('git', ['ls-files', '--other', '--exclude-standard'], {\n encoding: 'utf8',\n });\n\n if (lsRes.status !== 0) {\n return { headSha, headShaWithLocalChanges: headSha };\n }\n\n const localChanges = [diffRes.stdout.trim(), lsRes.stdout.trim()];\n\n // Get contents of untracked files\n const untrackedFiles = lsRes.stdout\n .trim()\n .split('\\n')\n .filter((file) => file.trim());\n\n for (const file of untrackedFiles) {\n try {\n const content = readFileSync(file, 'utf8');\n localChanges.push(content);\n } catch {\n // If we can't read the file, include just the filename\n localChanges.push(`${file}:<unreadable>`);\n }\n }\n\n const allChanges = localChanges.join('');\n\n if (!allChanges.trim()) {\n return { headSha, headShaWithLocalChanges: headSha };\n }\n\n // If there are local changes, create a hash that includes both HEAD and the changes\n const headShaWithLocalChanges = crypto\n .createHash('sha256')\n .update(headSha)\n .update(allChanges)\n .digest('hex')\n .slice(0, 40);\n\n return { headSha, headShaWithLocalChanges };\n}\n\nasync function resolveAfterSha(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n): Promise<string | { headSha: string; headShaWithLocalChanges: string }> {\n if (cliArgs.afterSha) {\n return cliArgs.afterSha;\n }\n\n const {\n CIRCLE_SHA1,\n TRAVIS_PULL_REQUEST_SHA,\n TRAVIS_COMMIT,\n GITHUB_EVENT_PATH,\n GITHUB_SHA,\n BUILD_SOURCEVERSION,\n SYSTEM_PULLREQUEST_SOURCEBRANCH,\n CI,\n } = env;\n\n const sha = CIRCLE_SHA1 || TRAVIS_PULL_REQUEST_SHA || TRAVIS_COMMIT;\n\n if (sha) {\n return sha;\n }\n\n if (SYSTEM_PULLREQUEST_SOURCEBRANCH) {\n // azure pull request\n const rawBranchName = SYSTEM_PULLREQUEST_SOURCEBRANCH.split('/').toReversed()[0];\n const res = spawnSync('git', ['rev-parse', `origin/${rawBranchName}`], {\n encoding: 'utf8',\n });\n if (res.status === 0 && res.stdout) {\n const sha = res.stdout.split('\\n')[0];\n if (sha) {\n return sha;\n }\n }\n }\n if (BUILD_SOURCEVERSION) {\n // azure master job\n return BUILD_SOURCEVERSION;\n }\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.head.sha;\n }\n if (ghEvent.merge_group) {\n return ghEvent.merge_group.head_sha;\n }\n if (ghEvent.after) {\n return ghEvent.after;\n }\n if (GITHUB_SHA) {\n return GITHUB_SHA;\n }\n }\n const headShaWithLocalChanges = getHeadShaWithLocalChanges();\n if (CI) {\n return headShaWithLocalChanges.headSha;\n }\n return headShaWithLocalChanges;\n}\n\nfunction resolveFallbackShas(\n cliArgs: CLIArgs,\n beforeSha: string | undefined,\n): Array<string> | undefined {\n if (cliArgs.fallbackShas) {\n return cliArgs.fallbackShas.split(/[,\\s]+/).filter(Boolean);\n }\n\n const fallbackShasCount = cliArgs.fallbackShasCount\n ? Number.parseInt(cliArgs.fallbackShasCount, 10)\n : 50;\n\n if (Number.isNaN(fallbackShasCount)) {\n throw new TypeError(\n `fallbackShasCount must be a number. Invalid value: '${cliArgs.fallbackShasCount}'`,\n );\n }\n\n const res = spawnSync(\n 'git',\n [\n 'log',\n '--format=%H',\n '--first-parent',\n `--max-count=${fallbackShasCount}`,\n `${beforeSha}^`,\n ],\n {\n encoding: 'utf8',\n },\n );\n\n if (res.status !== 0) {\n return undefined;\n }\n\n return res.stdout.split('\\n').filter(Boolean);\n}\n\nfunction getRawEnv(\n env: Record<string, string | undefined>,\n): Record<string, string | undefined> {\n const res: Record<string, string | undefined> = {};\n for (const key of envKeys) {\n res[key] = env[key];\n }\n return res;\n}\n\nexport default async function resolveEnvironment(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined> = process.env,\n): Promise<EnvironmentResult> {\n const debugMode = !!env.HAPPO_DEBUG;\n const afterSha = await resolveAfterSha(cliArgs, env);\n\n const realAfterSha = typeof afterSha === 'string' ? afterSha : afterSha.headSha;\n const afterShaWithLocalChanges =\n typeof afterSha === 'string' ? afterSha : afterSha.headShaWithLocalChanges;\n\n // Resolve the before SHA with the true HEAD SHA\n const [beforeSha, link, authorEmail, message] = await Promise.all([\n resolveBeforeSha(cliArgs, env, realAfterSha),\n resolveLink(cliArgs, env),\n resolveAuthorEmail(cliArgs, env),\n\n // Resolve message with the SHA that includes local changes\n resolveMessage(cliArgs, env, afterShaWithLocalChanges),\n ]);\n\n const nonNullBeforeSha = beforeSha || afterShaWithLocalChanges;\n\n const result = {\n link,\n authorEmail,\n message,\n beforeSha: nonNullBeforeSha,\n afterSha: afterShaWithLocalChanges,\n nonce: cliArgs.nonce,\n debugMode,\n notify: cliArgs.notify,\n fallbackShas: resolveFallbackShas(cliArgs, nonNullBeforeSha),\n githubToken: cliArgs.githubToken,\n };\n\n if (debugMode) {\n console.log('[HAPPO] Raw environment', getRawEnv(env));\n console.log('[HAPPO] Resolved environment', result);\n }\n\n return result;\n}\n", "import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { createInterface } from 'node:readline';\n\nimport { up as findPackage } from 'empathic/package';\n\nimport pkg from '../../package.json' with { type: 'json' };\n\n/**\n * Happo's PUBLIC DSN. Safe to ship.\n */\nconst SENTRY_DSN =\n 'https://3a495ff2101313edb024de73b005398f@o108341.ingest.us.sentry.io/4510341337645056';\n\nconst MAX_STACK_FRAMES = 50;\nconst MAX_FUNCTION_NAME_LENGTH = 120;\n\ntype CI =\n | 'github'\n | 'circleci'\n | 'travis'\n | 'azure'\n | 'buildkite'\n | 'jenkins'\n | 'gitlab'\n | 'bitbucket'\n | 'appveyor'\n | 'drone'\n | 'ci'\n | 'unknown';\n\ntype ErrorPayload = {\n pkg: {\n name: string;\n version: string;\n };\n\n error: {\n name: string;\n message: string;\n stack?: string;\n };\n\n env: string;\n\n /** Detected CI vendor if any */\n ci: CI;\n\n nodeVersion: string;\n platform: string;\n};\n\nexport type ReporterOptions = {\n maxPerMinute?: number; // default 10\n env?: string;\n};\n\nexport interface Reporter {\n captureException(e: unknown): Promise<void>;\n}\n\nexport function detectCI(env: Record<string, string | undefined> = process.env): CI {\n if (env.GITHUB_ACTIONS) {\n return 'github';\n }\n\n if (env.CIRCLECI) {\n return 'circleci';\n }\n\n if (env.TRAVIS) {\n return 'travis';\n }\n\n if (env.TF_BUILD) {\n return 'azure';\n }\n\n if (env.BUILDKITE) {\n return 'buildkite';\n }\n\n if (env.JENKINS_URL) {\n return 'jenkins';\n }\n\n if (env.GITLAB_CI) {\n return 'gitlab';\n }\n\n if (env.BITBUCKET_BUILD_NUMBER) {\n return 'bitbucket';\n }\n\n if (env.APPVEYOR) {\n return 'appveyor';\n }\n\n if (env.DRONE) {\n return 'drone';\n }\n\n if (env.CI) {\n return 'ci';\n }\n\n return 'unknown';\n}\n\nexport function parseDsn(dsn: string): {\n host: string;\n projectId: string;\n key: string;\n protocol: string;\n} | null {\n try {\n // https://{PUBLIC_KEY}@{host}/{project_id}\n const u = new URL(dsn);\n const projectId = u.pathname.replace(/^\\//, '');\n const key = u.username; // public key\n const host = u.host; // includes subdomain + port\n return {\n host,\n projectId,\n key,\n protocol: u.protocol.replace(':', ''),\n };\n } catch {\n return null;\n }\n}\n\nasync function sendToSentry(payload: ErrorPayload) {\n const dsn = parseDsn(SENTRY_DSN);\n\n if (!dsn) {\n return;\n }\n\n // Sentry envelope: https://develop.sentry.dev/sdk/envelopes/\n const now = Date.now();\n\n // Hexadecimal string representing a uuid4 value. The length is exactly 32\n // characters. Dashes are not allowed. Has to be lowercase.\n const eventId = crypto.randomUUID().replaceAll('-', '');\n\n const url = `https://${dsn.host}/api/${dsn.projectId}/envelope/`;\n\n const httpHeaders = {\n 'content-type': 'application/x-sentry-envelope',\n 'x-sentry-auth': [\n 'Sentry sentry_version=7',\n `sentry_key=${dsn.key}`,\n `sentry_client=${pkg.name}@${pkg.version}`,\n ].join(', '),\n };\n\n const memoryInfo = process.memoryUsage();\n\n // Minimal event; we keep fields lean & sanitized\n // https://develop.sentry.dev/sdk/data-model/event-payloads/#required-attributes\n const event = {\n event_id: eventId,\n\n // RFC 3339 format\n timestamp: new Date(now).toISOString(),\n\n // A string representing the platform the SDK is submitting from. This will\n // be used by the Sentry interface to customize various components in the\n // interface.\n platform: 'node',\n\n // Possible values: fatal, error, warning, info, debug\n level: 'error',\n\n logger: 'happo.telemetry.cli',\n\n environment: payload.env ?? 'unknown',\n\n // Release versions must be unique across all projects in the organization.\n release: `${payload.pkg.name}@${payload.pkg.version}`,\n\n tags: {\n ci: payload.ci ?? '',\n },\n\n contexts: {\n runtime: {\n type: 'runtime',\n name: 'node',\n version: payload.nodeVersion ?? '',\n },\n os: {\n type: 'os',\n name: payload.platform ?? '',\n },\n memory_info: {\n type: 'memory_info',\n ...memoryInfo,\n },\n },\n\n exception: {\n values: [\n {\n type: payload.error.name || 'Error',\n value: payload.error.message,\n\n // https://develop.sentry.dev/sdk/data-model/event-payloads/stacktrace/\n stacktrace: payload.error.stack\n ? {\n frames: await parseFrames(payload.error.stack),\n }\n : undefined,\n },\n ],\n },\n };\n\n /**\n * Envelope is ndjson-like chunks\n *\n * Envelope = Headers { \"\\n\" Item } [ \"\\n\" ] ;\n * Item = Headers \"\\n\" Payload ;\n * Payload = { * } ;\n *\n * @see https://develop.sentry.dev/sdk/data-model/envelopes/#headers\n */\n const envelopeHeader = JSON.stringify({\n event_id: eventId,\n dsn: SENTRY_DSN,\n sent_at: new Date(now).toISOString(),\n });\n\n // https://develop.sentry.dev/sdk/data-model/envelopes/#items\n const item = JSON.stringify(event);\n const itemHeader = JSON.stringify({ type: 'event', length: item.length });\n\n const body = [envelopeHeader, itemHeader, item].join('\\n');\n\n try {\n await fetch(url, { method: 'POST', headers: httpHeaders, body });\n } catch {\n // swallow; never throw in library code\n }\n}\n\ninterface SentryFrame {\n function: string;\n raw_function: string;\n abs_path: string | undefined;\n filename: string | undefined;\n lineno: number | undefined;\n colno: number | undefined;\n context_line?: string;\n pre_context?: Array<string>;\n post_context?: Array<string>;\n}\n\n/**\n * Get the package root directory\n */\nfunction getPackageRoot(): string {\n const packageJsonPath = findPackage({ cwd: import.meta.dirname });\n\n if (!packageJsonPath) {\n // Fallback to relative path if empathic can't find it\n return path.resolve(import.meta.dirname, '../..');\n }\n\n return path.dirname(packageJsonPath);\n}\n\n/**\n * Check if a file path is part of this package\n */\nfunction isFileInThisPackage(filePath: string): boolean {\n // Skip node: internal modules\n if (filePath.startsWith('node:')) {\n return false;\n }\n\n try {\n const packageRoot = getPackageRoot();\n // Remove file:// prefix if present\n const cleanPath = filePath.replace(/^file:\\/\\//, '');\n const resolvedPath = path.resolve(cleanPath);\n return resolvedPath.startsWith(packageRoot + path.sep);\n } catch {\n return false;\n }\n}\n\n/**\n * Read context lines from a source file around a given line number\n * Only reads files that are part of this package\n */\nasync function readContextLines(\n filePath: string,\n lineNumber: number,\n contextLines: number = 5,\n): Promise<{\n context_line?: string;\n pre_context?: Array<string>;\n post_context?: Array<string>;\n}> {\n // Only read context for files in this package. This is to avoid reading\n // context from external files that may contain sensitive information.\n if (!isFileInThisPackage(filePath)) {\n return {};\n }\n\n try {\n // Remove file:// prefix if present\n const cleanPath = filePath.replace(/^file:\\/\\//, '');\n\n // Line numbers are 1-indexed in stack traces, but arrays are 0-indexed\n const lineIndex = lineNumber - 1;\n\n if (lineIndex < 0) {\n return {};\n }\n\n const startLine = Math.max(0, lineIndex - contextLines);\n const endLine = lineIndex + contextLines + 1;\n\n const pre_context: Array<string> = [];\n let context_line: string | undefined;\n const post_context: Array<string> = [];\n\n const fileStream = fs.createReadStream(cleanPath, { encoding: 'utf8' });\n const rl = createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n let currentLine = 0;\n\n for await (const line of rl) {\n if (currentLine < startLine) {\n // Skip lines before our context window\n currentLine++;\n continue;\n }\n\n if (currentLine === lineIndex) {\n context_line = line;\n } else if (currentLine < lineIndex) {\n pre_context.push(line);\n } else if (currentLine < endLine) {\n post_context.push(line);\n } else {\n // We've read all the lines we need\n break;\n }\n\n currentLine++;\n }\n\n // Check if we found the target line\n if (context_line === undefined) {\n return {};\n }\n\n const result: {\n context_line: string;\n pre_context?: Array<string>;\n post_context?: Array<string>;\n } = {\n context_line,\n };\n\n if (pre_context.length > 0) {\n result.pre_context = pre_context;\n }\n\n if (post_context.length > 0) {\n result.post_context = post_context;\n }\n\n return result;\n } catch {\n // File might not exist, might not be readable, etc. Best-effort only.\n return {};\n }\n}\n\n/**\n * Convert an error stack to Sentry frames (best-effort)\n */\nexport async function parseFrames(\n stack: string,\n cwd: string = process.cwd(),\n): Promise<Array<SentryFrame>> {\n // Node stack lines like: \" at func (file:///absolute/path/to/file.js:10:5)\"\n const stackLines = stack.split('\\n').slice(0, MAX_STACK_FRAMES);\n\n const frames = [];\n\n for (const stackLine of stackLines) {\n const match = stackLine.match(\n /\\s+at\\s+(?<functionName>.*?)\\s+\\((?:file:\\/\\/)?(?<absPath>.+?):(?<lineno>\\d+):(?<colno>\\d+)\\)/,\n );\n\n if (!match || !match.groups) {\n // We didn't match a stack line, so skip it. This could cause some stack\n // lines to be dropped.\n continue;\n }\n\n const rawAbsPath = match.groups.absPath ?? '';\n\n const absPath = rawAbsPath.startsWith('node:')\n ? rawAbsPath\n : path.relative(cwd, rawAbsPath);\n const filename = rawAbsPath.startsWith('node:')\n ? rawAbsPath\n : path.basename(rawAbsPath);\n\n const functionName = match.groups.functionName ?? '';\n\n const lineno = match.groups.lineno\n ? Number.parseInt(match.groups.lineno, 10)\n : undefined;\n\n // Read context lines from the source file (only for package files)\n const context =\n rawAbsPath && lineno ? await readContextLines(rawAbsPath, lineno) : {};\n\n // https://develop.sentry.dev/sdk/data-model/event-payloads/stacktrace/#frame-attributes\n const frame: SentryFrame = {\n function: functionName.slice(0, MAX_FUNCTION_NAME_LENGTH),\n raw_function: functionName,\n\n abs_path: absPath,\n filename,\n\n lineno,\n\n colno: match.groups.colno\n ? Number.parseInt(match.groups.colno, 10)\n : undefined,\n\n ...context,\n };\n\n frames.push(frame);\n }\n\n // Sentry expects most recent frame last\n return frames.toReversed();\n}\n\n/**\n * Create a reporter with rate limiting\n */\nexport function createReporter(opts: ReporterOptions = {}): Reporter {\n const maxPerMinute = Math.max(1, opts.maxPerMinute ?? 10);\n let sentThisMinute = 0;\n let minuteTick = Date.now();\n\n function isRateLimitExceeded(): boolean {\n const now = Date.now();\n\n if (now - minuteTick >= 60_000) {\n minuteTick = now;\n sentThisMinute = 0;\n }\n\n if (sentThisMinute >= maxPerMinute) {\n return true;\n }\n\n sentThisMinute++;\n\n return false;\n }\n\n const ci = detectCI(process.env);\n const nodeVersion = process.version;\n const platform = process.platform;\n\n return {\n async captureException(e: unknown) {\n if (isRateLimitExceeded()) {\n return;\n }\n\n const err = e instanceof Error ? e : new Error(String(e));\n const message = err.message ?? 'Error';\n const stack = err.stack;\n\n const payload: ErrorPayload = {\n pkg,\n env: opts.env ?? process.env.NODE_ENV ?? 'unknown',\n ci,\n nodeVersion,\n platform,\n\n error: {\n name: err.name || 'Error',\n message,\n stack: stack ?? '',\n },\n };\n\n await sendToSentry(payload);\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\nimport path from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport type { ConfigWithDefaults } from '../config/index.ts';\nimport { findConfigFile, loadConfigFile } from '../config/loadConfig.ts';\nimport type { EnvironmentResult } from '../environment/index.ts';\nimport resolveEnvironment from '../environment/index.ts';\nimport type { Logger } from '../isomorphic/types.ts';\nimport type { Reporter } from './telemetry.ts';\nimport { createReporter } from './telemetry.ts';\n\nasync function getVersion() {\n const packageJson = await import('../../package.json', {\n with: { type: 'json' },\n });\n return packageJson.default.version;\n}\n\nfunction parseDashdashCommandParts(\n rawArgs: Array<string>,\n): Array<string> | undefined {\n const dashdashIndex = rawArgs.indexOf('--');\n if (dashdashIndex === -1) {\n return undefined;\n }\n return rawArgs.slice(dashdashIndex + 1);\n}\n\nfunction parseRawArgs(rawArgs: Array<string>) {\n const parsedArgs = parseArgs({\n args: rawArgs,\n\n options: {\n version: {\n type: 'boolean',\n short: 'v',\n },\n\n help: {\n type: 'boolean',\n short: 'h',\n },\n\n config: {\n type: 'string',\n short: 'c',\n },\n\n baseBranch: {\n type: 'string',\n },\n\n link: {\n type: 'string',\n },\n\n message: {\n type: 'string',\n },\n\n authorEmail: {\n type: 'string',\n },\n\n afterSha: {\n type: 'string',\n },\n\n beforeSha: {\n type: 'string',\n },\n\n fallbackShas: {\n type: 'string',\n },\n\n fallbackShasCount: {\n type: 'string',\n },\n\n notify: {\n type: 'string',\n },\n\n nonce: {\n type: 'string',\n },\n\n githubToken: {\n type: 'string',\n },\n },\n\n allowPositionals: true,\n });\n\n return {\n ...parsedArgs,\n dashdashCommandParts: parseDashdashCommandParts(rawArgs),\n };\n}\n\nconst helpText = `Happo ${await getVersion()}\nUsage: happo [options]\n\nCommands:\n <default> Run happo tests\n finalize Finalize happo report for Cypress/Playwright tests running in parallel\n\nOptions:\n --config Path to happo config file\n --version Show version number\n --help Show help text\n --baseBranch <branch> Base branch to use for comparison (default: 'origin/main')\n --link <url> URL to contextualize the comparison (default: auto-detected from CI environment)\n --message <message> Message to associate with the comparison (default: auto-detected from CI environment)\n --authorEmail <email> Email address of the author of the comparison (default: auto-detected from CI environment)\n --afterSha <sha> \"After\" SHA to use for comparison (default: auto-detected from CI environment, or HEAD SHA if not set)\n --beforeSha <sha> \"Before\" SHA to use for comparison (default: auto-detected from CI environment)\n --beforeShaTagMatcher <matcher> git tag matcher to use for \"before\" SHA resolution\n --fallbackShas <shas> Space-, newline- or comma-separated list of fallback shas for compare calls (default: auto-detected from CI environment)\n --fallbackShasCount <count> Number of fallback shas to use for compare calls (default: 50)\n --notify <emails> One or more (comma-separated) email addresses to notify with results\n --nonce <nonce> Nonce to use for Cypress/Playwright comparison\n --githubToken <token> GitHub token to use for posting Happo statuses as comments. Use in combination with the \\`githubApiUrl\\` configuration option. (default: auto-detected from environment)\n\nExamples:\n happo\n\n happo --config path/to/happo.config.ts\n happo --baseBranch origin/long-lived-branch\n happo --link https://github.com/happo/happo/pull/123\n happo --message \"Add new feature\"\n happo --notify me@example.com,you@example.com\n happo --nonce my-unique-nonce\n happo --githubToken {{ secrets.GITHUB_TOKEN }}\n\n happo --version\n happo --help\n\n happo -- playwright test\n\n happo finalize\n happo finalize --nonce my-unique-nonce\n `;\n\nfunction makeAbsolute(configFilePath: string): string {\n if (configFilePath.startsWith('.')) {\n return path.resolve(process.cwd(), configFilePath);\n }\n return configFilePath;\n}\n\nfunction installErrorHandlers(reporter: Reporter, logger: Logger) {\n const unhandledRejectionHandler: NodeJS.UnhandledRejectionListener = (reason) => {\n if (reason instanceof Error) {\n reporter.captureException(reason);\n logger.error(reason.stack || reason.message || String(reason));\n } else {\n reporter.captureException(reason);\n logger.error(`Unhandled rejection (non-Error value): ${String(reason)}`);\n }\n\n process.exitCode = 1;\n return;\n };\n\n const uncaughtExceptionHandler: NodeJS.UncaughtExceptionListener = (error) => {\n reporter.captureException(error);\n logger.error(error.stack || error.message || String(error));\n process.exitCode = 1;\n };\n\n process.on('unhandledRejection', unhandledRejectionHandler);\n process.on('uncaughtException', uncaughtExceptionHandler);\n\n return () => {\n process.removeListener('unhandledRejection', unhandledRejectionHandler);\n process.removeListener('uncaughtException', uncaughtExceptionHandler);\n };\n}\n\nexport async function main(\n rawArgs: Array<string> = process.argv,\n logger: Logger = console,\n): Promise<void> {\n const reporter = createReporter();\n const uninstallErrorHandlers = installErrorHandlers(reporter, logger);\n\n try {\n const args = parseRawArgs(rawArgs.slice(2));\n\n if (args.values.version) {\n // --version\n logger.log(await getVersion());\n return;\n }\n\n if (args.values.help) {\n // --help\n logger.log(helpText);\n return;\n }\n\n const environment = await resolveEnvironment(args.values);\n\n // Get config file path (use --config if provided, otherwise find default)\n const configFilePath = makeAbsolute(args.values.config || findConfigFile());\n const config = await loadConfigFile(configFilePath, environment, logger);\n\n // Handle positional arguments (commands)\n const command = args.positionals[0];\n\n if (args.dashdashCommandParts) {\n await handleE2ECommand(\n config,\n environment,\n args.dashdashCommandParts,\n configFilePath,\n logger,\n );\n return;\n }\n\n if (command === 'finalize') {\n await handleFinalizeCommand(config, environment, logger);\n return;\n }\n\n if (command === undefined) {\n await handleDefaultCommand(config, environment, logger);\n return;\n }\n\n logger.error(`Unknown command: ${command}\\n`);\n logger.error(helpText);\n process.exitCode = 1;\n } catch (error) {\n await reporter.captureException(error);\n logger.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n } finally {\n uninstallErrorHandlers();\n }\n}\n\nasync function handleDefaultCommand(\n config: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n): Promise<void> {\n logger.log('Running happo tests...');\n\n const [startJob, createAsyncComparison, createAsyncReport, prepareSnapRequests] =\n await Promise.all([\n (await import('../network/startJob.ts')).default,\n (await import('../network/createAsyncComparison.ts')).default,\n (await import('../network/createAsyncReport.ts')).default,\n (await import('../network/prepareSnapRequests.ts')).default,\n ]);\n\n // Tell Happo that we are about to run a job\n await startJob(config, environment, logger);\n\n try {\n // Prepare the snap requests for the job. This includes bundling static\n // assets and uploading them.\n const snapRequestIds = await prepareSnapRequests(config);\n\n // Put together a report from the snap requests.\n const asyncReport = await createAsyncReport(\n snapRequestIds,\n config,\n environment,\n logger,\n );\n\n // Create an async comparison.\n logger.log(`[HAPPO] Async report URL: ${asyncReport.url}`);\n if (environment.beforeSha !== environment.afterSha) {\n const asyncComparison = await createAsyncComparison(\n config,\n environment,\n logger,\n );\n logger.log(`[HAPPO] Async comparison URL: ${asyncComparison.compareUrl}`);\n }\n } catch (e) {\n logger.error(e instanceof Error ? e.message : String(e), e);\n const cancelJob = (await import('../network/cancelJob.ts')).default;\n await cancelJob('failure', config, environment, logger);\n process.exitCode = 1;\n return;\n }\n}\n\nasync function handleFinalizeCommand(\n config: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n): Promise<void> {\n logger.log('Finalizing happo report...');\n logger.log('Config:', config);\n logger.log('Environment:', environment);\n\n try {\n const finalizeAll = (await import('../e2e/wrapper.ts')).finalizeAll;\n await finalizeAll({ happoConfig: config, environment, logger });\n } catch (e) {\n logger.error(e instanceof Error ? e.message : String(e), e);\n process.exitCode = 1;\n return;\n }\n process.exitCode = 0;\n return;\n}\n\nconst E2E_INTEGRATION_TYPES = ['cypress', 'playwright'];\n\nasync function handleE2ECommand(\n config: ConfigWithDefaults,\n environment: EnvironmentResult,\n dashdashCommandParts: Array<string>,\n configFilePath: string,\n logger: Logger,\n): Promise<void> {\n if (!E2E_INTEGRATION_TYPES.includes(config.integration.type)) {\n logger.error(\n `Unsupported integration type used for e2e command: ${config.integration.type}. Supported integration types for e2e are: ${E2E_INTEGRATION_TYPES.join(', ')}`,\n );\n process.exitCode = 1;\n return;\n }\n\n if (!dashdashCommandParts || dashdashCommandParts.length === 0) {\n logger.error('Missing command for e2e action');\n logger.error(helpText);\n process.exitCode = 1;\n return;\n }\n\n logger.log('Setting up happo wrapper for Cypress and Playwright...');\n logger.log('Config:', config);\n logger.log('Environment:', environment);\n logger.log('Dashdash command parts:', dashdashCommandParts);\n\n const runWithWrapper = (await import('../e2e/wrapper.ts')).default;\n const exitCode = await runWithWrapper(\n dashdashCommandParts,\n config,\n environment,\n logger,\n configFilePath,\n );\n process.exitCode = exitCode;\n}\n\nif (import.meta.main) {\n await main();\n}\n", "import fs from 'node:fs';\n\nimport { any as findAny } from 'empathic/find';\n\nimport type { EnvironmentResult } from '../environment/index.ts';\nimport type { Logger } from '../isomorphic/types.ts';\nimport type { ConfigWithDefaults, TargetWithDefaults } from './index.ts';\n\nconst CONFIG_FILENAMES = [\n 'happo.config.js',\n 'happo.config.mjs',\n 'happo.config.cjs',\n 'happo.config.ts',\n 'happo.config.mts',\n 'happo.config.cts',\n];\n\nconst DEFAULT_ENDPOINT = 'https://happo.io';\n\nexport function findConfigFile(): string {\n if (process.env.HAPPO_CONFIG_FILE) {\n return process.env.HAPPO_CONFIG_FILE;\n }\n\n const configFilePath = findAny(CONFIG_FILENAMES, { cwd: process.cwd() });\n\n if (!configFilePath) {\n throw new Error(\n 'Happo config file could not be found. Please create a config file in the root of your project.',\n );\n }\n\n return configFilePath;\n}\n\nfunction assertIsPullRequestTokenResponse(\n response: unknown,\n): asserts response is { secret: string } {\n if (typeof response !== 'object' || response === null || !('secret' in response)) {\n throw new TypeError('Unexpected pull request token response');\n }\n}\n\nasync function getPullRequestSecret(\n endpoint: string,\n prUrl: string,\n): Promise<string> {\n const res = await fetch(`${endpoint}/api/pull-request-token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ prUrl }),\n });\n\n if (!res || !res.ok) {\n throw new Error(\n `Failed to get pull request secret: ${res.status} - ${await res.text()}`,\n );\n }\n\n const json = await res.json();\n assertIsPullRequestTokenResponse(json);\n\n return json.secret;\n}\n\nexport async function loadConfigFile(\n configFilePath: string,\n environment?: Pick<EnvironmentResult, 'link'>,\n logger: Logger = console,\n): Promise<ConfigWithDefaults> {\n try {\n const stats = await fs.promises.stat(configFilePath);\n if (!stats.isFile()) {\n throw new Error(`Happo config file path is not a file: ${configFilePath}`);\n }\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n throw new Error(`Happo config file could not be found: ${configFilePath}`);\n }\n\n throw error;\n }\n\n const config = (await import(configFilePath)).default;\n\n // We read these in here so that they can be passed along to the child process\n // in e2e/wrapper.ts. This allows us to use pull-request authentication\n // without having to make an additional HTTP request.\n if (!config.apiKey && process.env.HAPPO_API_KEY) {\n config.apiKey = process.env.HAPPO_API_KEY;\n }\n if (!config.apiSecret && process.env.HAPPO_API_SECRET) {\n config.apiSecret = process.env.HAPPO_API_SECRET;\n }\n\n if (!config.apiKey || !config.apiSecret) {\n const missing = [\n config.apiKey ? null : 'apiKey',\n config.apiSecret ? null : 'apiSecret',\n ]\n .filter(Boolean)\n .map((key) => `\\`${key}\\``)\n .join(' and ');\n\n if (!environment?.link) {\n throw new Error(\n `Missing ${missing} in your Happo config. Reference yours at https://happo.io/settings`,\n );\n }\n\n try {\n // Reassign API tokens to temporary ones provided for the PR\n logger.log(\n `Missing ${missing} in Happo config. Falling back to pull-request authentication.`,\n );\n config.apiKey = environment.link;\n config.apiSecret = await getPullRequestSecret(\n config.endpoint || DEFAULT_ENDPOINT,\n environment.link,\n );\n } catch (e) {\n throw new Error('Failed to obtain temporary pull-request token', { cause: e });\n }\n }\n\n if (!config.targets) {\n config.targets = {\n chrome: {\n type: 'chrome',\n viewport: '1024x768',\n },\n };\n }\n\n if (!config.integration) {\n config.integration = {\n type: 'storybook',\n };\n }\n\n const allTargets = Object.values(config.targets);\n for (const target of allTargets as Array<TargetWithDefaults>) {\n target.viewport = target.viewport || '1024x768';\n target.freezeAnimations = target.freezeAnimations || 'last-frame';\n target.prefersReducedMotion = target.prefersReducedMotion ?? true;\n }\n\n const configWithDefaults = {\n endpoint: DEFAULT_ENDPOINT,\n githubApiUrl: 'https://api.github.com',\n targets: allTargets,\n ...config,\n };\n\n return configWithDefaults;\n}\n", "import { spawnSync } from 'node:child_process';\nimport crypto, { randomBytes } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\n\ninterface GitHubEvent {\n pull_request?: {\n html_url: string;\n title: string;\n base: {\n sha: string;\n };\n head: {\n sha: string;\n };\n };\n head_commit?: {\n url: string;\n };\n merge_group?: {\n head_sha: string;\n base_sha: string;\n };\n repository?: {\n html_url: string;\n };\n before?: string;\n after?: string;\n}\n\ninterface CLIArgs {\n baseBranch?: string;\n afterSha?: string;\n beforeSha?: string;\n message?: string;\n link?: string;\n authorEmail?: string;\n beforeShaTagMatcher?: string;\n notify?: string;\n fallbackShas?: string;\n fallbackShasCount?: string;\n nonce?: string;\n githubToken?: string;\n}\n\nexport interface EnvironmentResult {\n link: string | undefined;\n message: string | undefined;\n authorEmail: string | undefined;\n beforeSha: string;\n afterSha: string;\n nonce: string | undefined;\n debugMode: boolean;\n notify: string | undefined;\n fallbackShas: Array<string> | undefined;\n githubToken: string | undefined;\n}\n\nconst envKeys: ReadonlyArray<string> = [\n 'BUILD_REPOSITORY_URI',\n 'BUILD_SOURCEVERSION',\n 'CIRCLE_PROJECT_REPONAME',\n 'CIRCLE_PROJECT_USERNAME',\n 'CIRCLE_SHA1',\n 'CI_PULL_REQUEST',\n 'GITHUB_EVENT_PATH',\n 'GITHUB_SERVER_URL',\n 'GITHUB_SHA',\n 'HAPPO_DEBUG',\n 'SYSTEM_PULLREQUEST_PULLREQUESTID',\n 'SYSTEM_PULLREQUEST_SOURCEBRANCH',\n 'SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI',\n 'SYSTEM_PULLREQUEST_TARGETBRANCH',\n 'TRAVIS_COMMIT',\n 'TRAVIS_COMMIT_RANGE',\n 'TRAVIS_PULL_REQUEST',\n 'TRAVIS_PULL_REQUEST_SHA',\n 'TRAVIS_REPO_SLUG',\n];\n\nasync function resolveGithubEvent(GITHUB_EVENT_PATH: string): Promise<GitHubEvent> {\n try {\n const fs = await import('node:fs/promises');\n const content = await fs.readFile(GITHUB_EVENT_PATH, 'utf8');\n return JSON.parse(content);\n } catch (e) {\n throw new Error(\n `Failed to load GitHub event from the GITHUB_EVENT_PATH environment variable: ${JSON.stringify(GITHUB_EVENT_PATH)}`,\n { cause: e },\n );\n }\n}\n\nasync function resolveLink(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n): Promise<string | undefined> {\n if (cliArgs.link) {\n // Validate the link\n let parsed: URL;\n try {\n parsed = new URL(cliArgs.link);\n } catch (e) {\n throw new TypeError(\n `link must be a valid http/https URL. Invalid URL: '${cliArgs.link}'`,\n { cause: e },\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new TypeError(\n `link must be a valid http/https URL. Invalid protocol: '${parsed.protocol}' (from '${cliArgs.link}')`,\n );\n }\n\n return cliArgs.link;\n }\n\n const {\n BUILD_REPOSITORY_URI,\n BUILD_SOURCEVERSION,\n\n // https://circleci.com/docs/reference/variables/\n CIRCLE_PROJECT_REPONAME,\n CIRCLE_PROJECT_USERNAME,\n CIRCLE_PULL_REQUEST,\n CIRCLE_SHA1,\n\n CI_PULL_REQUEST,\n\n // https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables\n GITHUB_EVENT_PATH,\n GITHUB_SHA,\n\n SYSTEM_PULLREQUEST_PULLREQUESTID,\n SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI,\n\n // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables\n TRAVIS_COMMIT,\n TRAVIS_PULL_REQUEST,\n TRAVIS_REPO_SLUG,\n } = env;\n\n if (CI_PULL_REQUEST) {\n // Circle CI\n return CI_PULL_REQUEST;\n }\n\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.html_url;\n }\n if (ghEvent.head_commit) {\n return ghEvent.head_commit.url;\n }\n if (ghEvent.merge_group && ghEvent.repository) {\n return `${ghEvent.repository.html_url}/commit/${ghEvent.merge_group.head_sha}`;\n }\n if (GITHUB_SHA && ghEvent.repository) {\n return `${ghEvent.repository.html_url}/commit/${GITHUB_SHA}`;\n }\n }\n\n if (SYSTEM_PULLREQUEST_PULLREQUESTID && SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI) {\n return `${SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI}/pullrequest/${SYSTEM_PULLREQUEST_PULLREQUESTID}`.replace(\n /[^/]+@/,\n '',\n );\n }\n\n if (BUILD_REPOSITORY_URI && BUILD_SOURCEVERSION) {\n return `${BUILD_REPOSITORY_URI}/commit/${BUILD_SOURCEVERSION}`.replace(\n /[^/]+@/,\n '',\n );\n }\n\n const githubBase = 'https://github.com';\n\n if (TRAVIS_REPO_SLUG && TRAVIS_PULL_REQUEST) {\n return `${githubBase}/${TRAVIS_REPO_SLUG}/pull/${TRAVIS_PULL_REQUEST}`;\n }\n\n if (TRAVIS_REPO_SLUG && TRAVIS_COMMIT) {\n return `${githubBase}/${TRAVIS_REPO_SLUG}/commit/${TRAVIS_COMMIT}`;\n }\n\n if (CIRCLE_PULL_REQUEST) {\n return CIRCLE_PULL_REQUEST;\n }\n\n if (CIRCLE_PROJECT_USERNAME && CIRCLE_PROJECT_REPONAME && CIRCLE_SHA1) {\n return `${githubBase}/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/commit/${CIRCLE_SHA1}`;\n }\n\n return undefined;\n}\n\nasync function resolveAuthorEmail(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n): Promise<string | undefined> {\n if (cliArgs.authorEmail) {\n return cliArgs.authorEmail;\n }\n\n const { GITHUB_EVENT_PATH } = env;\n\n if (GITHUB_EVENT_PATH) {\n // const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n // TODO: do something with the github event\n }\n\n const res = spawnSync('git', ['show', '-s', '--format=%ae'], {\n encoding: 'utf8',\n });\n\n if (res.status !== 0) {\n return undefined;\n }\n\n return res.stdout.trim();\n}\n\nasync function resolveMessage(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n afterSha: string,\n): Promise<string | undefined> {\n if (cliArgs.message) {\n return cliArgs.message;\n }\n\n const { GITHUB_EVENT_PATH } = env;\n\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.title;\n }\n }\n\n const res = spawnSync('git', ['log', '-1', '--pretty=%s', afterSha], {\n encoding: 'utf8',\n });\n\n if (res.status !== 0) {\n return undefined;\n }\n\n const message = res.stdout.split('\\n')[0];\n\n if (!message) {\n return undefined;\n }\n\n return message;\n}\n\nfunction resolveShaFromTagMatcher(tagMatcher: string): string | undefined {\n const res = spawnSync(\n 'git',\n ['tag', '--list', tagMatcher, '--sort', 'refname', '--no-contains'],\n {\n encoding: 'utf8',\n },\n );\n\n if (res.status !== 0) {\n throw new Error(\n `Failed to list git tags when matching against --beforeShaTagMatcher '${tagMatcher}'. Error: ${res.stderr}`,\n );\n }\n\n const rawAllTags = res.stdout.trim();\n if (!rawAllTags.length) {\n return undefined;\n }\n\n const allTags = rawAllTags.split('\\n');\n const tag = allTags.at(-1);\n\n if (!tag) {\n throw new Error('No tag found matching the pattern');\n }\n\n const commitRes = spawnSync('git', ['rev-list', '-n', '1', tag], {\n encoding: 'utf8',\n });\n\n if (commitRes.status !== 0) {\n throw new Error(\n `Failed to resolve commit sha from tag \"${tag}\". Error: ${res.stderr}`,\n );\n }\n\n return commitRes.stdout.trim();\n}\n\nasync function resolveBeforeSha(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n afterSha: string,\n): Promise<string | undefined> {\n if (cliArgs.beforeSha) {\n return cliArgs.beforeSha;\n }\n\n if (cliArgs.beforeShaTagMatcher) {\n const resolvedSha = resolveShaFromTagMatcher(cliArgs.beforeShaTagMatcher);\n if (resolvedSha) {\n return resolvedSha;\n }\n }\n\n const { TRAVIS_COMMIT_RANGE, GITHUB_EVENT_PATH, SYSTEM_PULLREQUEST_TARGETBRANCH } =\n env;\n\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.base.sha;\n }\n if (ghEvent.merge_group) {\n return ghEvent.merge_group.base_sha;\n }\n return ghEvent.before;\n }\n\n if (TRAVIS_COMMIT_RANGE) {\n const [first] = TRAVIS_COMMIT_RANGE.split('...');\n return first;\n }\n\n let baseAzureBranch;\n if (SYSTEM_PULLREQUEST_TARGETBRANCH) {\n baseAzureBranch = [\n 'origin',\n SYSTEM_PULLREQUEST_TARGETBRANCH.split('/').toReversed()[0],\n ].join('/');\n }\n\n const baseBranch = cliArgs.baseBranch || baseAzureBranch || 'origin/main';\n const res = spawnSync('git', ['merge-base', baseBranch, afterSha], {\n encoding: 'utf8',\n });\n if (res.status !== 0) {\n console.error(`[HAPPO] Ignored error when resolving base commit: ${res.stderr}`);\n return undefined;\n }\n return res.stdout.split('\\n')[0];\n}\n\nfunction getHeadShaWithLocalChanges(): {\n headSha: string;\n headShaWithLocalChanges: string;\n} {\n const randomSha = randomBytes(20).toString('hex');\n // Get the HEAD sha from the git repo, or if we have local changes, add them to the sha\n const res = spawnSync('git', ['rev-parse', 'HEAD'], {\n encoding: 'utf8',\n });\n if (res.status !== 0) {\n return { headSha: randomSha, headShaWithLocalChanges: randomSha };\n }\n const headSha = res.stdout.split('\\n')[0];\n if (!headSha) {\n return { headSha: randomSha, headShaWithLocalChanges: randomSha };\n }\n\n // Check for local changes\n const diffRes = spawnSync('git', ['diff', 'HEAD'], {\n encoding: 'utf8',\n });\n\n // If git diff fails, return HEAD sha\n if (diffRes.status !== 0) {\n return { headSha, headShaWithLocalChanges: headSha };\n }\n\n const lsRes = spawnSync('git', ['ls-files', '--other', '--exclude-standard'], {\n encoding: 'utf8',\n });\n\n if (lsRes.status !== 0) {\n return { headSha, headShaWithLocalChanges: headSha };\n }\n\n const localChanges = [diffRes.stdout.trim(), lsRes.stdout.trim()];\n\n // Get contents of untracked files\n const untrackedFiles = lsRes.stdout\n .trim()\n .split('\\n')\n .filter((file) => file.trim());\n\n for (const file of untrackedFiles) {\n try {\n const content = readFileSync(file, 'utf8');\n localChanges.push(content);\n } catch {\n // If we can't read the file, include just the filename\n localChanges.push(`${file}:<unreadable>`);\n }\n }\n\n const allChanges = localChanges.join('');\n\n if (!allChanges.trim()) {\n return { headSha, headShaWithLocalChanges: headSha };\n }\n\n // If there are local changes, create a hash that includes both HEAD and the changes\n const headShaWithLocalChanges = crypto\n .createHash('sha256')\n .update(headSha)\n .update(allChanges)\n .digest('hex')\n .slice(0, 40);\n\n return { headSha, headShaWithLocalChanges };\n}\n\nasync function resolveAfterSha(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined>,\n): Promise<string | { headSha: string; headShaWithLocalChanges: string }> {\n if (cliArgs.afterSha) {\n return cliArgs.afterSha;\n }\n\n const {\n CIRCLE_SHA1,\n TRAVIS_PULL_REQUEST_SHA,\n TRAVIS_COMMIT,\n GITHUB_EVENT_PATH,\n GITHUB_SHA,\n BUILD_SOURCEVERSION,\n SYSTEM_PULLREQUEST_SOURCEBRANCH,\n CI,\n } = env;\n\n const sha = CIRCLE_SHA1 || TRAVIS_PULL_REQUEST_SHA || TRAVIS_COMMIT;\n\n if (sha) {\n return sha;\n }\n\n if (SYSTEM_PULLREQUEST_SOURCEBRANCH) {\n // azure pull request\n const rawBranchName = SYSTEM_PULLREQUEST_SOURCEBRANCH.split('/').toReversed()[0];\n const res = spawnSync('git', ['rev-parse', `origin/${rawBranchName}`], {\n encoding: 'utf8',\n });\n if (res.status === 0 && res.stdout) {\n const sha = res.stdout.split('\\n')[0];\n if (sha) {\n return sha;\n }\n }\n }\n if (BUILD_SOURCEVERSION) {\n // azure master job\n return BUILD_SOURCEVERSION;\n }\n if (GITHUB_EVENT_PATH) {\n const ghEvent = await resolveGithubEvent(GITHUB_EVENT_PATH);\n if (ghEvent.pull_request) {\n return ghEvent.pull_request.head.sha;\n }\n if (ghEvent.merge_group) {\n return ghEvent.merge_group.head_sha;\n }\n if (ghEvent.after) {\n return ghEvent.after;\n }\n if (GITHUB_SHA) {\n return GITHUB_SHA;\n }\n }\n const headShaWithLocalChanges = getHeadShaWithLocalChanges();\n if (CI) {\n return headShaWithLocalChanges.headSha;\n }\n return headShaWithLocalChanges;\n}\n\nfunction resolveFallbackShas(\n cliArgs: CLIArgs,\n beforeSha: string | undefined,\n): Array<string> | undefined {\n if (cliArgs.fallbackShas) {\n return cliArgs.fallbackShas.split(/[,\\s]+/).filter(Boolean);\n }\n\n const fallbackShasCount = cliArgs.fallbackShasCount\n ? Number.parseInt(cliArgs.fallbackShasCount, 10)\n : 50;\n\n if (Number.isNaN(fallbackShasCount)) {\n throw new TypeError(\n `fallbackShasCount must be a number. Invalid value: '${cliArgs.fallbackShasCount}'`,\n );\n }\n\n const res = spawnSync(\n 'git',\n [\n 'log',\n '--format=%H',\n '--first-parent',\n `--max-count=${fallbackShasCount}`,\n `${beforeSha}^`,\n ],\n {\n encoding: 'utf8',\n },\n );\n\n if (res.status !== 0) {\n return undefined;\n }\n\n return res.stdout.split('\\n').filter(Boolean);\n}\n\nfunction getRawEnv(\n env: Record<string, string | undefined>,\n): Record<string, string | undefined> {\n const res: Record<string, string | undefined> = {};\n for (const key of envKeys) {\n res[key] = env[key];\n }\n return res;\n}\n\nexport default async function resolveEnvironment(\n cliArgs: CLIArgs,\n env: Record<string, string | undefined> = process.env,\n): Promise<EnvironmentResult> {\n const debugMode = !!env.HAPPO_DEBUG;\n const afterSha = await resolveAfterSha(cliArgs, env);\n\n const realAfterSha = typeof afterSha === 'string' ? afterSha : afterSha.headSha;\n const afterShaWithLocalChanges =\n typeof afterSha === 'string' ? afterSha : afterSha.headShaWithLocalChanges;\n\n // Resolve the before SHA with the true HEAD SHA\n const [beforeSha, link, authorEmail, message] = await Promise.all([\n resolveBeforeSha(cliArgs, env, realAfterSha),\n resolveLink(cliArgs, env),\n resolveAuthorEmail(cliArgs, env),\n\n // Resolve message with the SHA that includes local changes\n resolveMessage(cliArgs, env, afterShaWithLocalChanges),\n ]);\n\n const nonNullBeforeSha = beforeSha || afterShaWithLocalChanges;\n\n const result = {\n link,\n authorEmail,\n message,\n beforeSha: nonNullBeforeSha,\n afterSha: afterShaWithLocalChanges,\n nonce: cliArgs.nonce,\n debugMode,\n notify: cliArgs.notify,\n fallbackShas: resolveFallbackShas(cliArgs, nonNullBeforeSha),\n githubToken: cliArgs.githubToken,\n };\n\n if (debugMode) {\n console.log('[HAPPO] Raw environment', getRawEnv(env));\n console.log('[HAPPO] Resolved environment', result);\n }\n\n return result;\n}\n", "import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { createInterface } from 'node:readline';\n\nimport { up as findPackage } from 'empathic/package';\n\nimport pkg from '../../package.json' with { type: 'json' };\n\n/**\n * Happo's PUBLIC DSN. Safe to ship.\n */\nconst SENTRY_DSN =\n 'https://3a495ff2101313edb024de73b005398f@o108341.ingest.us.sentry.io/4510341337645056';\n\nconst MAX_STACK_FRAMES = 50;\nconst MAX_FUNCTION_NAME_LENGTH = 120;\n\ntype CI =\n | 'github'\n | 'circleci'\n | 'travis'\n | 'azure'\n | 'buildkite'\n | 'jenkins'\n | 'gitlab'\n | 'bitbucket'\n | 'appveyor'\n | 'drone'\n | 'ci'\n | 'unknown';\n\ntype ErrorPayload = {\n pkg: {\n name: string;\n version: string;\n };\n\n error: {\n name: string;\n message: string;\n stack?: string;\n };\n\n env: string;\n\n /** Detected CI vendor if any */\n ci: CI;\n\n nodeVersion: string;\n platform: string;\n};\n\nexport type ReporterOptions = {\n maxPerMinute?: number; // default 10\n env?: string;\n};\n\nexport interface Reporter {\n captureException(e: unknown): Promise<void>;\n}\n\nexport function detectCI(env: Record<string, string | undefined> = process.env): CI {\n if (env.GITHUB_ACTIONS) {\n return 'github';\n }\n\n if (env.CIRCLECI) {\n return 'circleci';\n }\n\n if (env.TRAVIS) {\n return 'travis';\n }\n\n if (env.TF_BUILD) {\n return 'azure';\n }\n\n if (env.BUILDKITE) {\n return 'buildkite';\n }\n\n if (env.JENKINS_URL) {\n return 'jenkins';\n }\n\n if (env.GITLAB_CI) {\n return 'gitlab';\n }\n\n if (env.BITBUCKET_BUILD_NUMBER) {\n return 'bitbucket';\n }\n\n if (env.APPVEYOR) {\n return 'appveyor';\n }\n\n if (env.DRONE) {\n return 'drone';\n }\n\n if (env.CI) {\n return 'ci';\n }\n\n return 'unknown';\n}\n\nexport function parseDsn(dsn: string): {\n host: string;\n projectId: string;\n key: string;\n protocol: string;\n} | null {\n try {\n // https://{PUBLIC_KEY}@{host}/{project_id}\n const u = new URL(dsn);\n const projectId = u.pathname.replace(/^\\//, '');\n const key = u.username; // public key\n const host = u.host; // includes subdomain + port\n return {\n host,\n projectId,\n key,\n protocol: u.protocol.replace(':', ''),\n };\n } catch {\n return null;\n }\n}\n\nasync function sendToSentry(payload: ErrorPayload) {\n const dsn = parseDsn(SENTRY_DSN);\n\n if (!dsn) {\n return;\n }\n\n // Sentry envelope: https://develop.sentry.dev/sdk/envelopes/\n const now = Date.now();\n\n // Hexadecimal string representing a uuid4 value. The length is exactly 32\n // characters. Dashes are not allowed. Has to be lowercase.\n const eventId = crypto.randomUUID().replaceAll('-', '');\n\n const url = `https://${dsn.host}/api/${dsn.projectId}/envelope/`;\n\n const httpHeaders = {\n 'content-type': 'application/x-sentry-envelope',\n 'x-sentry-auth': [\n 'Sentry sentry_version=7',\n `sentry_key=${dsn.key}`,\n `sentry_client=${pkg.name}@${pkg.version}`,\n ].join(', '),\n };\n\n const memoryInfo = process.memoryUsage();\n\n // Minimal event; we keep fields lean & sanitized\n // https://develop.sentry.dev/sdk/data-model/event-payloads/#required-attributes\n const event = {\n event_id: eventId,\n\n // RFC 3339 format\n timestamp: new Date(now).toISOString(),\n\n // A string representing the platform the SDK is submitting from. This will\n // be used by the Sentry interface to customize various components in the\n // interface.\n platform: 'node',\n\n // Possible values: fatal, error, warning, info, debug\n level: 'error',\n\n logger: 'happo.telemetry.cli',\n\n environment: payload.env ?? 'unknown',\n\n // Release versions must be unique across all projects in the organization.\n release: `${payload.pkg.name}@${payload.pkg.version}`,\n\n tags: {\n ci: payload.ci ?? '',\n },\n\n contexts: {\n runtime: {\n type: 'runtime',\n name: 'node',\n version: payload.nodeVersion ?? '',\n },\n os: {\n type: 'os',\n name: payload.platform ?? '',\n },\n memory_info: {\n type: 'memory_info',\n ...memoryInfo,\n },\n },\n\n exception: {\n values: [\n {\n type: payload.error.name || 'Error',\n value: payload.error.message,\n\n // https://develop.sentry.dev/sdk/data-model/event-payloads/stacktrace/\n stacktrace: payload.error.stack\n ? {\n frames: await parseFrames(payload.error.stack),\n }\n : undefined,\n },\n ],\n },\n };\n\n /**\n * Envelope is ndjson-like chunks\n *\n * Envelope = Headers { \"\\n\" Item } [ \"\\n\" ] ;\n * Item = Headers \"\\n\" Payload ;\n * Payload = { * } ;\n *\n * @see https://develop.sentry.dev/sdk/data-model/envelopes/#headers\n */\n const envelopeHeader = JSON.stringify({\n event_id: eventId,\n dsn: SENTRY_DSN,\n sent_at: new Date(now).toISOString(),\n });\n\n // https://develop.sentry.dev/sdk/data-model/envelopes/#items\n const item = JSON.stringify(event);\n const itemHeader = JSON.stringify({ type: 'event', length: item.length });\n\n const body = [envelopeHeader, itemHeader, item].join('\\n');\n\n try {\n await fetch(url, { method: 'POST', headers: httpHeaders, body });\n } catch {\n // swallow; never throw in library code\n }\n}\n\ninterface SentryFrame {\n function: string;\n raw_function: string;\n abs_path: string | undefined;\n filename: string | undefined;\n lineno: number | undefined;\n colno: number | undefined;\n context_line?: string;\n pre_context?: Array<string>;\n post_context?: Array<string>;\n}\n\n/**\n * Get the package root directory\n */\nfunction getPackageRoot(): string {\n const packageJsonPath = findPackage({ cwd: import.meta.dirname });\n\n if (!packageJsonPath) {\n // Fallback to relative path if empathic can't find it\n return path.resolve(import.meta.dirname, '../..');\n }\n\n return path.dirname(packageJsonPath);\n}\n\n/**\n * Check if a file path is part of this package\n */\nfunction isFileInThisPackage(filePath: string): boolean {\n // Skip node: internal modules\n if (filePath.startsWith('node:')) {\n return false;\n }\n\n try {\n const packageRoot = getPackageRoot();\n // Remove file:// prefix if present\n const cleanPath = filePath.replace(/^file:\\/\\//, '');\n const resolvedPath = path.resolve(cleanPath);\n return resolvedPath.startsWith(packageRoot + path.sep);\n } catch {\n return false;\n }\n}\n\n/**\n * Read context lines from a source file around a given line number\n * Only reads files that are part of this package\n */\nasync function readContextLines(\n filePath: string,\n lineNumber: number,\n contextLines: number = 5,\n): Promise<{\n context_line?: string;\n pre_context?: Array<string>;\n post_context?: Array<string>;\n}> {\n // Only read context for files in this package. This is to avoid reading\n // context from external files that may contain sensitive information.\n if (!isFileInThisPackage(filePath)) {\n return {};\n }\n\n try {\n // Remove file:// prefix if present\n const cleanPath = filePath.replace(/^file:\\/\\//, '');\n\n // Line numbers are 1-indexed in stack traces, but arrays are 0-indexed\n const lineIndex = lineNumber - 1;\n\n if (lineIndex < 0) {\n return {};\n }\n\n const startLine = Math.max(0, lineIndex - contextLines);\n const endLine = lineIndex + contextLines + 1;\n\n const pre_context: Array<string> = [];\n let context_line: string | undefined;\n const post_context: Array<string> = [];\n\n const fileStream = fs.createReadStream(cleanPath, { encoding: 'utf8' });\n const rl = createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n let currentLine = 0;\n\n for await (const line of rl) {\n if (currentLine < startLine) {\n // Skip lines before our context window\n currentLine++;\n continue;\n }\n\n if (currentLine === lineIndex) {\n context_line = line;\n } else if (currentLine < lineIndex) {\n pre_context.push(line);\n } else if (currentLine < endLine) {\n post_context.push(line);\n } else {\n // We've read all the lines we need\n break;\n }\n\n currentLine++;\n }\n\n // Check if we found the target line\n if (context_line === undefined) {\n return {};\n }\n\n const result: {\n context_line: string;\n pre_context?: Array<string>;\n post_context?: Array<string>;\n } = {\n context_line,\n };\n\n if (pre_context.length > 0) {\n result.pre_context = pre_context;\n }\n\n if (post_context.length > 0) {\n result.post_context = post_context;\n }\n\n return result;\n } catch {\n // File might not exist, might not be readable, etc. Best-effort only.\n return {};\n }\n}\n\n/**\n * Convert an error stack to Sentry frames (best-effort)\n */\nexport async function parseFrames(\n stack: string,\n cwd: string = process.cwd(),\n): Promise<Array<SentryFrame>> {\n // Node stack lines like: \" at func (file:///absolute/path/to/file.js:10:5)\"\n const stackLines = stack.split('\\n').slice(0, MAX_STACK_FRAMES);\n\n const frames = [];\n\n for (const stackLine of stackLines) {\n const match = stackLine.match(\n /\\s+at\\s+(?<functionName>.*?)\\s+\\((?:file:\\/\\/)?(?<absPath>.+?):(?<lineno>\\d+):(?<colno>\\d+)\\)/,\n );\n\n if (!match || !match.groups) {\n // We didn't match a stack line, so skip it. This could cause some stack\n // lines to be dropped.\n continue;\n }\n\n const rawAbsPath = match.groups.absPath ?? '';\n\n const absPath = rawAbsPath.startsWith('node:')\n ? rawAbsPath\n : path.relative(cwd, rawAbsPath);\n const filename = rawAbsPath.startsWith('node:')\n ? rawAbsPath\n : path.basename(rawAbsPath);\n\n const functionName = match.groups.functionName ?? '';\n\n const lineno = match.groups.lineno\n ? Number.parseInt(match.groups.lineno, 10)\n : undefined;\n\n // Read context lines from the source file (only for package files)\n const context =\n rawAbsPath && lineno ? await readContextLines(rawAbsPath, lineno) : {};\n\n // https://develop.sentry.dev/sdk/data-model/event-payloads/stacktrace/#frame-attributes\n const frame: SentryFrame = {\n function: functionName.slice(0, MAX_FUNCTION_NAME_LENGTH),\n raw_function: functionName,\n\n abs_path: absPath,\n filename,\n\n lineno,\n\n colno: match.groups.colno\n ? Number.parseInt(match.groups.colno, 10)\n : undefined,\n\n ...context,\n };\n\n frames.push(frame);\n }\n\n // Sentry expects most recent frame last\n return frames.toReversed();\n}\n\n/**\n * Create a reporter with rate limiting\n */\nexport function createReporter(opts: ReporterOptions = {}): Reporter {\n const maxPerMinute = Math.max(1, opts.maxPerMinute ?? 10);\n let sentThisMinute = 0;\n let minuteTick = Date.now();\n\n function isRateLimitExceeded(): boolean {\n const now = Date.now();\n\n if (now - minuteTick >= 60_000) {\n minuteTick = now;\n sentThisMinute = 0;\n }\n\n if (sentThisMinute >= maxPerMinute) {\n return true;\n }\n\n sentThisMinute++;\n\n return false;\n }\n\n const ci = detectCI(process.env);\n const nodeVersion = process.version;\n const platform = process.platform;\n\n return {\n async captureException(e: unknown) {\n if (isRateLimitExceeded()) {\n return;\n }\n\n const err = e instanceof Error ? e : new Error(String(e));\n const message = err.message ?? 'Error';\n const stack = err.stack;\n\n const payload: ErrorPayload = {\n pkg,\n env: opts.env ?? process.env.NODE_ENV ?? 'unknown',\n ci,\n nodeVersion,\n platform,\n\n error: {\n name: err.name || 'Error',\n message,\n stack: stack ?? '',\n },\n };\n\n await sendToSentry(payload);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["path", "fs", "sha", "crypto", "fs", "crypto", "fs", "path"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
makeHappoAPIRequest
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-TCHC2O3E.js";
|
|
4
|
+
import "./chunk-DYCU4INF.js";
|
|
5
5
|
|
|
6
6
|
// src/network/prepareSnapRequests.ts
|
|
7
7
|
import fs5 from "node:fs";
|
|
@@ -731,4 +731,4 @@ async function prepareSnapRequests(config) {
|
|
|
731
731
|
export {
|
|
732
732
|
prepareSnapRequests as default
|
|
733
733
|
};
|
|
734
|
-
//# sourceMappingURL=prepareSnapRequests-
|
|
734
|
+
//# sourceMappingURL=prepareSnapRequests-NPYZG2AI.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
makeHappoAPIRequest
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-TCHC2O3E.js";
|
|
4
|
+
import "./chunk-DYCU4INF.js";
|
|
5
5
|
|
|
6
6
|
// src/network/startJob.ts
|
|
7
7
|
function assertResultIsStartJobResult(result) {
|
|
@@ -36,4 +36,4 @@ async function startJob(config, { beforeSha, afterSha, link, message }, logger)
|
|
|
36
36
|
export {
|
|
37
37
|
startJob as default
|
|
38
38
|
};
|
|
39
|
-
//# sourceMappingURL=startJob-
|
|
39
|
+
//# sourceMappingURL=startJob-T3PWEYOP.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAsyncComparison
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TPF2272Y.js";
|
|
4
4
|
import {
|
|
5
5
|
makeHappoAPIRequest
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-TCHC2O3E.js";
|
|
7
|
+
import "./chunk-DYCU4INF.js";
|
|
8
8
|
|
|
9
9
|
// src/e2e/wrapper.ts
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
@@ -315,7 +315,9 @@ async function runWithWrapper(dashdashCommandParts, happoConfig, environment, lo
|
|
|
315
315
|
env: {
|
|
316
316
|
...process.env,
|
|
317
317
|
HAPPO_E2E_PORT: e2eServer.port.toString(),
|
|
318
|
-
HAPPO_CONFIG_FILE: configFilePath
|
|
318
|
+
HAPPO_CONFIG_FILE: configFilePath,
|
|
319
|
+
HAPPO_API_KEY: happoConfig.apiKey,
|
|
320
|
+
HAPPO_API_SECRET: happoConfig.apiSecret
|
|
319
321
|
},
|
|
320
322
|
shell: process.platform == "win32"
|
|
321
323
|
});
|
|
@@ -370,4 +372,4 @@ export {
|
|
|
370
372
|
runWithWrapper as default,
|
|
371
373
|
finalizeAll
|
|
372
374
|
};
|
|
373
|
-
//# sourceMappingURL=wrapper-
|
|
375
|
+
//# sourceMappingURL=wrapper-WKKUKM7T.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/e2e/wrapper.ts", "../../src/network/postGitHubComment.ts", "../../src/network/startServer.ts"],
|
|
4
|
-
"sourcesContent": ["import { spawn } from 'node:child_process';\nimport http from 'node:http';\n\nimport type { ConfigWithDefaults, E2EIntegration } from '../config/index.ts';\nimport type { EnvironmentResult } from '../environment/index.ts';\nimport createAsyncComparison from '../network/createAsyncComparison.ts';\nimport makeHappoAPIRequest from '../network/makeHappoAPIRequest.ts';\nimport postGitHubComment from '../network/postGitHubComment.ts';\nimport startServer, { type ServerInfo } from '../network/startServer.ts';\n\nlet allRequestIds: Set<number>;\n\nasync function postAsyncReport(\n requestIds: Array<number>,\n environment: EnvironmentResult,\n happoConfig: ConfigWithDefaults,\n) {\n const { afterSha, nonce, link, message } = environment;\n return await makeHappoAPIRequest(\n {\n path: `/api/async-reports/${afterSha}`,\n method: 'POST',\n json: true,\n body: {\n requestIds,\n project: happoConfig.project,\n nonce,\n link,\n message,\n },\n },\n happoConfig,\n { retryCount: 2 },\n );\n}\n\ntype Example = {\n component: string;\n variant: string;\n target: string;\n};\n\ntype Logger = Pick<Console, 'log' | 'error'>;\n\ninterface FinalizeAllOptions {\n happoConfig: ConfigWithDefaults;\n environment: EnvironmentResult;\n skippedExamplesJSON?: string;\n logger: Logger;\n}\n\nexport async function finalizeAll({\n happoConfig,\n environment,\n skippedExamplesJSON,\n logger,\n}: FinalizeAllOptions): Promise<void> {\n const { afterSha, nonce } = environment;\n\n if (!nonce) {\n throw new Error('[HAPPO] Missing --nonce argument');\n }\n\n const body: {\n project?: string | undefined;\n nonce: string;\n skippedExamples: Array<Example>;\n } = {\n project: happoConfig.project,\n nonce,\n skippedExamples: [],\n };\n\n if (skippedExamplesJSON) {\n try {\n const skippedExamples = JSON.parse(skippedExamplesJSON);\n body.skippedExamples = skippedExamples;\n } catch (e) {\n logger.error('Error when parsing --skippedExamples', skippedExamplesJSON);\n throw e;\n }\n }\n await makeHappoAPIRequest(\n {\n path: `/api/async-reports/${afterSha}/finalize`,\n method: 'POST',\n json: true,\n body,\n },\n happoConfig,\n { retryCount: 3 },\n );\n\n if (environment.beforeSha !== environment.afterSha) {\n const compareResult = await createAsyncComparison(\n happoConfig,\n environment,\n logger,\n );\n\n if (environment.link && environment.githubToken && happoConfig.githubApiUrl) {\n // githubToken and githubApiUrl are set which means that we should post\n // a comment to the PR.\n // https://docs.happo.io/docs/continuous-integration#posting-statuses-without-installing-the-happo-github-app\n await postGitHubComment({\n authToken: environment.githubToken,\n link: environment.link,\n statusImageUrl: compareResult.statusImageUrl,\n compareUrl: compareResult.compareUrl,\n githubApiUrl: happoConfig.githubApiUrl,\n });\n }\n }\n}\n\nasync function finalizeHappoReport(\n happoConfig: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n) {\n if (!allRequestIds.size) {\n logger.log(`[HAPPO] No snapshots were recorded. Ignoring.`);\n return;\n }\n const reportResult = await postAsyncReport(\n [...allRequestIds],\n environment,\n happoConfig,\n );\n\n if (!reportResult) {\n throw new Error('Failed to create async Happo report');\n }\n\n const { beforeSha, afterSha, link, message, nonce } = environment;\n\n const jobResult = await makeHappoAPIRequest(\n {\n path: `/api/jobs/${beforeSha}/${afterSha}`,\n method: 'POST',\n json: true,\n body: {\n project: happoConfig.project,\n link,\n message,\n },\n },\n happoConfig,\n { retryCount: 2 },\n );\n\n if (!jobResult) {\n throw new Error('Failed to create Happo job');\n }\n\n if (!('url' in jobResult) || typeof jobResult.url !== 'string') {\n throw new Error('Job result is missing url');\n }\n\n if (!nonce) {\n // If there is a nonce, the comparison will happen when the finalize\n // command is called.\n const compareResult = await createAsyncComparison(\n happoConfig,\n environment,\n logger,\n );\n\n if (\n compareResult &&\n environment.link &&\n environment.githubToken &&\n happoConfig.githubApiUrl\n ) {\n // githubToken and githubApiUrl is set which means that we should post\n // a comment to the PR.\n // https://docs.happo.io/docs/continuous-integration#posting-statuses-without-installing-the-happo-github-app\n await postGitHubComment({\n authToken: environment.githubToken,\n link: environment.link,\n statusImageUrl: compareResult.statusImageUrl,\n compareUrl: compareResult.compareUrl,\n githubApiUrl: happoConfig.githubApiUrl,\n });\n }\n }\n logger.log(`[HAPPO] ${jobResult.url}`);\n}\n\nfunction startE2EServer(\n environment: EnvironmentResult,\n happoConfig: ConfigWithDefaults,\n): Promise<ServerInfo> {\n function requestHandler(req: http.IncomingMessage, res: http.ServerResponse) {\n const bodyParts: Array<string> = [];\n req.on('data', (chunk: Buffer) => {\n bodyParts.push(chunk.toString());\n });\n req.on('end', async () => {\n const potentialIds = bodyParts\n .join('')\n .split('\\n')\n .filter(Boolean)\n .map((requestId) => Number.parseInt(requestId, 10));\n\n if (potentialIds.some((id) => Number.isNaN(id))) {\n res.writeHead(400);\n res.end('invalid payload');\n return;\n }\n\n for (const requestId of potentialIds) {\n allRequestIds.add(requestId);\n }\n\n const { nonce } = environment;\n if (nonce && potentialIds.length) {\n // Associate these snapRequests with the async report as soon as possible\n await postAsyncReport(potentialIds, environment, happoConfig);\n }\n res.writeHead(200);\n res.end('');\n });\n }\n return startServer(requestHandler);\n}\n\nfunction assertE2EIntegration(\n integration: NonNullable<ConfigWithDefaults['integration']>,\n): asserts integration is E2EIntegration {\n if (integration.type !== 'cypress' && integration.type !== 'playwright') {\n throw new Error(`Unsupported integration type: ${integration.type}`);\n }\n}\n\n/**\n * Runs a command with the wrapper and returns the exit code.\n *\n * @param dashdashCommandParts The command to run with the wrapper\n * @param happoConfig The Happo config\n * @param environment The environment\n * @param port The port to listen on\n * @param logger The logger\n * @returns The exit code of the command\n */\nexport default async function runWithWrapper(\n dashdashCommandParts: Array<string>,\n happoConfig: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n configFilePath: string,\n): Promise<number> {\n allRequestIds = new Set<number>();\n const e2eServer = await startE2EServer(environment, happoConfig);\n logger.log(`[HAPPO] Listening on port ${e2eServer.port}`);\n\n try {\n const exitCode = await new Promise<number>((resolve, reject) => {\n const child = spawn(dashdashCommandParts[0]!, dashdashCommandParts.slice(1), {\n stdio: 'inherit',\n env: {\n ...process.env,\n HAPPO_E2E_PORT: e2eServer.port.toString(),\n HAPPO_CONFIG_FILE: configFilePath,\n },\n shell: process.platform == 'win32',\n });\n\n child.on('error', (e) => {\n return reject(e);\n });\n\n const e2eIntegration = happoConfig.integration;\n assertE2EIntegration(e2eIntegration);\n child.on('close', async (code: number) => {\n if (code === 0 || e2eIntegration.allowFailures) {\n try {\n await finalizeHappoReport(happoConfig, environment, logger);\n } catch (e) {\n logger.error('Failed to finalize Happo report', e);\n return reject(e);\n }\n } else {\n logger.error(\n 'Command failed with exit code ${code}. Cancelling Happo job.',\n );\n try {\n await makeHappoAPIRequest(\n {\n path: `/api/jobs/${environment.beforeSha}/${environment.afterSha}/cancel`,\n method: 'POST',\n json: true,\n body: {\n status: 'failure',\n project: happoConfig.project,\n link: environment.link,\n message: `${e2eIntegration.type} run failed`,\n },\n },\n happoConfig,\n { retryCount: 3 },\n );\n } catch (e) {\n logger.error('Failed to cancel Happo job', e);\n return reject(e);\n }\n }\n resolve(code);\n });\n });\n return exitCode;\n } finally {\n allRequestIds.clear();\n await e2eServer.close();\n }\n}\n", "const REPO_URL_MATCHER = /https?:\\/\\/[^/]+\\/([^/]+)\\/([^/]+)\\/pull\\/([0-9]+)/;\n// https://github.com/lightstep/lightstep/pull/6555\n\nconst HAPPO_COMMENT_MARKER = '<!-- happo-comment -->';\n\nconst HAPPO_USER_AGENT = 'Happo client';\n\nasync function deleteExistingComments(\n normalizedGithubApiUrl: string,\n owner: string,\n repo: string,\n prNumber: number,\n authHeader: string,\n) {\n const commentsRes = await fetch(\n `${normalizedGithubApiUrl}/repos/${owner}/${repo}/issues/${prNumber}/comments`,\n {\n headers: {\n 'User-Agent': HAPPO_USER_AGENT,\n Authorization: authHeader,\n },\n method: 'GET',\n },\n );\n\n if (!commentsRes.ok) {\n throw new Error(\n `Failed to fetch existing comments: ${commentsRes.status} ${await commentsRes.text()}`,\n );\n }\n\n const comments = await commentsRes.json();\n if (!Array.isArray(comments)) {\n throw new TypeError('Comments is not an array');\n }\n const happoComments = comments.filter((comment: unknown) => {\n if (typeof comment !== 'object' || comment === null) {\n return false;\n }\n if (!('body' in comment)) {\n return false;\n }\n if (typeof comment.body !== 'string') {\n return false;\n }\n return comment.body.startsWith(HAPPO_COMMENT_MARKER);\n });\n\n console.log(\n `[HAPPO] Found ${happoComments.length} happo comments to delete out of a total of ${comments.length} comments on the PR.`,\n );\n\n await Promise.all(\n happoComments.map(async (comment) => {\n const res = await fetch(\n `${normalizedGithubApiUrl}/repos/${owner}/${repo}/issues/comments/${comment.id}`,\n {\n method: 'DELETE',\n headers: {\n 'User-Agent': HAPPO_USER_AGENT,\n Authorization: authHeader,\n },\n },\n );\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${await res.text()}`);\n }\n return res;\n }),\n );\n}\n\ninterface PostGitHubCommentOptions {\n githubApiUrl: string;\n statusImageUrl: string;\n compareUrl: string;\n link: string;\n authToken?: string | undefined;\n}\n\nexport default async function postGitHubComment({\n githubApiUrl,\n statusImageUrl,\n compareUrl,\n link,\n authToken,\n}: PostGitHubCommentOptions): Promise<boolean> {\n const matches = link.match(REPO_URL_MATCHER);\n if (!matches) {\n console.log(\n `[HAPPO] URL does not look like a github PR URL: ${link}. Skipping github comment posting...`,\n );\n return false;\n }\n\n const owner = matches[1];\n const repo = matches[2];\n const prNumber = Number.parseInt(matches[3] || '', 10);\n\n if (!owner) {\n throw new Error('Missing owner');\n }\n if (!repo) {\n throw new Error('Missing repo');\n }\n if (Number.isNaN(prNumber)) {\n throw new TypeError('PR number is not a number');\n }\n\n const normalizedGithubApiUrl = githubApiUrl.replace(/\\/$/, '');\n\n const authHeader = `Bearer ${authToken}`;\n\n console.log('[HAPPO] Deleting existing happo comments...');\n await deleteExistingComments(\n normalizedGithubApiUrl,\n owner,\n repo,\n prNumber,\n authHeader,\n );\n\n const body = `${HAPPO_COMMENT_MARKER}\\n[](${compareUrl})`;\n const res = await fetch(\n `${normalizedGithubApiUrl}/repos/${owner}/${repo}/issues/${prNumber}/comments`,\n {\n method: 'POST',\n headers: {\n 'User-Agent': HAPPO_USER_AGENT,\n Authorization: authHeader,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ body }),\n },\n );\n if (!res.ok) {\n throw new Error(\n `Failed to post github comment: ${res.status} ${await res.text()}`,\n );\n }\n\n console.log(\n `[HAPPO] Posted github comment successfully. Response is`,\n await res.json(),\n );\n\n return true;\n}\n", "import http from 'node:http';\n\nexport interface ServerInfo {\n close: () => Promise<void>;\n port: number;\n}\n\nexport default function startServer(\n requestHandler: (req: http.IncomingMessage, res: http.ServerResponse) => void,\n { port }: { port?: number | undefined } = {},\n): Promise<ServerInfo> {\n return new Promise((resolve) => {\n const server = http.createServer(requestHandler);\n server.listen(port, () => {\n const address = server.address();\n if (!address || typeof address === 'string') {\n throw new Error('Expected server address to be AddressInfo');\n }\n resolve({\n close: () =>\n new Promise((resolve, reject) =>\n server.close((err) => (err ? reject(err) : resolve())),\n ),\n port: address.port,\n });\n });\n });\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { spawn } from 'node:child_process';\nimport http from 'node:http';\n\nimport type { ConfigWithDefaults, E2EIntegration } from '../config/index.ts';\nimport type { EnvironmentResult } from '../environment/index.ts';\nimport createAsyncComparison from '../network/createAsyncComparison.ts';\nimport makeHappoAPIRequest from '../network/makeHappoAPIRequest.ts';\nimport postGitHubComment from '../network/postGitHubComment.ts';\nimport startServer, { type ServerInfo } from '../network/startServer.ts';\n\nlet allRequestIds: Set<number>;\n\nasync function postAsyncReport(\n requestIds: Array<number>,\n environment: EnvironmentResult,\n happoConfig: ConfigWithDefaults,\n) {\n const { afterSha, nonce, link, message } = environment;\n return await makeHappoAPIRequest(\n {\n path: `/api/async-reports/${afterSha}`,\n method: 'POST',\n json: true,\n body: {\n requestIds,\n project: happoConfig.project,\n nonce,\n link,\n message,\n },\n },\n happoConfig,\n { retryCount: 2 },\n );\n}\n\ntype Example = {\n component: string;\n variant: string;\n target: string;\n};\n\ntype Logger = Pick<Console, 'log' | 'error'>;\n\ninterface FinalizeAllOptions {\n happoConfig: ConfigWithDefaults;\n environment: EnvironmentResult;\n skippedExamplesJSON?: string;\n logger: Logger;\n}\n\nexport async function finalizeAll({\n happoConfig,\n environment,\n skippedExamplesJSON,\n logger,\n}: FinalizeAllOptions): Promise<void> {\n const { afterSha, nonce } = environment;\n\n if (!nonce) {\n throw new Error('[HAPPO] Missing --nonce argument');\n }\n\n const body: {\n project?: string | undefined;\n nonce: string;\n skippedExamples: Array<Example>;\n } = {\n project: happoConfig.project,\n nonce,\n skippedExamples: [],\n };\n\n if (skippedExamplesJSON) {\n try {\n const skippedExamples = JSON.parse(skippedExamplesJSON);\n body.skippedExamples = skippedExamples;\n } catch (e) {\n logger.error('Error when parsing --skippedExamples', skippedExamplesJSON);\n throw e;\n }\n }\n await makeHappoAPIRequest(\n {\n path: `/api/async-reports/${afterSha}/finalize`,\n method: 'POST',\n json: true,\n body,\n },\n happoConfig,\n { retryCount: 3 },\n );\n\n if (environment.beforeSha !== environment.afterSha) {\n const compareResult = await createAsyncComparison(\n happoConfig,\n environment,\n logger,\n );\n\n if (environment.link && environment.githubToken && happoConfig.githubApiUrl) {\n // githubToken and githubApiUrl are set which means that we should post\n // a comment to the PR.\n // https://docs.happo.io/docs/continuous-integration#posting-statuses-without-installing-the-happo-github-app\n await postGitHubComment({\n authToken: environment.githubToken,\n link: environment.link,\n statusImageUrl: compareResult.statusImageUrl,\n compareUrl: compareResult.compareUrl,\n githubApiUrl: happoConfig.githubApiUrl,\n });\n }\n }\n}\n\nasync function finalizeHappoReport(\n happoConfig: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n) {\n if (!allRequestIds.size) {\n logger.log(`[HAPPO] No snapshots were recorded. Ignoring.`);\n return;\n }\n const reportResult = await postAsyncReport(\n [...allRequestIds],\n environment,\n happoConfig,\n );\n\n if (!reportResult) {\n throw new Error('Failed to create async Happo report');\n }\n\n const { beforeSha, afterSha, link, message, nonce } = environment;\n\n const jobResult = await makeHappoAPIRequest(\n {\n path: `/api/jobs/${beforeSha}/${afterSha}`,\n method: 'POST',\n json: true,\n body: {\n project: happoConfig.project,\n link,\n message,\n },\n },\n happoConfig,\n { retryCount: 2 },\n );\n\n if (!jobResult) {\n throw new Error('Failed to create Happo job');\n }\n\n if (!('url' in jobResult) || typeof jobResult.url !== 'string') {\n throw new Error('Job result is missing url');\n }\n\n if (!nonce) {\n // If there is a nonce, the comparison will happen when the finalize\n // command is called.\n const compareResult = await createAsyncComparison(\n happoConfig,\n environment,\n logger,\n );\n\n if (\n compareResult &&\n environment.link &&\n environment.githubToken &&\n happoConfig.githubApiUrl\n ) {\n // githubToken and githubApiUrl is set which means that we should post\n // a comment to the PR.\n // https://docs.happo.io/docs/continuous-integration#posting-statuses-without-installing-the-happo-github-app\n await postGitHubComment({\n authToken: environment.githubToken,\n link: environment.link,\n statusImageUrl: compareResult.statusImageUrl,\n compareUrl: compareResult.compareUrl,\n githubApiUrl: happoConfig.githubApiUrl,\n });\n }\n }\n logger.log(`[HAPPO] ${jobResult.url}`);\n}\n\nfunction startE2EServer(\n environment: EnvironmentResult,\n happoConfig: ConfigWithDefaults,\n): Promise<ServerInfo> {\n function requestHandler(req: http.IncomingMessage, res: http.ServerResponse) {\n const bodyParts: Array<string> = [];\n req.on('data', (chunk: Buffer) => {\n bodyParts.push(chunk.toString());\n });\n req.on('end', async () => {\n const potentialIds = bodyParts\n .join('')\n .split('\\n')\n .filter(Boolean)\n .map((requestId) => Number.parseInt(requestId, 10));\n\n if (potentialIds.some((id) => Number.isNaN(id))) {\n res.writeHead(400);\n res.end('invalid payload');\n return;\n }\n\n for (const requestId of potentialIds) {\n allRequestIds.add(requestId);\n }\n\n const { nonce } = environment;\n if (nonce && potentialIds.length) {\n // Associate these snapRequests with the async report as soon as possible\n await postAsyncReport(potentialIds, environment, happoConfig);\n }\n res.writeHead(200);\n res.end('');\n });\n }\n return startServer(requestHandler);\n}\n\nfunction assertE2EIntegration(\n integration: NonNullable<ConfigWithDefaults['integration']>,\n): asserts integration is E2EIntegration {\n if (integration.type !== 'cypress' && integration.type !== 'playwright') {\n throw new Error(`Unsupported integration type: ${integration.type}`);\n }\n}\n\n/**\n * Runs a command with the wrapper and returns the exit code.\n *\n * @param dashdashCommandParts The command to run with the wrapper\n * @param happoConfig The Happo config\n * @param environment The environment\n * @param port The port to listen on\n * @param logger The logger\n * @returns The exit code of the command\n */\nexport default async function runWithWrapper(\n dashdashCommandParts: Array<string>,\n happoConfig: ConfigWithDefaults,\n environment: EnvironmentResult,\n logger: Logger,\n configFilePath: string,\n): Promise<number> {\n allRequestIds = new Set<number>();\n const e2eServer = await startE2EServer(environment, happoConfig);\n logger.log(`[HAPPO] Listening on port ${e2eServer.port}`);\n\n try {\n const exitCode = await new Promise<number>((resolve, reject) => {\n const child = spawn(dashdashCommandParts[0]!, dashdashCommandParts.slice(1), {\n stdio: 'inherit',\n env: {\n ...process.env,\n HAPPO_E2E_PORT: e2eServer.port.toString(),\n HAPPO_CONFIG_FILE: configFilePath,\n HAPPO_API_KEY: happoConfig.apiKey,\n HAPPO_API_SECRET: happoConfig.apiSecret,\n },\n shell: process.platform == 'win32',\n });\n\n child.on('error', (e) => {\n return reject(e);\n });\n\n const e2eIntegration = happoConfig.integration;\n assertE2EIntegration(e2eIntegration);\n child.on('close', async (code: number) => {\n if (code === 0 || e2eIntegration.allowFailures) {\n try {\n await finalizeHappoReport(happoConfig, environment, logger);\n } catch (e) {\n logger.error('Failed to finalize Happo report', e);\n return reject(e);\n }\n } else {\n logger.error(\n 'Command failed with exit code ${code}. Cancelling Happo job.',\n );\n try {\n await makeHappoAPIRequest(\n {\n path: `/api/jobs/${environment.beforeSha}/${environment.afterSha}/cancel`,\n method: 'POST',\n json: true,\n body: {\n status: 'failure',\n project: happoConfig.project,\n link: environment.link,\n message: `${e2eIntegration.type} run failed`,\n },\n },\n happoConfig,\n { retryCount: 3 },\n );\n } catch (e) {\n logger.error('Failed to cancel Happo job', e);\n return reject(e);\n }\n }\n resolve(code);\n });\n });\n return exitCode;\n } finally {\n allRequestIds.clear();\n await e2eServer.close();\n }\n}\n", "const REPO_URL_MATCHER = /https?:\\/\\/[^/]+\\/([^/]+)\\/([^/]+)\\/pull\\/([0-9]+)/;\n// https://github.com/lightstep/lightstep/pull/6555\n\nconst HAPPO_COMMENT_MARKER = '<!-- happo-comment -->';\n\nconst HAPPO_USER_AGENT = 'Happo client';\n\nasync function deleteExistingComments(\n normalizedGithubApiUrl: string,\n owner: string,\n repo: string,\n prNumber: number,\n authHeader: string,\n) {\n const commentsRes = await fetch(\n `${normalizedGithubApiUrl}/repos/${owner}/${repo}/issues/${prNumber}/comments`,\n {\n headers: {\n 'User-Agent': HAPPO_USER_AGENT,\n Authorization: authHeader,\n },\n method: 'GET',\n },\n );\n\n if (!commentsRes.ok) {\n throw new Error(\n `Failed to fetch existing comments: ${commentsRes.status} ${await commentsRes.text()}`,\n );\n }\n\n const comments = await commentsRes.json();\n if (!Array.isArray(comments)) {\n throw new TypeError('Comments is not an array');\n }\n const happoComments = comments.filter((comment: unknown) => {\n if (typeof comment !== 'object' || comment === null) {\n return false;\n }\n if (!('body' in comment)) {\n return false;\n }\n if (typeof comment.body !== 'string') {\n return false;\n }\n return comment.body.startsWith(HAPPO_COMMENT_MARKER);\n });\n\n console.log(\n `[HAPPO] Found ${happoComments.length} happo comments to delete out of a total of ${comments.length} comments on the PR.`,\n );\n\n await Promise.all(\n happoComments.map(async (comment) => {\n const res = await fetch(\n `${normalizedGithubApiUrl}/repos/${owner}/${repo}/issues/comments/${comment.id}`,\n {\n method: 'DELETE',\n headers: {\n 'User-Agent': HAPPO_USER_AGENT,\n Authorization: authHeader,\n },\n },\n );\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${await res.text()}`);\n }\n return res;\n }),\n );\n}\n\ninterface PostGitHubCommentOptions {\n githubApiUrl: string;\n statusImageUrl: string;\n compareUrl: string;\n link: string;\n authToken?: string | undefined;\n}\n\nexport default async function postGitHubComment({\n githubApiUrl,\n statusImageUrl,\n compareUrl,\n link,\n authToken,\n}: PostGitHubCommentOptions): Promise<boolean> {\n const matches = link.match(REPO_URL_MATCHER);\n if (!matches) {\n console.log(\n `[HAPPO] URL does not look like a github PR URL: ${link}. Skipping github comment posting...`,\n );\n return false;\n }\n\n const owner = matches[1];\n const repo = matches[2];\n const prNumber = Number.parseInt(matches[3] || '', 10);\n\n if (!owner) {\n throw new Error('Missing owner');\n }\n if (!repo) {\n throw new Error('Missing repo');\n }\n if (Number.isNaN(prNumber)) {\n throw new TypeError('PR number is not a number');\n }\n\n const normalizedGithubApiUrl = githubApiUrl.replace(/\\/$/, '');\n\n const authHeader = `Bearer ${authToken}`;\n\n console.log('[HAPPO] Deleting existing happo comments...');\n await deleteExistingComments(\n normalizedGithubApiUrl,\n owner,\n repo,\n prNumber,\n authHeader,\n );\n\n const body = `${HAPPO_COMMENT_MARKER}\\n[](${compareUrl})`;\n const res = await fetch(\n `${normalizedGithubApiUrl}/repos/${owner}/${repo}/issues/${prNumber}/comments`,\n {\n method: 'POST',\n headers: {\n 'User-Agent': HAPPO_USER_AGENT,\n Authorization: authHeader,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ body }),\n },\n );\n if (!res.ok) {\n throw new Error(\n `Failed to post github comment: ${res.status} ${await res.text()}`,\n );\n }\n\n console.log(\n `[HAPPO] Posted github comment successfully. Response is`,\n await res.json(),\n );\n\n return true;\n}\n", "import http from 'node:http';\n\nexport interface ServerInfo {\n close: () => Promise<void>;\n port: number;\n}\n\nexport default function startServer(\n requestHandler: (req: http.IncomingMessage, res: http.ServerResponse) => void,\n { port }: { port?: number | undefined } = {},\n): Promise<ServerInfo> {\n return new Promise((resolve) => {\n const server = http.createServer(requestHandler);\n server.listen(port, () => {\n const address = server.address();\n if (!address || typeof address === 'string') {\n throw new Error('Expected server address to be AddressInfo');\n }\n resolve({\n close: () =>\n new Promise((resolve, reject) =>\n server.close((err) => (err ? reject(err) : resolve())),\n ),\n port: address.port,\n });\n });\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["resolve"]
|
|
7
7
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { EnvironmentResult } from '../environment/index.ts';
|
|
2
|
+
import type { Logger } from '../isomorphic/types.ts';
|
|
1
3
|
import type { ConfigWithDefaults } from './index.ts';
|
|
2
4
|
export declare function findConfigFile(): string;
|
|
3
|
-
export declare function loadConfigFile(configFilePath: string): Promise<ConfigWithDefaults>;
|
|
5
|
+
export declare function loadConfigFile(configFilePath: string, environment?: Pick<EnvironmentResult, 'link'>, logger?: Logger): Promise<ConfigWithDefaults>;
|
|
4
6
|
//# sourceMappingURL=loadConfig.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadConfig.d.ts","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"loadConfig.d.ts","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AAazE,wBAAgB,cAAc,IAAI,MAAM,CAcvC;AAgCD,wBAAsB,cAAc,CAClC,cAAc,EAAE,MAAM,EACtB,WAAW,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,EAC7C,MAAM,GAAE,MAAgB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAsF7B"}
|