diff --git a/README.md b/README.md index c49f751..a72c4de 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Use this entry point when you need to process or manipulate reports in browser-b - **`fetchTestDurations()`** - Fetch historical test durations from Flakiness.io and return a report enriched with timings - **`showReport()`** - Start a local server and open the report in your browser - **`showReportCommand()`** - Build a shell command for opening the report later with the Flakiness CLI +- **`showReportMessage()`** - Build the message a runner prints after writing a report (CI-aware: a one-liner with the report path on CI, open-in-CLI instructions otherwise) - **`uploadReport()`** - Upload reports and attachments to Flakiness.io - **`writeReport()`** - Write reports to disk in the standard Flakiness report format diff --git a/src/_internalUtils.ts b/src/_internalUtils.ts index 29471b6..c8724bc 100644 --- a/src/_internalUtils.ts +++ b/src/_internalUtils.ts @@ -97,6 +97,19 @@ export function sha1File(filePath: string): Promise { }); } +/** + * Whether the current process is running on CI. + * + * Virtually every CI provider (GitHub Actions, GitLab, CircleCI, …) sets the `CI` + * environment variable, usually to `true` or `1`. An explicit `false`/`0`/empty + * value is treated as an opt-out rather than as "on" — `process.env.CI` is a + * non-empty truthy string in those cases too, so a plain truthiness check is wrong. + */ +export function isCI(): boolean { + const ci = (process.env.CI ?? '').toLowerCase(); + return ci !== '' && ci !== 'false' && ci !== '0'; +} + export function randomUUIDBase62(): string { const BASE62_CHARSET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let num = BigInt('0x' + crypto.randomUUID().replace(/-/g, '')); diff --git a/src/index.ts b/src/index.ts index cddfa34..134b33c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,5 +11,6 @@ export { fetchTestDurations, type FetchTestDurationsOptions } from './fetchTestD export { readReport } from './readReport.js'; export { showReport } from './showReport.js'; export { showReportCommand } from './showReportCommand.js'; +export { showReportMessage } from './showReportMessage.js'; export { uploadReport } from './uploadReport.js'; export { writeReport } from './writeReport.js'; diff --git a/src/showReportMessage.ts b/src/showReportMessage.ts new file mode 100644 index 0000000..92ffa7d --- /dev/null +++ b/src/showReportMessage.ts @@ -0,0 +1,31 @@ +import path from 'node:path'; +import { styleText } from 'node:util'; +import { isCI } from './_internalUtils.js'; +import { showReportCommand } from './showReportCommand.js'; + +/** + * Builds the human-facing message that test runners print after writing a report. + * + * In interactive environments this returns instructions for opening the report + * locally with the Flakiness CLI. On CI (the `CI` environment variable is set to + * anything other than `false`/`0`), where launching the local viewer isn't useful, + * it returns a one-liner pointing at the folder the report was written to instead. + * + * @param {string} reportFolder - Absolute or relative path to the report folder. + * + * @returns {string} A ready-to-print message. + * + * @example + * ```typescript + * console.log(showReportMessage('./flakiness-report')); + * // Interactive: "To open last Flakiness report, run:\n\n npx flakiness show" + * // On CI: "Flakiness report written to /abs/path/flakiness-report" + * ``` + */ +export function showReportMessage(reportFolder: string): string { + if (isCI()) + return `Flakiness report written to ${path.resolve(reportFolder)}`; + + const command = showReportCommand(reportFolder); + return `To open last Flakiness report, run:\n\n ${styleText('cyan', command)}`; +} diff --git a/src/uploadReport.ts b/src/uploadReport.ts index 365ae5f..2fd82ac 100644 --- a/src/uploadReport.ts +++ b/src/uploadReport.ts @@ -3,7 +3,7 @@ import assert from 'assert'; import fs from 'fs'; import { URL } from 'url'; import { GithubOIDC } from './githubOIDC.js'; -import { compressTextAsync, getJSON, putBuffer, sha1File, sha1Text } from './_internalUtils.js'; +import { compressTextAsync, getJSON, isCI, putBuffer, sha1File, sha1Text } from './_internalUtils.js'; type ReportUploaderOptions = { flakinessEndpoint: string; @@ -246,7 +246,7 @@ export async function uploadReport( if (!flakinessAccessToken && githubOIDC) { if (!report.flakinessProject) { const reason = '`flakinessProject` is not configured to upload using Github OIDC.'; - if (process.env.CI) + if (isCI()) logger.warn(`[flakiness.io] ⚠ Skipping upload: ${reason}`); return { status: 'skipped', reason }; } @@ -265,7 +265,7 @@ export async function uploadReport( if (!flakinessAccessToken) { const reason = 'No FLAKINESS_ACCESS_TOKEN found'; - if (process.env.CI) + if (isCI()) logger.warn(`[flakiness.io] ⚠ Skipping upload: ${reason}`); return { status: 'skipped', reason }; }