diff --git a/.github/workflows/report_test.yml b/.github/workflows/report_test.yml index ffbb044..60475d5 100644 --- a/.github/workflows/report_test.yml +++ b/.github/workflows/report_test.yml @@ -69,6 +69,8 @@ jobs: gh_pages: 'gh-pages-dir' report_dir: 'allure-results' list_dirs: ${{ github.ref == 'refs/heads/main' }} + cleanup_enabled: true + max_reports: 5 - name: Git Commit and Push Action uses: mgrybyk/git-commit-pull-push-action@v1 diff --git a/Dockerfile b/Dockerfile index 15c87da..05ea84b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM timbru31/java-node:17-alpine-jre-20 -ARG RELEASE=2.26.0 +ARG RELEASE=2.27.0 ARG ALLURE_REPO=https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline RUN echo "===============" && \ @@ -8,6 +8,7 @@ RUN echo "===============" && \ echo NodeJS: $(node --version) && \ java -version && \ echo "===============" && \ + apk update && apk add git && \ wget --no-verbose -O /tmp/allure-$RELEASE.tgz $ALLURE_REPO/$RELEASE/allure-commandline-$RELEASE.tgz && \ tar -xf /tmp/allure-$RELEASE.tgz && \ rm -rf /tmp/* && \ diff --git a/README.md b/README.md index b919f58..1624c26 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Allure Report with history per branch (type: `docker`) See examples: - [Allure History List](https://mgrybyk.github.io/allure-report-branch-action/allure-action/main/self-test/) -- [Allure Report](https://mgrybyk.github.io/allure-report-branch-action/allure-action/main/self-test/5931206129_1692650191550/) +- [Allure Report](https://mgrybyk.github.io/allure-report-branch-action/allure-action/main/self-test/7643572205_1706116100424/) - [Browse different branches](https://mgrybyk.github.io/allure-report-branch-action/allure-action/) - [Pull Request Comment Example](https://github.com/mgrybyk/allure-report-branch-action/pull/12) @@ -106,13 +106,14 @@ The `allure-report-branch-action` is designed as a JavaScript action wrapped wit As far as `docker` action runs in linux environments only, it's required to do some extra steps for users running Windows and MacOS workflows. See [Types of actions](https://docs.github.com/en/actions/creating-actions/about-custom-actions#types-of-actions) for more details. - option 1: using upload/download artifacts. See [simple-elf/allure-report-action/issues/28#issuecomment-1139332329](https://github.com/simple-elf/allure-report-action/issues/28#issuecomment-1139332329) -- option 2: use JS version of this action (raise an issue and I'll publish it). In this case you'll have to install Java and download download allure-commandline yourself. +- option 2: use JS version of this action [mgrybyk/allure-report-branch-js-action](https://github.com/mgrybyk/allure-report-branch-js-action). ## Credits - [docker-java-node](https://github.com/timbru31/docker-java-node) for building Dockerimage with Java and NodeJS together - [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) for building Github Action that comments the linked PRs -## Upcoming features +## Planned features -- cleanup old reports +- cleanup `data.json` file per report. Raise an issue if you're interested! +- think how to deal with branch cleaup without using url. Feel free to pick up. diff --git a/action.yml b/action.yml index 0d2fd29..66c5f1f 100644 --- a/action.yml +++ b/action.yml @@ -21,6 +21,14 @@ inputs: description: 'Write index.html to the Github Action folders. Might cause concurrency issues!' required: false default: false + cleanup_enabled: + description: 'Cleanup reports from deleted branches or if reports numbers is too big' + required: false + default: false + max_reports: + description: 'Max reports to keep per branch/report. Make sure to enable cleanup_enabled' + required: false + default: 100 outputs: report_url: description: 'Published Allure Report url' diff --git a/dist/index.js b/dist/index.js index f0b8ffa..b97a799 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29293,16 +29293,21 @@ function wrappy (fn, cb) { /***/ ((module, __unused_webpack___webpack_exports__, __nccwpck_require__) => { __nccwpck_require__.a(module, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try { -/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(2186); -/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(_actions_core__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(5438); -/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nccwpck_require__.n(_actions_github__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(7436); -/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__nccwpck_require__.n(_actions_io__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_3__ = __nccwpck_require__(4362); -/* harmony import */ var _src_allure_js__WEBPACK_IMPORTED_MODULE_4__ = __nccwpck_require__(8791); -/* harmony import */ var _src_helpers_js__WEBPACK_IMPORTED_MODULE_6__ = __nccwpck_require__(3015); -/* harmony import */ var _src_isFileExists_js__WEBPACK_IMPORTED_MODULE_5__ = __nccwpck_require__(2139); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(1017); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(2186); +/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nccwpck_require__.n(_actions_core__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(5438); +/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__nccwpck_require__.n(_actions_github__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_3__ = __nccwpck_require__(7436); +/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__nccwpck_require__.n(_actions_io__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_4__ = __nccwpck_require__(4362); +/* harmony import */ var _src_allure_js__WEBPACK_IMPORTED_MODULE_5__ = __nccwpck_require__(9302); +/* harmony import */ var _src_helpers_js__WEBPACK_IMPORTED_MODULE_8__ = __nccwpck_require__(3015); +/* harmony import */ var _src_isFileExists_js__WEBPACK_IMPORTED_MODULE_6__ = __nccwpck_require__(2139); +/* harmony import */ var _src_cleanup_js__WEBPACK_IMPORTED_MODULE_7__ = __nccwpck_require__(2193); + + @@ -29314,80 +29319,89 @@ const baseDir = 'allure-action'; try { const runTimestamp = Date.now(); // vars - const sourceReportDir = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('report_dir'); - const ghPagesPath = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('gh_pages'); - const reportId = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('report_id'); - const listDirs = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('list_dirs') == 'true'; - const branchName = (0,_src_helpers_js__WEBPACK_IMPORTED_MODULE_6__/* .getBranchName */ .L)(_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.ref, _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.payload.pull_request); - const reportBaseDir = `${ghPagesPath}/${baseDir}/${branchName}/${reportId}`; + const sourceReportDir = _actions_core__WEBPACK_IMPORTED_MODULE_1__.getInput('report_dir'); + const ghPagesPath = _actions_core__WEBPACK_IMPORTED_MODULE_1__.getInput('gh_pages'); + const reportId = _actions_core__WEBPACK_IMPORTED_MODULE_1__.getInput('report_id'); + const listDirs = _actions_core__WEBPACK_IMPORTED_MODULE_1__.getInput('list_dirs') == 'true'; + const cleanupEnabled = _actions_core__WEBPACK_IMPORTED_MODULE_1__.getInput('cleanup_enabled') == 'true'; + const maxReports = parseInt(_actions_core__WEBPACK_IMPORTED_MODULE_1__.getInput('max_reports'), 10); + const branchName = (0,_src_helpers_js__WEBPACK_IMPORTED_MODULE_8__/* .getBranchName */ .L)(_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.ref, _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.payload.pull_request); + const ghPagesBaseDir = path__WEBPACK_IMPORTED_MODULE_0__.join(ghPagesPath, baseDir); + const reportBaseDir = path__WEBPACK_IMPORTED_MODULE_0__.join(ghPagesBaseDir, branchName, reportId); /** * `runId` is unique but won't change on job re-run * `runNumber` is not unique and resets from time to time * that's why the `runTimestamp` is used to guarantee uniqueness */ - const runUniqueId = `${_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.runId}_${runTimestamp}`; - const reportDir = `${reportBaseDir}/${runUniqueId}`; + const runUniqueId = `${_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.runId}_${runTimestamp}`; + const reportDir = path__WEBPACK_IMPORTED_MODULE_0__.join(reportBaseDir, runUniqueId); // urls - const githubActionRunUrl = `https://github.com/${_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo.owner}/${_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo.repo}/actions/runs/${_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.runId}`; - const ghPagesUrl = `https://${_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo.owner}.github.io/${_actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo.repo}`; - const ghPagesBaseDir = `${ghPagesUrl}/${baseDir}/${branchName}/${reportId}`.replaceAll(' ', '%20'); - const ghPagesReportDir = `${ghPagesBaseDir}/${runUniqueId}`.replaceAll(' ', '%20'); + const githubActionRunUrl = `https://github.com/${_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo.owner}/${_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo.repo}/actions/runs/${_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.runId}`; + const ghPagesUrl = `https://${_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo.owner}.github.io/${_actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo.repo}`; + const ghPagesBaseUrl = `${ghPagesUrl}/${baseDir}/${branchName}/${reportId}`.replaceAll(' ', '%20'); + const ghPagesReportUrl = `${ghPagesBaseUrl}/${runUniqueId}`.replaceAll(' ', '%20'); // log console.log({ report_dir: sourceReportDir, gh_pages: ghPagesPath, report_id: reportId, runUniqueId, - ref: _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.ref, - repo: _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo, + ref: _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.ref, + repo: _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo, branchName, reportBaseDir, reportDir, - report_url: ghPagesReportDir, + report_url: ghPagesReportUrl, listDirs, + cleanupEnabled, + maxReports, }); - if (!(await (0,_src_isFileExists_js__WEBPACK_IMPORTED_MODULE_5__/* .isFileExist */ .e)(ghPagesPath))) { + if (!(await (0,_src_isFileExists_js__WEBPACK_IMPORTED_MODULE_6__/* .isFileExist */ .e)(ghPagesPath))) { throw new Error("Folder with gh-pages branch doesn't exist: " + ghPagesPath); } - if (!(await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .isAllureResultsOk */ .aL)(sourceReportDir))) { + if (!(await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .isAllureResultsOk */ .aL)(sourceReportDir))) { throw new Error('There were issues with the allure-results, see error above.'); } // action - await _actions_io__WEBPACK_IMPORTED_MODULE_2__.mkdirP(reportBaseDir); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__.mkdirP(reportBaseDir); // folder listing if (listDirs) { - if (await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_3__/* .shouldWriteRootHtml */ .z)(ghPagesPath)) { - await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_3__/* .writeFolderListing */ .l)(ghPagesPath, '.'); + if (await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_4__/* .shouldWriteRootHtml */ .z)(ghPagesPath)) { + await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_4__/* .writeFolderListing */ .l)(ghPagesPath, '.'); } - await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_3__/* .writeFolderListing */ .l)(ghPagesPath, baseDir); - await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_3__/* .writeFolderListing */ .l)(ghPagesPath, `${baseDir}/${branchName}`); + await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_4__/* .writeFolderListing */ .l)(ghPagesPath, baseDir); + await (0,_src_writeFolderListing_js__WEBPACK_IMPORTED_MODULE_4__/* .writeFolderListing */ .l)(ghPagesPath, path__WEBPACK_IMPORTED_MODULE_0__.join(baseDir, branchName)); } // process allure report - const lastRunId = await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .getLastRunId */ .k4)(reportBaseDir); + const lastRunId = await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .getLastRunId */ .k4)(reportBaseDir); if (lastRunId) { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__.cp(`${reportBaseDir}/${lastRunId}/history`, sourceReportDir, { recursive: true }); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__.cp(path__WEBPACK_IMPORTED_MODULE_0__.join(reportBaseDir, lastRunId, 'history'), sourceReportDir, { recursive: true }); } - await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .writeExecutorJson */ .sp)(sourceReportDir, { + await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .writeExecutorJson */ .sp)(sourceReportDir, { runUniqueId, - buildOrder: _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.runId, + buildOrder: _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.runId, buildUrl: githubActionRunUrl, - reportUrl: ghPagesReportDir, + reportUrl: ghPagesReportUrl, }); - await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .spawnAllure */ .Mo)(sourceReportDir, reportDir); - const results = await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .updateDataJson */ .V0)(reportBaseDir, reportDir, _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.runId, runUniqueId); - await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .writeAllureListing */ .rF)(reportBaseDir); - await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .writeLastRunId */ .j9)(reportBaseDir, _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.runId, runTimestamp); + await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .spawnAllure */ .Mo)(sourceReportDir, reportDir); + const results = await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .updateDataJson */ .V0)(reportBaseDir, reportDir, _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.runId, runUniqueId); + await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .writeAllureListing */ .rF)(reportBaseDir); + await (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .writeLastRunId */ .j9)(reportBaseDir, _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.runId, runTimestamp); // outputs - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('report_url', ghPagesReportDir); - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('report_history_url', ghPagesBaseDir); - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('test_result', results.testResult); - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('test_result_icon', (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_4__/* .getTestResultIcon */ .RG)(results.testResult)); - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('test_result_passed', results.passed); - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('test_result_failed', results.failed); - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setOutput('test_result_total', results.total); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('report_url', ghPagesReportUrl); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('report_history_url', ghPagesBaseUrl); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('test_result', results.testResult); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('test_result_icon', (0,_src_allure_js__WEBPACK_IMPORTED_MODULE_5__/* .getTestResultIcon */ .RG)(results.testResult)); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('test_result_passed', results.passed); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('test_result_failed', results.failed); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setOutput('test_result_total', results.total); + if (cleanupEnabled) { + await (0,_src_cleanup_js__WEBPACK_IMPORTED_MODULE_7__/* .cleanupOutdatedBranches */ .B)(ghPagesBaseDir, _actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo); + await (0,_src_cleanup_js__WEBPACK_IMPORTED_MODULE_7__/* .cleanupOutdatedReports */ .g)(ghPagesBaseDir, maxReports); + } } catch (error) { - _actions_core__WEBPACK_IMPORTED_MODULE_0__.setFailed(error.message); + _actions_core__WEBPACK_IMPORTED_MODULE_1__.setFailed(error.message); } __webpack_async_result__(); @@ -29395,7 +29409,7 @@ __webpack_async_result__(); /***/ }), -/***/ 8791: +/***/ 9302: /***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => { @@ -29411,10 +29425,12 @@ __nccwpck_require__.d(__webpack_exports__, { "j9": () => (/* binding */ writeLastRunId) }); -;// CONCATENATED MODULE: external "child_process" -const external_child_process_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("child_process"); +// EXTERNAL MODULE: external "child_process" +var external_child_process_ = __nccwpck_require__(2081); // EXTERNAL MODULE: external "fs/promises" var promises_ = __nccwpck_require__(3292); +// EXTERNAL MODULE: external "path" +var external_path_ = __nccwpck_require__(1017); ;// CONCATENATED MODULE: ./src/report_allure.ts // autogenerated const allureReport = Buffer.from('PCFkb2N0eXBlIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KICA8aGVhZD4KICAgIDxtZXRhIGNoYXJzZXQ9InV0Zi04IiAvPgogICAgPHRpdGxlPkFsbHVyZSBSZXBvcnRzPC90aXRsZT4KICAgIDxzdHlsZT4KICAgICAgYm9keSB7CiAgICAgICAgbWFyZ2luOiAwcHg7CiAgICAgICAgZm9udC1zaXplOiAwLjllbTsKICAgICAgICBmb250LWZhbWlseTogc2Fucy1zZXJpZjsKICAgICAgfQoKICAgICAgI2FsbHVyZVRhYmxlIHsKICAgICAgICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogICAgICAgIG1hcmdpbjogMTBweDsKICAgICAgICBib3gtc2hhZG93OiAwIDAgMjBweCByZ2JhKDAsIDAsIDAsIDAuMTUpOwogICAgICB9CiAgICAgICNhbGx1cmVUYWJsZSB0Ym9keSB0ci50ZXN0LWZhaWwgewogICAgICAgIGJhY2tncm91bmQtY29sb3I6ICNmZTk4OGY7CiAgICAgIH0KICAgICAgI2FsbHVyZVRhYmxlIHRib2R5IHRyLnRlc3QtcGFzcyB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzZiZTc4MTsKICAgICAgfQogICAgICAjYWxsdXJlVGFibGUgdGJvZHkgdHIudGVzdC11bmtub3duIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjYmFiYWJhOwogICAgICB9CiAgICAgICNhbGx1cmVUYWJsZSB0aGVhZCB0ciB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzRjOTBlMjsKICAgICAgICBjb2xvcjogI2ZmZmZmZjsKICAgICAgICB0ZXh0LWFsaWduOiBsZWZ0OwogICAgICB9CiAgICAgICNhbGx1cmVUYWJsZSB0aGVhZCB0aDpudGgtY2hpbGQoMSkgewogICAgICAgIG1pbi13aWR0aDogODBweDsKICAgICAgfQogICAgICAjYWxsdXJlVGFibGUgdGgsCiAgICAgICNhbGx1cmVUYWJsZSB0ZCB7CiAgICAgICAgcGFkZGluZzogMTJweCAxNXB4OwogICAgICB9CiAgICAgICNhbGx1cmVUYWJsZSB0Ym9keSB0ciB7CiAgICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNkZGRkZGQ7CiAgICAgIH0KICAgICAgI2FsbHVyZVRhYmxlIHRib2R5IHRyOmhvdmVyIHsKICAgICAgICBmaWx0ZXI6IGJyaWdodG5lc3MoMTI1JSk7CiAgICAgICAgdHJhbnNpdGlvbjogZmlsdGVyIDAuMTVzIGVhc2UtaW4tb3V0OwogICAgICB9CiAgICAgICNhbGx1cmVUYWJsZSB0Zm9vdCB0ciB7CiAgICAgICAgYm9yZGVyLXRvcDogMnB4IHNvbGlkICM0YzkwZTI7CiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHRhYmxlIGlkPSJhbGx1cmVUYWJsZSI+CiAgICAgIDx0aGVhZD48L3RoZWFkPgogICAgICA8dGJvZHk+PC90Ym9keT4KICAgICAgPHRmb290PjwvdGZvb3Q+CiAgICA8L3RhYmxlPgogIDwvYm9keT4KICA8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgICBjb25zdCBjcmVhdGVUciA9IChkYXRhLCBjZWxsVHlwZSA9ICd0ZCcsIHRlc3RSZXN1bHQpID0+IHsKICAgICAgY29uc3Qgcm93ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndHInKQoKICAgICAgaWYgKHRlc3RSZXN1bHQgPT09ICdQQVNTJykgewogICAgICAgIHJvdy5jbGFzc0xpc3QuYWRkKCd0ZXN0LXBhc3MnKQogICAgICB9IGVsc2UgaWYgKHRlc3RSZXN1bHQgPT09ICdGQUlMJykgewogICAgICAgIHJvdy5jbGFzc0xpc3QuYWRkKCd0ZXN0LWZhaWwnKQogICAgICB9IGVsc2UgaWYgKHRlc3RSZXN1bHQgPT09ICdVTktOT1dOJykgewogICAgICAgIHJvdy5jbGFzc0xpc3QuYWRkKCd0ZXN0LXVua25vd24nKQogICAgICB9CgogICAgICBkYXRhLmZvckVhY2goKGVsKSA9PiB7CiAgICAgICAgY29uc3QgY2VsbCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoY2VsbFR5cGUpCiAgICAgICAgY2VsbC5hcHBlbmRDaGlsZChlbCkKICAgICAgICByb3cuYXBwZW5kQ2hpbGQoY2VsbCkKICAgICAgfSkKICAgICAgcmV0dXJuIHJvdwogICAgfQoKICAgIGNvbnN0IGZvcm1hdERhdGUgPSAodHMpID0+IHsKICAgICAgbGV0IGRhdGVUaW1lID0gbmV3IERhdGUodHMpLnRvSVNPU3RyaW5nKCkucmVwbGFjZSgnVCcsICcgJykKICAgICAgZGF0ZVRpbWUgPSBkYXRlVGltZS5zdWJzdHJpbmcoMCwgZGF0ZVRpbWUuaW5kZXhPZignLicpKQogICAgICByZXR1cm4gZGF0ZVRpbWUKICAgIH0KCiAgICBjb25zdCBmb3JtYXREdXJhdGlvbiA9IChkdXIpID0+IHsKICAgICAgY29uc3Qgc2Vjb25kcyA9IE1hdGgucm91bmQoZHVyIC8gMTAwMCkKICAgICAgY29uc3QgbWludXRlcyA9IE1hdGguZmxvb3Ioc2Vjb25kcyAvIDYwKQogICAgICBjb25zdCBzZWNvbmRzUmVtYWluZGVyID0gc2Vjb25kcyAtIG1pbnV0ZXMgKiA2MAogICAgICByZXR1cm4gYCR7bWludXRlc31tICR7c2Vjb25kc1JlbWFpbmRlcn1zYAogICAgfQoKICAgIGNvbnN0IGFsbHVyZVRhYmxlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2FsbHVyZVRhYmxlJykKCiAgICAvLyBoZWFkZXIgZGF0YQogICAgY29uc3QgYWxsdXJlVGFibGVIZWFkZXIgPSBhbGx1cmVUYWJsZS5nZXRFbGVtZW50c0J5VGFnTmFtZSgndGhlYWQnKVswXQogICAgY29uc3QgaGVhZGVycyA9IFsnRGF0ZScsICdQYXNzJywgJ0ZhaWwnLCAnU2tpcCcsICdUb3RhbCcsICdEdXJhdGlvbicsICdMaW5rJ10ubWFwKCh0KSA9PiBkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZSh0KSkKICAgIGFsbHVyZVRhYmxlSGVhZGVyLmFwcGVuZENoaWxkKGNyZWF0ZVRyKGhlYWRlcnMsICd0aCcpKQoKICAgIC8vIGJvZHkgZGF0YQogICAgY29uc3QgYWxsdXJlVGFibGVCb2R5ID0gYWxsdXJlVGFibGUuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ3Rib2R5JylbMF0KCiAgICBmZXRjaChgLi9kYXRhLmpzb24/dD0ke0RhdGUubm93KCl9YCkKICAgICAgLnRoZW4oKHJlc3BvbnNlKSA9PiByZXNwb25zZS5vayAmJiByZXNwb25zZS5qc29uKCkpCiAgICAgIC50aGVuKChqc29uKSA9PiB7CiAgICAgICAgaWYgKCFqc29uKSB7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CgogICAgICAgIC8vIGZvb3RlciBkYXRhCiAgICAgICAgY29uc3QgYWxsdXJlVGFibGVGb290ZXIgPSBhbGx1cmVUYWJsZS5nZXRFbGVtZW50c0J5VGFnTmFtZSgndGZvb3QnKVswXQogICAgICAgIGFsbHVyZVRhYmxlRm9vdGVyLmFwcGVuZENoaWxkKGNyZWF0ZVRyKFsnVE9UQUwnLCAnJywgJycsICcnLCAnJywgJycsIGpzb24ubGVuZ3RoXS5tYXAoKHQpID0+IGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKHQpKSkpCgogICAgICAgIGpzb24uZm9yRWFjaCgocikgPT4gewogICAgICAgICAgY29uc3Qgcm93RGF0YSA9IFtdCgogICAgICAgICAgcm93RGF0YS5wdXNoKGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKGZvcm1hdERhdGUoci50aW1lc3RhbXApKSkKICAgICAgICAgIHJvd0RhdGEucHVzaChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShyLnN1bW1hcnkuc3RhdGlzdGljLnBhc3NlZCkpCiAgICAgICAgICByb3dEYXRhLnB1c2goZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoci5zdW1tYXJ5LnN0YXRpc3RpYy5mYWlsZWQgKyByLnN1bW1hcnkuc3RhdGlzdGljLmJyb2tlbikpCiAgICAgICAgICByb3dEYXRhLnB1c2goZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoci5zdW1tYXJ5LnN0YXRpc3RpYy5za2lwcGVkKSkKICAgICAgICAgIHJvd0RhdGEucHVzaChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShyLnN1bW1hcnkuc3RhdGlzdGljLnRvdGFsKSkKICAgICAgICAgIHJvd0RhdGEucHVzaChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShmb3JtYXREdXJhdGlvbihyLnN1bW1hcnkudGltZS5kdXJhdGlvbikpKQoKICAgICAgICAgIGNvbnN0IGxpbmsgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJykKICAgICAgICAgIGxpbmsuaHJlZiA9IGAuLyR7ci5ydW5VbmlxdWVJZH1gCiAgICAgICAgICBsaW5rLmFwcGVuZENoaWxkKGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKHIucnVuSWQpKQogICAgICAgICAgcm93RGF0YS5wdXNoKGxpbmspCgogICAgICAgICAgLy8gYXBwZW5kIHJvdyB0byBib2R5CiAgICAgICAgICBhbGx1cmVUYWJsZUJvZHkuYXBwZW5kQ2hpbGQoY3JlYXRlVHIocm93RGF0YSwgJ3RkJywgci50ZXN0UmVzdWx0KSkKICAgICAgICB9KQogICAgICB9KQogIDwvc2NyaXB0Pgo8L2h0bWw+Cg==', 'base64'); @@ -29426,8 +29442,9 @@ var isFileExists = __nccwpck_require__(2139); + const writeExecutorJson = async (sourceReportDir, { buildUrl, buildOrder, reportUrl, runUniqueId, }) => { - const dataFile = `${sourceReportDir}/executor.json`; + const dataFile = external_path_.join(sourceReportDir, 'executor.json'); const dataJson = { // type is required, otherwise allure fails with java.lang.NullPointerException type: 'github', @@ -29442,7 +29459,7 @@ const writeExecutorJson = async (sourceReportDir, { buildUrl, buildOrder, report await promises_.writeFile(dataFile, JSON.stringify(dataJson, null, 2)); }; const spawnAllure = async (allureResultsDir, allureReportDir) => { - const allureChildProcess = external_child_process_namespaceObject.spawn('/allure-commandline/bin/allure', ['generate', '--clean', allureResultsDir, '-o', allureReportDir], { stdio: 'inherit' }); + const allureChildProcess = external_child_process_.spawn('/allure-commandline/bin/allure', ['generate', '--clean', allureResultsDir, '-o', allureReportDir], { stdio: 'inherit' }); const generation = new Promise((resolve, reject) => { allureChildProcess.once('error', reject); allureChildProcess.once('exit', (code) => (code === 0 ? resolve() : reject(code))); @@ -29450,7 +29467,7 @@ const spawnAllure = async (allureResultsDir, allureReportDir) => { return generation; }; const getLastRunId = async (reportBaseDir) => { - const dataFile = `${reportBaseDir}/lastRun.json`; + const dataFile = external_path_.join(reportBaseDir, 'lastRun.json'); if (await (0,isFileExists/* isFileExist */.e)(dataFile)) { const lastRun = JSON.parse((await promises_.readFile(dataFile)).toString('utf-8')); return `${lastRun.runId}_${lastRun.runTimestamp}`; @@ -29460,13 +29477,13 @@ const getLastRunId = async (reportBaseDir) => { } }; const writeLastRunId = async (reportBaseDir, runId, runTimestamp) => { - const dataFile = `${reportBaseDir}/lastRun.json`; + const dataFile = external_path_.join(reportBaseDir, 'lastRun.json'); const dataJson = { runId, runTimestamp }; await promises_.writeFile(dataFile, JSON.stringify(dataJson, null, 2)); }; const updateDataJson = async (reportBaseDir, reportDir, runId, runUniqueId) => { - const summaryJson = JSON.parse((await promises_.readFile(`${reportDir}/widgets/summary.json`)).toString('utf-8')); - const dataFile = `${reportBaseDir}/data.json`; + const summaryJson = JSON.parse((await promises_.readFile(external_path_.join(reportDir, 'widgets', 'summary.json'))).toString('utf-8')); + const dataFile = external_path_.join(reportBaseDir, 'data.json'); let dataJson; if (await (0,isFileExists/* isFileExist */.e)(dataFile)) { dataJson = JSON.parse((await promises_.readFile(dataFile)).toString('utf-8')); @@ -29504,14 +29521,18 @@ const getTestResultIcon = (testResult) => { } return '❔'; }; -const writeAllureListing = async (reportBaseDir) => promises_.writeFile(`${reportBaseDir}/index.html`, allureReport); +const writeAllureListing = async (reportBaseDir) => promises_.writeFile(external_path_.join(reportBaseDir, 'index.html'), allureReport); const isAllureResultsOk = async (sourceReportDir) => { + const allureResultExt = ['.json', '.xml']; if (await (0,isFileExists/* isFileExist */.e)(sourceReportDir)) { - const listfiles = (await promises_.readdir(sourceReportDir, { withFileTypes: true })).filter((d) => d.isFile() && d.name.toLowerCase().endsWith('.json')); + const listfiles = (await promises_.readdir(sourceReportDir, { withFileTypes: true })).filter((d) => { + const fileName = d.name.toLowerCase(); + return d.isFile() && allureResultExt.some((ext) => fileName.endsWith(ext)); + }); if (listfiles.length > 0) { return true; } - console.log('allure-results folder has no json files:', sourceReportDir); + console.log('allure-results folder has no json or xml files:', sourceReportDir); return false; } console.log("allure-results folder doesn't exist:", sourceReportDir); @@ -29519,17 +29540,121 @@ const isAllureResultsOk = async (sourceReportDir) => { }; +/***/ }), + +/***/ 2193: +/***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => { + + +// EXPORTS +__nccwpck_require__.d(__webpack_exports__, { + "B": () => (/* binding */ cleanupOutdatedBranches), + "g": () => (/* binding */ cleanupOutdatedReports) +}); + +// EXTERNAL MODULE: external "path" +var external_path_ = __nccwpck_require__(1017); +// EXTERNAL MODULE: external "fs/promises" +var promises_ = __nccwpck_require__(3292); +// EXTERNAL MODULE: external "child_process" +var external_child_process_ = __nccwpck_require__(2081); +;// CONCATENATED MODULE: ./src/spawnProcess.ts + +const logError = (err, output) => { + console.log(output.join('')); + return err; +}; +const spawnProcess = async (command, args, cwd) => { + const childProcess = external_child_process_.spawn(command, args, { cwd }); + return new Promise((resolve, reject) => { + const output = []; + const r1 = childProcess.stdout?.on('data', (d) => output.push(d.toString())); + const r2 = childProcess.stderr?.on('data', (d) => output.push(d.toString())); + const p1 = new Promise((resolve) => (r1 ? r1.once('close', resolve) : resolve())); + const p2 = new Promise((resolve) => (r2 ? r2.once('close', resolve) : resolve())); + childProcess.once('error', (err) => reject(logError(err, output))); + childProcess.once('exit', async (code) => { + r1?.removeAllListeners('data'); + r2?.removeAllListeners('data'); + await p1; + await p2; + return code === 0 ? resolve(output.join('')) : reject(logError(code, output)); + }); + }); +}; + +// EXTERNAL MODULE: ./src/helpers.ts +var helpers = __nccwpck_require__(3015); +;// CONCATENATED MODULE: ./src/cleanup.ts + + + + +const cleanupOutdatedBranches = async (ghPagesBaseDir, repo) => { + try { + const prefix = 'refs/heads/'; + // for some reason git won't pick up config, using url for now + const lsRemote = await spawnProcess('git', ['ls-remote', '--heads', `https://github.com/${repo.owner}/${repo.repo}.git`], process.env.GITHUB_WORKSPACE); + const remoteBranches = lsRemote + .split('\n') + .filter((l) => l.includes(prefix)) + .map((l) => (0,helpers/* normalizeBranchName */.i)(l.split(prefix)[1])); + const localBranches = (await promises_.readdir(ghPagesBaseDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name); + for (const localBranch of localBranches) { + if (!remoteBranches.includes(localBranch)) { + await promises_.rm(external_path_.join(ghPagesBaseDir, localBranch), { recursive: true, force: true }); + } + } + } + catch (err) { + console.error('cleanup outdated branches failed.', err); + } +}; +const cleanupOutdatedReports = async (ghPagesBaseDir, maxReports) => { + try { + const localBranches = (await promises_.readdir(ghPagesBaseDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name); + // branches + for (const localBranch of localBranches) { + const reports = (await promises_.readdir(external_path_.join(ghPagesBaseDir, localBranch), { withFileTypes: true })) + .filter((d) => d.isDirectory()) + .map((d) => d.name); + // report per branch + for (const reportName of reports) { + const runs = (await promises_.readdir(external_path_.join(ghPagesBaseDir, localBranch, reportName), { withFileTypes: true })) + .filter((d) => d.isDirectory()) + .map((d) => d.name); + // run per report + if (runs.length > maxReports) { + runs.sort(); + while (runs.length > maxReports) { + await promises_.rm(external_path_.join(ghPagesBaseDir, localBranch, reportName, runs.shift()), { + recursive: true, + force: true, + }); + } + } + } + } + } + catch (err) { + console.error('cleanup outdated reports failed.', err); + } +}; + + /***/ }), /***/ 3015: /***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => { /* harmony export */ __nccwpck_require__.d(__webpack_exports__, { -/* harmony export */ "L": () => (/* binding */ getBranchName) +/* harmony export */ "L": () => (/* binding */ getBranchName), +/* harmony export */ "i": () => (/* binding */ normalizeBranchName) /* harmony export */ }); +const normalizeBranchName = (branchName) => branchName.replaceAll('/', '_').replaceAll('.', '_'); const getBranchName = (gitRef, pull_request) => { const branchName = pull_request ? pull_request.head.ref : gitRef.replace('refs/heads/', ''); - return branchName.replaceAll('/', '_').replaceAll('.', '_'); + return normalizeBranchName(branchName); }; @@ -29567,6 +29692,8 @@ __nccwpck_require__.d(__webpack_exports__, { "l": () => (/* binding */ writeFolderListing) }); +// EXTERNAL MODULE: external "path" +var external_path_ = __nccwpck_require__(1017); // EXTERNAL MODULE: external "fs/promises" var promises_ = __nccwpck_require__(3292); ;// CONCATENATED MODULE: ./src/report_listing.ts @@ -29579,10 +29706,11 @@ var isFileExists = __nccwpck_require__(2139); + const indexHtmlFirstLine = ''; const writeFolderListing = async (ghPagesPath, relPath) => { const isRoot = relPath === '.'; - const fullPath = isRoot ? ghPagesPath : `${ghPagesPath}/${relPath}`; + const fullPath = isRoot ? ghPagesPath : external_path_.join(ghPagesPath, relPath); const links = []; if (!isRoot) { links.push('..'); @@ -29592,12 +29720,12 @@ const writeFolderListing = async (ghPagesPath, relPath) => { .map((d) => d.name); links.push(...listdir); const data = { links }; - await promises_.writeFile(`${fullPath}/data.json`, JSON.stringify(data, null, 2)); - await promises_.writeFile(`${fullPath}/index.html`, listingReport); + await promises_.writeFile(external_path_.join(fullPath, 'data.json'), JSON.stringify(data, null, 2)); + await promises_.writeFile(external_path_.join(fullPath, 'index.html'), listingReport); }; const shouldWriteRootHtml = async (ghPagesPath) => { // do noot overwrite index.html in the folder root to avoid conflicts - const rootHtmlPath = `${ghPagesPath}/index.html`; + const rootHtmlPath = external_path_.join(ghPagesPath, 'index.html'); const isRootHtmlExisting = await (0,isFileExists/* isFileExist */.e)(rootHtmlPath); // write index.html in the folder root if it doesn't exist if (!isRootHtmlExisting) { @@ -29636,6 +29764,13 @@ module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("buffer"); /***/ }), +/***/ 2081: +/***/ ((module) => { + +module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("child_process"); + +/***/ }), + /***/ 6206: /***/ ((module) => { diff --git a/index.ts b/index.ts index db34122..f392bab 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,4 @@ +import * as path from 'path' import * as core from '@actions/core' import * as github from '@actions/github' import * as io from '@actions/io' @@ -14,6 +15,7 @@ import { } from './src/allure.js' import { getBranchName } from './src/helpers.js' import { isFileExist } from './src/isFileExists.js' +import { cleanupOutdatedBranches, cleanupOutdatedReports } from './src/cleanup.js' const baseDir = 'allure-action' @@ -25,8 +27,11 @@ try { const ghPagesPath = core.getInput('gh_pages') const reportId = core.getInput('report_id') const listDirs = core.getInput('list_dirs') == 'true' + const cleanupEnabled = core.getInput('cleanup_enabled') == 'true' + const maxReports = parseInt(core.getInput('max_reports'), 10) const branchName = getBranchName(github.context.ref, github.context.payload.pull_request) - const reportBaseDir = `${ghPagesPath}/${baseDir}/${branchName}/${reportId}` + const ghPagesBaseDir = path.join(ghPagesPath, baseDir) + const reportBaseDir = path.join(ghPagesBaseDir, branchName, reportId) /** * `runId` is unique but won't change on job re-run @@ -34,13 +39,13 @@ try { * that's why the `runTimestamp` is used to guarantee uniqueness */ const runUniqueId = `${github.context.runId}_${runTimestamp}` - const reportDir = `${reportBaseDir}/${runUniqueId}` + const reportDir = path.join(reportBaseDir, runUniqueId) // urls const githubActionRunUrl = `https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}` const ghPagesUrl = `https://${github.context.repo.owner}.github.io/${github.context.repo.repo}` - const ghPagesBaseDir = `${ghPagesUrl}/${baseDir}/${branchName}/${reportId}`.replaceAll(' ', '%20') - const ghPagesReportDir = `${ghPagesBaseDir}/${runUniqueId}`.replaceAll(' ', '%20') + const ghPagesBaseUrl = `${ghPagesUrl}/${baseDir}/${branchName}/${reportId}`.replaceAll(' ', '%20') + const ghPagesReportUrl = `${ghPagesBaseUrl}/${runUniqueId}`.replaceAll(' ', '%20') // log console.log({ @@ -53,8 +58,10 @@ try { branchName, reportBaseDir, reportDir, - report_url: ghPagesReportDir, + report_url: ghPagesReportUrl, listDirs, + cleanupEnabled, + maxReports, }) if (!(await isFileExist(ghPagesPath))) { @@ -74,19 +81,19 @@ try { await writeFolderListing(ghPagesPath, '.') } await writeFolderListing(ghPagesPath, baseDir) - await writeFolderListing(ghPagesPath, `${baseDir}/${branchName}`) + await writeFolderListing(ghPagesPath, path.join(baseDir, branchName)) } // process allure report const lastRunId = await getLastRunId(reportBaseDir) if (lastRunId) { - await io.cp(`${reportBaseDir}/${lastRunId}/history`, sourceReportDir, { recursive: true }) + await io.cp(path.join(reportBaseDir, lastRunId, 'history'), sourceReportDir, { recursive: true }) } await writeExecutorJson(sourceReportDir, { runUniqueId, buildOrder: github.context.runId, buildUrl: githubActionRunUrl, - reportUrl: ghPagesReportDir, + reportUrl: ghPagesReportUrl, }) await spawnAllure(sourceReportDir, reportDir) const results = await updateDataJson(reportBaseDir, reportDir, github.context.runId, runUniqueId) @@ -94,13 +101,18 @@ try { await writeLastRunId(reportBaseDir, github.context.runId, runTimestamp) // outputs - core.setOutput('report_url', ghPagesReportDir) - core.setOutput('report_history_url', ghPagesBaseDir) + core.setOutput('report_url', ghPagesReportUrl) + core.setOutput('report_history_url', ghPagesBaseUrl) core.setOutput('test_result', results.testResult) core.setOutput('test_result_icon', getTestResultIcon(results.testResult)) core.setOutput('test_result_passed', results.passed) core.setOutput('test_result_failed', results.failed) core.setOutput('test_result_total', results.total) + + if (cleanupEnabled) { + await cleanupOutdatedBranches(ghPagesBaseDir, github.context.repo) + await cleanupOutdatedReports(ghPagesBaseDir, maxReports) + } } catch (error) { core.setFailed(error.message) } diff --git a/package-lock.json b/package-lock.json index 88075ef..0485d1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "allure-report-branch-action", - "version": "1.1.4", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "allure-report-branch-action", - "version": "1.1.4", + "version": "1.2.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -16,16 +16,16 @@ }, "devDependencies": { "@playwright/test": "^1.41.1", - "@types/node": "^20.11.13", + "@types/node": "^20.11.15", "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.20.0", "@vercel/ncc": "^0.38.1", - "allure-playwright": "^2.11.1", + "allure-playwright": "^2.12.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "http-server": "^14.1.1", - "husky": "^9.0.7", + "husky": "^9.0.10", "npm-run-all": "^4.1.5", "prettier": "^3.2.4", "typescript": "^5.3.3" @@ -402,9 +402,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.13.tgz", - "integrity": "sha512-5G4zQwdiQBSWYTDAH1ctw2eidqdhMJaNsiIDKHFr55ihz5Trl2qqR8fdrT732yPBho5gkNxXm67OxWFBqX9aPg==", + "version": "20.11.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.15.tgz", + "integrity": "sha512-gscmuADZfvNULx1eyirVbr3kVOVZtpQtzKMCZpeSZcN6MfbkRXAR4s9/gsQ4CzxLHw6EStDtKLNtSDL3vbq05A==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -659,30 +659,31 @@ } }, "node_modules/allure-js-commons": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.11.1.tgz", - "integrity": "sha512-A7Eiofwj46JBbK2XsM9FKmbhTYrdok+5M2EzI5ueJ/S+T12xvINBrrKdtjkqFvz/oH9qA/iKHawlJc4MSQbxLQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.12.0.tgz", + "integrity": "sha512-uVMKT2LBRJQ9nPTrfE61zwryF3WhaUGIaj0PrVP7AoUpAexzGx0nOUjsJxUes1BwomcTIH1ORZo6r3kmIs7N9g==", "dev": true, "dependencies": { - "properties": "^1.2.1" + "properties": "^1.2.1", + "strip-ansi": "^5.2.0" } }, "node_modules/allure-playwright": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-2.11.1.tgz", - "integrity": "sha512-xzSFJ5Xrc8AxMM9fkpvvEOwjcuGWUiksx3mQiWFHALpUS1cgsJxm5M30omgqe6/rfbMBsIM2w4ufMts4N37+2w==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-2.12.0.tgz", + "integrity": "sha512-H2S7bEGqH/I3kR+6cnUpgRMfUOD1i73Sa8kQtPUGzavAZ6ZuF0vITf8jrlddNo1o4NKm2/HymUp0NnDBphT7uQ==", "dev": true, "dependencies": { - "allure-js-commons": "2.11.1" + "allure-js-commons": "2.12.0" } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/ansi-styles": { @@ -759,9 +760,9 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", "dev": true, "engines": { "node": ">= 0.4" @@ -1213,6 +1214,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1235,6 +1245,18 @@ "node": "*" } }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1720,13 +1742,10 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.1.tgz", + "integrity": "sha512-6J4rC9ROz0UkOpjn0BRtSSqlewDTDYJNQvy8N8RSrPCduUWId1o9BQPEVII/KKBqRk/ZIQff1YbRkUDCH2N5Sg==", "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, "engines": { "node": ">= 0.4" }, @@ -1815,12 +1834,12 @@ } }, "node_modules/husky": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.7.tgz", - "integrity": "sha512-vWdusw+y12DUEeoZqW1kplOFqk3tedGV8qlga8/SF6a3lOiWLqGZZQvfWvY0fQYdfiRi/u1DFNpudTSV9l1aCg==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.10.tgz", + "integrity": "sha512-TQGNknoiy6bURzIO77pPRu+XHi6zI7T93rX+QnJsoYFf3xdjKOur+IlfqzJGMHIK/wXrLg+GsvMs8Op7vI2jVA==", "dev": true, "bin": { - "husky": "bin.js" + "husky": "bin.mjs" }, "engines": { "node": ">=18" @@ -1842,9 +1861,9 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -3281,15 +3300,15 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/strip-bom": { diff --git a/package.json b/package.json index d7add74..451a661 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "allure-report-branch-action", - "version": "1.1.4", + "version": "1.2.0", "description": "Allure Report with history per branch", "main": "index.js", "type": "module", @@ -32,16 +32,16 @@ }, "devDependencies": { "@playwright/test": "^1.41.1", - "@types/node": "^20.11.13", + "@types/node": "^20.11.15", "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.20.0", "@vercel/ncc": "^0.38.1", - "allure-playwright": "^2.11.1", + "allure-playwright": "^2.12.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "http-server": "^14.1.1", - "husky": "^9.0.7", + "husky": "^9.0.10", "npm-run-all": "^4.1.5", "prettier": "^3.2.4", "typescript": "^5.3.3" diff --git a/src/allure.ts b/src/allure.ts index 9c03110..be1d1d0 100644 --- a/src/allure.ts +++ b/src/allure.ts @@ -1,5 +1,6 @@ import * as child_process from 'child_process' import * as fs from 'fs/promises' +import * as path from 'path' import { allureReport } from './report_allure.js' import { isFileExist } from './isFileExists.js' @@ -17,7 +18,7 @@ export const writeExecutorJson = async ( reportUrl: string } ) => { - const dataFile = `${sourceReportDir}/executor.json` + const dataFile = path.join(sourceReportDir, 'executor.json') const dataJson: AllureExecutor = { // type is required, otherwise allure fails with java.lang.NullPointerException type: 'github', @@ -47,7 +48,7 @@ export const spawnAllure = async (allureResultsDir: string, allureReportDir: str } export const getLastRunId = async (reportBaseDir: string) => { - const dataFile = `${reportBaseDir}/lastRun.json` + const dataFile = path.join(reportBaseDir, 'lastRun.json') if (await isFileExist(dataFile)) { const lastRun: LastRunJson = JSON.parse((await fs.readFile(dataFile)).toString('utf-8')) @@ -58,7 +59,7 @@ export const getLastRunId = async (reportBaseDir: string) => { } export const writeLastRunId = async (reportBaseDir: string, runId: number, runTimestamp: number) => { - const dataFile = `${reportBaseDir}/lastRun.json` + const dataFile = path.join(reportBaseDir, 'lastRun.json') const dataJson: LastRunJson = { runId, runTimestamp } @@ -66,8 +67,10 @@ export const writeLastRunId = async (reportBaseDir: string, runId: number, runTi } export const updateDataJson = async (reportBaseDir: string, reportDir: string, runId: number, runUniqueId: string) => { - const summaryJson: AllureSummaryJson = JSON.parse((await fs.readFile(`${reportDir}/widgets/summary.json`)).toString('utf-8')) - const dataFile = `${reportBaseDir}/data.json` + const summaryJson: AllureSummaryJson = JSON.parse( + (await fs.readFile(path.join(reportDir, 'widgets', 'summary.json'))).toString('utf-8') + ) + const dataFile = path.join(reportBaseDir, 'data.json') let dataJson: AllureRecord[] if (await isFileExist(dataFile)) { @@ -109,18 +112,20 @@ export const getTestResultIcon = (testResult: AllureRecordTestResult) => { return '❔' } -export const writeAllureListing = async (reportBaseDir: string) => fs.writeFile(`${reportBaseDir}/index.html`, allureReport) +export const writeAllureListing = async (reportBaseDir: string) => fs.writeFile(path.join(reportBaseDir, 'index.html'), allureReport) export const isAllureResultsOk = async (sourceReportDir: string) => { + const allureResultExt = ['.json', '.xml'] if (await isFileExist(sourceReportDir)) { - const listfiles = (await fs.readdir(sourceReportDir, { withFileTypes: true })).filter( - (d) => d.isFile() && d.name.toLowerCase().endsWith('.json') - ) + const listfiles = (await fs.readdir(sourceReportDir, { withFileTypes: true })).filter((d) => { + const fileName = d.name.toLowerCase() + return d.isFile() && allureResultExt.some((ext) => fileName.endsWith(ext)) + }) if (listfiles.length > 0) { return true } - console.log('allure-results folder has no json files:', sourceReportDir) + console.log('allure-results folder has no json or xml files:', sourceReportDir) return false } console.log("allure-results folder doesn't exist:", sourceReportDir) diff --git a/src/cleanup.ts b/src/cleanup.ts new file mode 100644 index 0000000..f32bf98 --- /dev/null +++ b/src/cleanup.ts @@ -0,0 +1,64 @@ +import * as path from 'path' +import * as fs from 'fs/promises' +import { spawnProcess } from './spawnProcess.js' +import { normalizeBranchName } from './helpers.js' +import { Context } from '@actions/github/lib/context.js' + +export const cleanupOutdatedBranches = async (ghPagesBaseDir: string, repo: Context['repo']) => { + try { + const prefix = 'refs/heads/' + // for some reason git won't pick up config, using url for now + const lsRemote = await spawnProcess( + 'git', + ['ls-remote', '--heads', `https://github.com/${repo.owner}/${repo.repo}.git`], + process.env.GITHUB_WORKSPACE + ) + const remoteBranches = lsRemote + .split('\n') + .filter((l) => l.includes(prefix)) + .map((l) => normalizeBranchName(l.split(prefix)[1])) + + const localBranches = (await fs.readdir(ghPagesBaseDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name) + + for (const localBranch of localBranches) { + if (!remoteBranches.includes(localBranch)) { + await fs.rm(path.join(ghPagesBaseDir, localBranch), { recursive: true, force: true }) + } + } + } catch (err) { + console.error('cleanup outdated branches failed.', err) + } +} + +export const cleanupOutdatedReports = async (ghPagesBaseDir: string, maxReports: number) => { + try { + const localBranches = (await fs.readdir(ghPagesBaseDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name) + + // branches + for (const localBranch of localBranches) { + const reports = (await fs.readdir(path.join(ghPagesBaseDir, localBranch), { withFileTypes: true })) + .filter((d) => d.isDirectory()) + .map((d) => d.name) + + // report per branch + for (const reportName of reports) { + const runs = (await fs.readdir(path.join(ghPagesBaseDir, localBranch, reportName), { withFileTypes: true })) + .filter((d) => d.isDirectory()) + .map((d) => d.name) + + // run per report + if (runs.length > maxReports) { + runs.sort() + while (runs.length > maxReports) { + await fs.rm(path.join(ghPagesBaseDir, localBranch, reportName, runs.shift() as string), { + recursive: true, + force: true, + }) + } + } + } + } + } catch (err) { + console.error('cleanup outdated reports failed.', err) + } +} diff --git a/src/helpers.ts b/src/helpers.ts index 01f9ab3..b94b57f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,7 +1,9 @@ import type { context } from '@actions/github' +export const normalizeBranchName = (branchName: string) => branchName.replaceAll('/', '_').replaceAll('.', '_') + export const getBranchName = (gitRef: string, pull_request?: (typeof context)['payload']) => { const branchName: string = pull_request ? pull_request.head.ref : gitRef.replace('refs/heads/', '') - return branchName.replaceAll('/', '_').replaceAll('.', '_') + return normalizeBranchName(branchName) } diff --git a/src/spawnProcess.ts b/src/spawnProcess.ts new file mode 100644 index 0000000..ba8f1e3 --- /dev/null +++ b/src/spawnProcess.ts @@ -0,0 +1,27 @@ +import * as child_process from 'child_process' + +const logError = (err: unknown, output: string[]) => { + console.log(output.join('')) + return err +} + +export const spawnProcess = async (command: string, args: string[], cwd?: string) => { + const childProcess = child_process.spawn(command, args, { cwd }) + return new Promise((resolve, reject) => { + const output: string[] = [] + const r1 = childProcess.stdout?.on('data', (d) => output.push(d.toString())) + const r2 = childProcess.stderr?.on('data', (d) => output.push(d.toString())) + + const p1 = new Promise((resolve) => (r1 ? r1.once('close', resolve) : resolve())) + const p2 = new Promise((resolve) => (r2 ? r2.once('close', resolve) : resolve())) + + childProcess.once('error', (err) => reject(logError(err, output))) + childProcess.once('exit', async (code: unknown) => { + r1?.removeAllListeners('data') + r2?.removeAllListeners('data') + await p1 + await p2 + return code === 0 ? resolve(output.join('')) : reject(logError(code, output)) + }) + }) +} diff --git a/src/writeFolderListing.ts b/src/writeFolderListing.ts index f6244aa..184cde7 100644 --- a/src/writeFolderListing.ts +++ b/src/writeFolderListing.ts @@ -1,3 +1,4 @@ +import * as path from 'path' import * as fs from 'fs/promises' import { listingReport } from './report_listing.js' import { isFileExist } from './isFileExists.js' @@ -6,7 +7,7 @@ const indexHtmlFirstLine = '' export const writeFolderListing = async (ghPagesPath: string, relPath: string) => { const isRoot = relPath === '.' - const fullPath = isRoot ? ghPagesPath : `${ghPagesPath}/${relPath}` + const fullPath = isRoot ? ghPagesPath : path.join(ghPagesPath, relPath) const links: string[] = [] if (!isRoot) { @@ -19,13 +20,13 @@ export const writeFolderListing = async (ghPagesPath: string, relPath: string) = const data = { links } - await fs.writeFile(`${fullPath}/data.json`, JSON.stringify(data, null, 2)) - await fs.writeFile(`${fullPath}/index.html`, listingReport) + await fs.writeFile(path.join(fullPath, 'data.json'), JSON.stringify(data, null, 2)) + await fs.writeFile(path.join(fullPath, 'index.html'), listingReport) } export const shouldWriteRootHtml = async (ghPagesPath: string) => { // do noot overwrite index.html in the folder root to avoid conflicts - const rootHtmlPath = `${ghPagesPath}/index.html` + const rootHtmlPath = path.join(ghPagesPath, 'index.html') const isRootHtmlExisting = await isFileExist(rootHtmlPath) // write index.html in the folder root if it doesn't exist