Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/scripts/ciScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module.exports = async ({ github, context, core }) => {
const owner = context.repo.owner;
const repo = context.repo.repo;
const pr = context.payload.pull_request;
const prNumber = pr.number;
const prState = pr.state;

const backendFiles = [];
const mobileFiles = [];
const webFiles = [];

try {
if (prState === 'closed') {
console.log(`PR state is: ${prState}`);
return {
backendChanged: false,
mobileChanged: false,
webChanged: false
};
}

const changedFiles = await github.paginate(
github.rest.pulls.listFiles,
{
owner,
repo,
pull_number: prNumber
}
);

changedFiles.forEach((file) => {
const fileName = file.filename;

if (fileName.startsWith('apps/backend/')) {
backendFiles.push(fileName);
} else if (fileName.startsWith('apps/mobile/')) {
mobileFiles.push(fileName);
} else if (fileName.startsWith('apps/web/')) {
webFiles.push(fileName);
}
});

console.log({
backendFiles,
mobileFiles,
webFiles
});

core.setOutput(
"backendFiles",
backendFiles
.map(file => file.replace("apps/backend/", ""))
.join(" ")
)

core.setOutput(
"mobileFiles",
mobileFiles
.map(file => file.replace("apps/mobile/", ""))
.join(" ")
)

core.setOutput(
"webFiles",
webFiles
.map(file => file.replace("apps/web/", ""))
.join(" ")
)

core.setOutput("backendChanged", backendFiles.length > 0)
core.setOutput("mobileChanged", mobileFiles.length > 0)
core.setOutput("webChanged", webFiles.length > 0)

} catch (error) {
console.error(error);

return {
backendChanged: false,
mobileChanged: false,
webChanged: false
};
}
};
101 changes: 101 additions & 0 deletions .github/scripts/commentResults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
module.exports = async ({
github,
context,
backend,
mobile,
web,
backendLint,
backendTest,
backendTypecheck,
mobileLint,
mobileTest,
webCheck,
webBuild
}) => {
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 label = (status) => {
if (!status) return '⚪ unknown';
return `${emoji(status)} ${status}`;
};

const anyFailure = [
backend,
mobile,
web
].includes('failure');

const title = anyFailure
? '❌ Some checks failed'
: '✅ CI completed';

const timestamp = new Date().toUTCString();

const body = `## CI Results — ${title}

### 🖥️ Backend (${label(backend)})
| Check | Status |
|---|---|
| Lint | ${label(backendLint)} |
| Test | ${label(backendTest)} |
| Typecheck | ${label(backendTypecheck)} |

### 📱 Mobile (${label(mobile)})
| Check | Status |
|---|---|
| Lint | ${label(mobileLint)} |
| Test | ${label(mobileTest)} |

### 🌐 Web (${label(web)})
| Check | Status |
|---|---|
| Check | ${label(webCheck)} |
| Build | ${label(webBuild)} |

---
🕐 Last updated: \`${timestamp}\``;

const COMMENT_MARKER = '## CI Results —';

try {
const comments = await github.paginate(
github.rest.issues.listComments,
{
owner,
repo,
issue_number: prNumber
}
);

const existing = comments.find(
c => c.body && c.body.startsWith(COMMENT_MARKER)
);

if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body
});
}
} catch (err) {
console.error(err);
}
};
134 changes: 134 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: CI

on:
pull_request_target:
types: [opened, synchronize, reopened]

permissions:
pull-requests: write

jobs:
detect-changes:
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 }}

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Detect changed files
id: detect
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const script = require('./.github/scripts/ciScript.js');
return await script({ github, context, core });

backend-ci:
needs: detect-changes
if: needs.detect-changes.outputs.backendChanged == 'true'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: 22

- uses: pnpm/action-setup@v6.0.8

- run: pnpm install

- name: Backend lint
id: backend_lint
run: cd apps/backend && pnpm eslint ${{ needs.detect-changes.outputs.backendFiles }}

- name: Backend test
id: backend_test
run: cd apps/backend && pnpm test ${{ needs.detect-changes.outputs.backendFiles }}

- name: Backend typecheck
id: backend_typecheck
run: cd apps/backend && pnpm typecheck ${{ needs.detect-changes.outputs.backendFiles }}

web-ci:
needs: detect-changes
if: needs.detect-changes.outputs.webChanged == 'true'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: 22

- uses: pnpm/action-setup@v6.0.8

- run: pnpm install

- name: Web check
id: web_check
run: cd apps/web && pnpm check

- name: Web build
id: web_build
run: cd apps/web && pnpm build

mobile-ci:
needs: detect-changes
if: needs.detect-changes.outputs.mobileChanged == 'true'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: 22

- uses: pnpm/action-setup@v6.0.8

- run: pnpm install

- name: Mobile lint
id: mobile_lint
run: cd apps/mobile && pnpm eslint ${{ needs.detect-changes.outputs.mobileFiles }}

- name: Mobile test
id: mobile_test
run: cd apps/mobile && pnpm test

comment-results:
needs:
- backend-ci
- web-ci
- mobile-ci
if: always()
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Comment results
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
with:
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 }}'
});
3 changes: 2 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"db:migrate": "prisma migrate dev",
"db:deploy": "prisma migrate deploy",
"db:seed": "tsx prisma/seed.ts",
"db:studio": "prisma studio"
"db:studio": "prisma studio",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@devcard/shared": "workspace:*",
Expand Down
Loading