From 759ca0438a3a003cb43dbae9cbd69f8ce1f5f98c Mon Sep 17 00:00:00 2001 From: bonubhavana04 Date: Mon, 18 May 2026 21:39:56 +0530 Subject: [PATCH] ci: set up CI pipeline, PR title enforcement, ESLint, and code cleanup --- .github/workflows/ci.yml | 51 +++++++++++++++++++++++++++++++ .github/workflows/pr-title.yml | 17 +++++++++++ CONTRIBUTING.md | 52 +++++++++++++++++++++++++++----- README.md | 6 ++-- apps/backend/eslint.config.js | 12 ++++++++ apps/backend/package.json | 3 ++ apps/mobile/package.json | 7 +++-- apps/web/eslint.config.js | 10 ++++++ apps/web/package.json | 8 ++++- package.json | 4 +-- packages/shared/eslint.config.js | 12 ++++++++ packages/shared/package.json | 2 ++ 12 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pr-title.yml create mode 100644 apps/backend/eslint.config.js create mode 100644 apps/web/eslint.config.js create mode 100644 packages/shared/eslint.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..622d48e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + ci: + name: CI (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18, 20] + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + + - name: Prepare SvelteKit + run: pnpm --filter @devcard/web run prepare + + - name: Typecheck + run: pnpm -r --if-present run typecheck + + - name: Lint + run: pnpm -r --if-present run lint + + - name: Test + run: pnpm -r --if-present run test + + - name: Upload coverage + uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-node-${{ matrix.node-version }} + path: | + **/coverage/ + **/.nyc_output/ + if-no-files-found: ignore diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..4fc910e --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,17 @@ +name: PR Title + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: + pull-requests: read + +jobs: + validate: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f95620..9fd9bea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ ### Prerequisites -- **Node.js** >= 20 +- **Node.js** >= 18 - **pnpm** >= 9 - **Docker** & Docker Compose - **React Native** dev environment — follow the [official setup guide](https://reactnative.dev/docs/environment-setup) @@ -65,11 +65,13 @@ The mobile app uses Jest: pnpm --filter @devcard/mobile test ``` #### apps/web -Currently, the web app does not define a test script. +The web app has a no-op test script (no tests yet). #### packages/shared -The shared package does not include test scripts. It only provides linting and type checking. - +The shared package uses Vitest: +```bash +pnpm --filter @devcard/shared test +``` ## Project Structure @@ -81,6 +83,41 @@ devcard/ └── packages/shared/ # Shared types, utils, platform registry ``` +## CI / Pipeline + +Every push to `main` and every pull request triggers the CI pipeline defined in [`.github/workflows/ci.yml`](.github/workflows/ci.yml). + +The pipeline runs on Node.js 18 and 20 and executes these checks across all workspace packages: + +| Step | Command | +|------|---------| +| Install | `pnpm install --frozen-lockfile` | +| Type check | `pnpm -r --if-present run typecheck` | +| Lint | `pnpm -r --if-present run lint` | +| Test | `pnpm -r --if-present run test` | +| Upload coverage | Artifacts uploaded from `**/coverage/` | + +Before opening a PR, make sure these all pass locally: + +```bash +pnpm -r --if-present run typecheck +pnpm -r --if-present run lint +pnpm -r --if-present run test +``` + +### PR Title Requirement + +PR titles are validated against the [Conventional Commits](https://www.conventionalcommits.org/) spec by [`.github/workflows/pr-title.yml`](.github/workflows/pr-title.yml). + +Your PR title **must** start with one of these prefixes: + +`feat:`, `fix:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, `build:`, `ci:`, `chore:`, `revert:` + +Examples of valid PR titles: +- `feat: add QR code sharing screen` +- `fix: correct card deletion 404 response` +- `chore: update dependencies` + ## Coding Standards - **TypeScript** for all new code @@ -92,10 +129,9 @@ devcard/ 1. Create a feature branch from `main`: `git checkout -b feat/your-feature` 2. Make your changes with clear, descriptive commits -3. Ensure all tests pass: `pnpm test` -4. Ensure linting passes: `pnpm lint` -5. Open a PR against `main` with a clear description of the change -6. Wait for review — maintainers will respond within 48 hours +3. Ensure all checks pass locally (see [CI / Pipeline](#ci--pipeline) above) +4. Open a PR against `main` — the title must follow Conventional Commits format +5. Wait for review — maintainers will respond within 48 hours ## Reporting Issues diff --git a/README.md b/README.md index 136600f..c66e618 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@

One Tap. Every Profile. Every Platform.

Open Source Developer Profile Exchange Platform

+ + CI + GitHub Repo @@ -52,7 +55,7 @@ Each exchange is manual, error-prone, and slow. DevCard fixes this. ### Prerequisites -- Node.js >= 20 +- Node.js >= 18 - pnpm >= 9 - Docker & Docker Compose - React Native development environment ([setup guide](https://reactnative.dev/docs/environment-setup)) @@ -248,7 +251,6 @@ The following error cases are implemented: | Scenario | Status | Response | |----------|--------|----------| | **Create/Update Card** | 400 | `{ error: 'Validation failed', details: parsed.error.flatten() }` — when title or linkIds don't meet constraints | -| **Create/Update Card** | 409 | `{ error: 'Username already taken'}` — when a user with the same username exists | | **Update Card** | 404 | `{ error: 'Card not found' }` — when card ID doesn't exist or doesn't belong to authenticated user | | **Delete Card** | 404 | `{ error: 'Card not found' }` — when card ID doesn't exist or doesn't belong to authenticated user | | **Set Default Card** | 404 | `{ error: 'Card not found' }` — when card ID doesn't exist or doesn't belong to authenticated user | diff --git a/apps/backend/eslint.config.js b/apps/backend/eslint.config.js new file mode 100644 index 0000000..7208672 --- /dev/null +++ b/apps/backend/eslint.config.js @@ -0,0 +1,12 @@ +import js from '@eslint/js'; + +export default [ + js.configs.recommended, + { + files: ['src/**/*.ts'], + rules: {}, + }, + { + ignores: ['dist/**', 'node_modules/**'], + }, +]; diff --git a/apps/backend/package.json b/apps/backend/package.json index b8d1141..8563d2d 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -9,6 +9,7 @@ "start": "node dist/server.js", "test": "vitest run", "test:watch": "vitest", + "typecheck": "tsc --noEmit", "lint": "eslint src/", "db:migrate": "prisma migrate dev", "db:deploy": "prisma migrate deploy", @@ -32,8 +33,10 @@ "zod": "^3.23.0" }, "devDependencies": { + "@eslint/js": "^9.0.0", "@types/node": "^22.0.0", "@types/qrcode": "^1.5.0", + "eslint": "^9.0.0", "pino-pretty": "^13.1.3", "prisma": "^6.0.0", "tsx": "^4.0.0", diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 92fcba4..a089306 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -5,9 +5,10 @@ "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", + "typecheck": "tsc --noEmit", "lint": "eslint .", "start": "react-native start", - "test": "jest" + "test": "jest --passWithNoTests --forceExit" }, "dependencies": { "@devcard/shared": "workspace:*", @@ -54,6 +55,6 @@ "typescript": "^5.8.3" }, "engines": { - "node": ">= 22.11.0" + "node": ">= 18.0.0" } -} \ No newline at end of file +} diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js new file mode 100644 index 0000000..b7d3699 --- /dev/null +++ b/apps/web/eslint.config.js @@ -0,0 +1,10 @@ +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; + +export default [ + js.configs.recommended, + ...svelte.configs['flat/recommended'], + { + ignores: ['.svelte-kit/**', 'build/**', 'node_modules/**'], + }, +]; diff --git a/apps/web/package.json b/apps/web/package.json index 3601215..43d1994 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,16 +8,22 @@ "build": "vite build", "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", + "typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint src/", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "exit 0" }, "dependencies": { "@devcard/shared": "workspace:*" }, "devDependencies": { + "@eslint/js": "^9.0.0", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.50.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", + "eslint": "^9.0.0", + "eslint-plugin-svelte": "^2.46.1", "svelte": "^5.51.0", "svelte-check": "^4.4.2", "typescript": "^5.9.3", diff --git a/package.json b/package.json index bbe44f7..d04727f 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,11 @@ "web": "pnpm --filter @devcard/web dev" }, "engines": { - "node": ">=20.0.0", + "node": ">=18.0.0", "pnpm": ">=9.0.0" }, "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be", "devDependencies": { "concurrently": "^9.2.1" } -} \ No newline at end of file +} diff --git a/packages/shared/eslint.config.js b/packages/shared/eslint.config.js new file mode 100644 index 0000000..7208672 --- /dev/null +++ b/packages/shared/eslint.config.js @@ -0,0 +1,12 @@ +import js from '@eslint/js'; + +export default [ + js.configs.recommended, + { + files: ['src/**/*.ts'], + rules: {}, + }, + { + ignores: ['dist/**', 'node_modules/**'], + }, +]; diff --git a/packages/shared/package.json b/packages/shared/package.json index b3b3ac7..2682d80 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -11,6 +11,8 @@ "test": "vitest run" }, "devDependencies": { + "@eslint/js": "^9.0.0", + "eslint": "^9.0.0", "typescript": "^5.4.0", "vitest": "^2.0.0" }