diff --git a/.github/scripts/ciScript.js b/.github/scripts/ciScript.js index f8cb346..4e4e479 100644 --- a/.github/scripts/ciScript.js +++ b/.github/scripts/ciScript.js @@ -1,3 +1,18 @@ +const isTestFile = (file) => /\.(test|spec)\.[jt]sx?$/.test(file); + +const deriveTestFiles = (files) => { + return files.map((file) => { + if (isTestFile(file)) return file; + + const withoutExt = file.replace(/\.[jt]sx?$/, ''); + const parts = withoutExt.split('/'); + const baseName = parts[parts.length - 1]; + const dir = parts.slice(0, -1).join('/'); + + return `${dir}/__tests__/${baseName}.test.ts`; + }); +}; + module.exports = async ({ github, context, core }) => { const owner = context.repo.owner; const repo = context.repo.repo; @@ -6,7 +21,6 @@ module.exports = async ({ github, context, core }) => { const prState = pr.state; const backendFiles = []; - const backendTests = []; const mobileFiles = []; const webFiles = []; @@ -34,17 +48,6 @@ module.exports = async ({ github, context, core }) => { if (fileName.startsWith('apps/backend/')) { backendFiles.push(fileName); - - const relative = fileName.replace('apps/backend/src/', ''); - const baseName = relative - .split('/') - .pop() - ?.replace(/\.(ts|tsx|js|jsx)$/, ''); - - if (baseName) { - backendTests.push(`src/__tests__/${baseName}.test.ts`); - } - } else if (fileName.startsWith('apps/mobile/')) { mobileFiles.push(fileName); } else if (fileName.startsWith('apps/web/')) { @@ -52,42 +55,19 @@ module.exports = async ({ github, context, core }) => { } }); - console.log({ - backendFiles, - backendTests, - mobileFiles, - webFiles, - }); + const strippedBackend = backendFiles.map(f => f.replace('apps/backend/', '')); + const strippedMobile = mobileFiles.map(f => f.replace('apps/mobile/', '')); - core.setOutput( - "backendFiles", - backendFiles - .map(file => file.replace("apps/backend/", "")) - .join(" ") - ); - - core.setOutput( - "backendTests", - [...new Set(backendTests)].join(" ") - ); - - core.setOutput( - "mobileFiles", - mobileFiles - .map(file => file.replace("apps/mobile/", "")) - .join(" ") - ); - - core.setOutput( - "webFiles", - webFiles - .map(file => file.replace("apps/web/", "")) - .join(" ") - ); + console.log({ backendFiles, mobileFiles, webFiles }); - core.setOutput("backendChanged", backendFiles.length > 0); - core.setOutput("mobileChanged", mobileFiles.length > 0); - core.setOutput("webChanged", webFiles.length > 0); + core.setOutput('backendFiles', strippedBackend.join(' ')); + core.setOutput('mobileFiles', strippedMobile.join(' ')); + core.setOutput('webFiles', webFiles.map(f => f.replace('apps/web/', '')).join(' ')); + core.setOutput('backendTestFiles', deriveTestFiles(strippedBackend).join(' ')); + core.setOutput('mobileTestFiles', deriveTestFiles(strippedMobile).join(' ')); + core.setOutput('backendChanged', backendFiles.length > 0); + core.setOutput('mobileChanged', mobileFiles.length > 0); + core.setOutput('webChanged', webFiles.length > 0); } catch (error) { console.error(error); diff --git a/.github/scripts/commentResults.js b/.github/scripts/commentResults.js index 50cd139..a1a9608 100644 --- a/.github/scripts/commentResults.js +++ b/.github/scripts/commentResults.js @@ -10,61 +10,60 @@ module.exports = async ({ mobileLint, mobileTest, webCheck, - webBuild + webBuild, + backendLintOutput, + mobileLintOutput, }) => { const owner = context.repo.owner; const repo = context.repo.repo; const prNumber = context.payload.pull_request.number; - const emoji = (status) => { - if (status === 'success') return '✅'; - if (status === 'failure') return '❌'; - if (status === 'skipped') return '⏭️'; - return '⚪'; + const status = (s) => { + if (s === 'success') return 'PASS'; + if (s === 'failure') return 'FAIL'; + if (s === 'skipped') return 'SKIP'; + return '-'; }; - const label = (status) => { - if (!status) return '⚪ unknown'; - return `${emoji(status)} ${status}`; + const lintDetails = (output) => { + if (!output || !output.trim()) return ''; + return `\n
\nView lint errors\n\n\`\`\`\n${output.trim()}\n\`\`\`\n
`; }; - const anyFailure = [ - backend, - mobile, - web - ].includes('failure'); - - const title = anyFailure - ? '❌ Some checks failed' - : '✅ CI completed'; - + const anyFailure = [backend, mobile, web].includes('failure'); + const title = anyFailure ? 'CI — Checks Failed' : 'CI — All Checks Passed'; const timestamp = new Date().toUTCString(); - const body = `## CI Results — ${title} + const body = `## ${title} + +### Backend — ${status(backend)} -### 🖥️ Backend (${label(backend)}) -| Check | Status | +| Check | Result | |---|---| -| Lint | ${label(backendLint)} | -| Test | ${label(backendTest)} | -| Typecheck | ${label(backendTypecheck)} | +| Lint | ${status(backendLint)} | +| Test | ${status(backendTest)} | +| Typecheck | ${status(backendTypecheck)} | +${backendLint === 'failure' ? lintDetails(backendLintOutput) : ''} -### 📱 Mobile (${label(mobile)}) -| Check | Status | +### Mobile — ${status(mobile)} + +| Check | Result | |---|---| -| Lint | ${label(mobileLint)} | -| Test | ${label(mobileTest)} | +| Lint | ${status(mobileLint)} | +| Test | ${status(mobileTest)} | +${mobileLint === 'failure' ? lintDetails(mobileLintOutput) : ''} + +### Web — ${status(web)} -### 🌐 Web (${label(web)}) -| Check | Status | +| Check | Result | |---|---| -| Check | ${label(webCheck)} | -| Build | ${label(webBuild)} | +| Check | ${status(webCheck)} | +| Build | ${status(webBuild)} | --- -🕐 Last updated: \`${timestamp}\``; +Last updated: \`${timestamp}\``; - const COMMENT_MARKER = '## CI Results —'; + const COMMENT_MARKER = '## CI —'; try { const comments = await github.paginate( diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45b5f7a..fbb952e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,16 +12,20 @@ jobs: runs-on: ubuntu-latest outputs: - backendChanged: ${{ steps.detect.outputs.backendChanged }} - mobileChanged: ${{ steps.detect.outputs.mobileChanged }} - webChanged: ${{ steps.detect.outputs.webChanged }} - backendFiles: ${{ steps.detect.outputs.backendFiles }} - mobileFiles: ${{ steps.detect.outputs.mobileFiles }} - webFiles: ${{ steps.detect.outputs.webFiles }} - backendTests: ${{ steps.detect.outputs.backendTests }} + backendChanged: ${{ steps.detect.outputs.backendChanged }} + mobileChanged: ${{ steps.detect.outputs.mobileChanged }} + webChanged: ${{ steps.detect.outputs.webChanged }} + backendFiles: ${{ steps.detect.outputs.backendFiles }} + mobileFiles: ${{ steps.detect.outputs.mobileFiles }} + webFiles: ${{ steps.detect.outputs.webFiles }} + backendTestFiles: ${{ steps.detect.outputs.backendTestFiles }} + mobileTestFiles: ${{ steps.detect.outputs.mobileTestFiles }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Detect changed files id: detect @@ -37,13 +41,15 @@ jobs: if: needs.detect-changes.outputs.backendChanged == 'true' runs-on: ubuntu-latest - outputs: - backend_lint: ${{ steps.backend_lint.outcome }} - backend_test: ${{ steps.backend_test.outcome }} - backend_typecheck: ${{ steps.backend_typecheck.outcome }} + outputs: + lint_result: ${{ steps.backend_lint.outcome }} + test_result: ${{ steps.backend_test.outcome }} + typecheck_result: ${{ steps.backend_typecheck.outcome }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e with: @@ -55,21 +61,25 @@ jobs: - name: Backend lint id: backend_lint - continue-on-error: true + continue-on-error: true run: cd apps/backend && pnpm eslint ${{ needs.detect-changes.outputs.backendFiles }} - name: Backend test id: backend_test + if: needs.detect-changes.outputs.backendTestFiles != '' continue-on-error: true - run: cd apps/backend && pnpm test ${{ needs.detect-changes.outputs.backendTests }} + run: cd apps/backend && pnpm test --passWithNoTests ${{ needs.detect-changes.outputs.backendTestFiles }} - name: Backend typecheck id: backend_typecheck continue-on-error: true - run: cd apps/backend && pnpm typecheck ${{ needs.detect-changes.outputs.backendFiles }} + run: cd apps/backend && pnpm typecheck - - name: Fail backend if checks failed - if: steps.backend_lint.outcome == 'failure' || steps.backend_test.outcome == 'failure' || steps.backend_typecheck.outcome == 'failure' + - name: Fail job if any check failed + if: > + steps.backend_lint.outcome == 'failure' || + steps.backend_test.outcome == 'failure' || + steps.backend_typecheck.outcome == 'failure' run: exit 1 web-ci: @@ -78,11 +88,13 @@ jobs: runs-on: ubuntu-latest outputs: - web_check: ${{ steps.web_check.outcome }} - web_build: ${{ steps.web_build.outcome }} + check_result: ${{ steps.web_check.outcome }} + build_result: ${{ steps.web_build.outcome }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e with: @@ -102,8 +114,10 @@ jobs: continue-on-error: true run: cd apps/web && pnpm build - - name: Fail web if checks failed - if: steps.web_check.outcome == 'failure' || steps.web_build.outcome == 'failure' + - name: Fail job if any check failed + if: > + steps.web_check.outcome == 'failure' || + steps.web_build.outcome == 'failure' run: exit 1 mobile-ci: @@ -112,11 +126,13 @@ jobs: runs-on: ubuntu-latest outputs: - mobile_lint: ${{ steps.mobile_lint.outcome }} - mobile_test: ${{ steps.mobile_test.outcome }} + lint_result: ${{ steps.mobile_lint.outcome }} + test_result: ${{ steps.mobile_test.outcome }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e with: @@ -133,11 +149,14 @@ jobs: - name: Mobile test id: mobile_test + if: needs.detect-changes.outputs.mobileTestFiles != '' continue-on-error: true - run: cd apps/mobile && pnpm test + run: cd apps/mobile && pnpm test --passWithNoTests ${{ needs.detect-changes.outputs.mobileTestFiles }} - - name: Fail mobile if checks failed - if: steps.mobile_lint.outcome == 'failure' || steps.mobile_test.outcome == 'failure' + - name: Fail job if any check failed + if: > + steps.mobile_lint.outcome == 'failure' || + steps.mobile_test.outcome == 'failure' run: exit 1 comment-results: @@ -157,22 +176,17 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const script = require('./.github/scripts/commentResults.js'); - await script({ github, context, - - backend: '${{ needs.backend-ci.result }}', - web: '${{ needs.web-ci.result }}', - mobile: '${{ needs.mobile-ci.result }}', - - backendLint: '${{ needs.backend-ci.outputs.backend_lint }}', - backendTest: '${{ needs.backend-ci.outputs.backend_test }}', - backendTypecheck: '${{ needs.backend-ci.outputs.backend_typecheck }}', - - mobileLint: '${{ needs.mobile-ci.outputs.mobile_lint }}', - mobileTest: '${{ needs.mobile-ci.outputs.mobile_test }}', - - webCheck: '${{ needs.web-ci.outputs.web_check }}', - webBuild: '${{ needs.web-ci.outputs.web_build }}' - }); \ No newline at end of file + backend: '${{ needs.backend-ci.result }}', + web: '${{ needs.web-ci.result }}', + mobile: '${{ needs.mobile-ci.result }}', + backendLint: '${{ needs.backend-ci.outputs.lint_result }}', + backendTest: '${{ needs.backend-ci.outputs.test_result }}', + backendTypecheck: '${{ needs.backend-ci.outputs.typecheck_result }}', + webCheck: '${{ needs.web-ci.outputs.check_result }}', + webBuild: '${{ needs.web-ci.outputs.build_result }}', + mobileLint: '${{ needs.mobile-ci.outputs.lint_result }}', + mobileTest: '${{ needs.mobile-ci.outputs.test_result }}', + }); \ No newline at end of file