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
+
+
+
@@ -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"
}