diff --git a/README.md b/README.md index bdced5c..f92e3a2 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ jobs: | time | Test execution time [ms] | | url | Check run URL | | url_html | Check run URL HTML | +| slug_prefix| Random anchor links slug prefix generated for the summary headers | ## Supported formats diff --git a/__tests__/utils/slugger.test.ts b/__tests__/utils/slugger.test.ts new file mode 100644 index 0000000..e2ad534 --- /dev/null +++ b/__tests__/utils/slugger.test.ts @@ -0,0 +1,28 @@ +import {DEFAULT_OPTIONS} from '../../src/report/get-report.js' +import {slug} from '../../src/utils/slugger.js' + +describe('slugger', () => { + it('adds prefix from report options to generated slug', () => { + const result = slug('r0s1', { + ...DEFAULT_OPTIONS, + slugPrefix: 'prefix-' + }) + + expect(result).toEqual({ + id: 'user-content-prefix-r0s1', + link: '#user-content-prefix-r0s1' + }) + }) + + it('sanitizes custom prefix using existing slug normalization', () => { + const result = slug('r0', { + ...DEFAULT_OPTIONS, + slugPrefix: ' my /custom_prefix?.' + }) + + expect(result).toEqual({ + id: 'user-content-my-customprefix-r0', + link: '#user-content-my-customprefix-r0' + }) + }) +}) diff --git a/action.yml b/action.yml index 8dbc85c..f3bab88 100644 --- a/action.yml +++ b/action.yml @@ -122,6 +122,8 @@ outputs: description: Check run URL url_html: description: Check run URL HTML + slug_prefix: + description: Random prefix added to generated report anchor slugs for this action run runs: using: 'node20' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 1e21e07..a3e0fca 100644 --- a/dist/index.js +++ b/dist/index.js @@ -55471,6 +55471,8 @@ function getOctokit(token, options, ...additionalPlugins) { return new GitHubWithPlugins(getOctokitOptions(token, options)); } //# sourceMappingURL=github.js.map +// EXTERNAL MODULE: external "node:crypto" +var external_node_crypto_ = __nccwpck_require__(7598); // EXTERNAL MODULE: ./node_modules/adm-zip/adm-zip.js var adm_zip = __nccwpck_require__(1316); // EXTERNAL MODULE: ./node_modules/picomatch/index.js @@ -55957,7 +55959,7 @@ function getExceptionSource(stackTrace, trackedFiles, getRelativePath) { ;// CONCATENATED MODULE: ./lib/utils/slugger.js function slug(name, options) { - const slugId = name + const slugId = `${options.slugPrefix}${name}` .trim() .replace(/_/g, '') .replace(/[./\\]/g, '-') @@ -55979,6 +55981,7 @@ const MAX_ACTIONS_SUMMARY_LENGTH = 1048576; const DEFAULT_OPTIONS = { listSuites: 'all', listTests: 'all', + slugPrefix: '', baseUrl: '', onlySummary: false, useActionsSummary: true, @@ -57951,6 +57954,7 @@ class NetteTesterJunitParser { + async function main() { try { @@ -57978,6 +57982,7 @@ class TestReporter { workDirInput = getInput('working-directory', { required: false }); onlySummary = getInput('only-summary', { required: false }) === 'true'; useActionsSummary = getInput('use-actions-summary', { required: false }) === 'true'; + slugPrefix = `tr-${(0,external_node_crypto_.randomBytes)(4).toString('base64url')}-`; badgeTitle = getInput('badge-title', { required: false }); reportTitle = getInput('report-title', { required: false }); collapsed = getInput('collapsed', { required: false }); @@ -58051,6 +58056,7 @@ class TestReporter { setOutput('failed', failed); setOutput('skipped', skipped); setOutput('time', time); + setOutput('slug_prefix', this.slugPrefix); if (this.failOnError && isFailed) { setFailed(`Failed test were found and 'fail-on-error' option is set to ${this.failOnError}`); return; @@ -58077,7 +58083,7 @@ class TestReporter { throw error; } } - const { listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this; + const { listSuites, listTests, slugPrefix, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this; const passed = results.reduce((sum, tr) => sum + tr.passed, 0); const failed = results.reduce((sum, tr) => sum + tr.failed, 0); const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0); @@ -58087,6 +58093,7 @@ class TestReporter { const summary = getReport(results, { listSuites, listTests, + slugPrefix, baseUrl, onlySummary, useActionsSummary, @@ -58115,6 +58122,7 @@ class TestReporter { const summary = getReport(results, { listSuites, listTests, + slugPrefix, baseUrl, onlySummary, useActionsSummary, diff --git a/src/main.ts b/src/main.ts index 888fd73..fc6ea16 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import * as core from '@actions/core' import * as github from '@actions/github' import {GitHub} from '@actions/github/lib/utils' +import {randomBytes} from 'node:crypto' import {ArtifactProvider} from './input-providers/artifact-provider.js' import {LocalFileProvider} from './input-providers/local-file-provider.js' @@ -49,6 +50,7 @@ class TestReporter { readonly workDirInput = core.getInput('working-directory', {required: false}) readonly onlySummary = core.getInput('only-summary', {required: false}) === 'true' readonly useActionsSummary = core.getInput('use-actions-summary', {required: false}) === 'true' + readonly slugPrefix = `tr-${randomBytes(4).toString('base64url')}-` readonly badgeTitle = core.getInput('badge-title', {required: false}) readonly reportTitle = core.getInput('report-title', {required: false}) readonly collapsed = core.getInput('collapsed', {required: false}) as 'auto' | 'always' | 'never' @@ -144,6 +146,7 @@ class TestReporter { core.setOutput('failed', failed) core.setOutput('skipped', skipped) core.setOutput('time', time) + core.setOutput('slug_prefix', this.slugPrefix) if (this.failOnError && isFailed) { core.setFailed(`Failed test were found and 'fail-on-error' option is set to ${this.failOnError}`) @@ -174,7 +177,7 @@ class TestReporter { } } - const {listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed} = this + const {listSuites, listTests, slugPrefix, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed} = this const passed = results.reduce((sum, tr) => sum + tr.passed, 0) const failed = results.reduce((sum, tr) => sum + tr.failed, 0) @@ -188,6 +191,7 @@ class TestReporter { { listSuites, listTests, + slugPrefix, baseUrl, onlySummary, useActionsSummary, @@ -219,6 +223,7 @@ class TestReporter { const summary = getReport(results, { listSuites, listTests, + slugPrefix, baseUrl, onlySummary, useActionsSummary, diff --git a/src/report/get-report.ts b/src/report/get-report.ts index 63617b0..325e28c 100644 --- a/src/report/get-report.ts +++ b/src/report/get-report.ts @@ -11,6 +11,7 @@ const MAX_ACTIONS_SUMMARY_LENGTH = 1048576 export interface ReportOptions { listSuites: 'all' | 'failed' | 'none' listTests: 'all' | 'failed' | 'none' + slugPrefix: string baseUrl: string onlySummary: boolean useActionsSummary: boolean @@ -22,6 +23,7 @@ export interface ReportOptions { export const DEFAULT_OPTIONS: ReportOptions = { listSuites: 'all', listTests: 'all', + slugPrefix: '', baseUrl: '', onlySummary: false, useActionsSummary: true, diff --git a/src/utils/slugger.ts b/src/utils/slugger.ts index 656a81b..f12b49e 100644 --- a/src/utils/slugger.ts +++ b/src/utils/slugger.ts @@ -4,7 +4,7 @@ import {ReportOptions} from '../report/get-report.js' export function slug(name: string, options: ReportOptions): {id: string; link: string} { - const slugId = name + const slugId = `${options.slugPrefix}${name}` .trim() .replace(/_/g, '') .replace(/[./\\]/g, '-')