3ac03a2
commit 3ac03a2
63 files changed
+5228 -1364
Showing 63 changed files+5228-1364
README.mdcli/README.mdcli/bin/och.tscli/src/commands/config/index.tsdocs-site/astro.config.mjsdocs-site/public/favicon.pngdocs-site/src/content/docs/administration/deploy-cloudflare.mddocs-site/src/content/docs/administration/deploy-cpanel.mddocs-site/src/content/docs/administration/deploy-cyberpanel.mddocs-site/src/content/docs/administration/deploy-docker.mddocs-site/src/content/docs/administration/deploy-nginx.mddocs-site/src/content/docs/administration/deploy-podman.mddocs-site/src/content/docs/development/database-migrations.mddocs-site/src/content/docs/development/local-dev-setup.mddocs-site/src/content/docs/development/observability.mddocs-site/src/content/docs/development/security-hardening.mddocs-site/src/content/docs/development/testing-pipelines.mddocs-site/src/content/docs/features/ai-review.mddocs-site/src/content/docs/features/automations.mddocs-site/src/content/docs/features/ci-actions.mddocs-site/src/content/docs/features/cli.mddocs-site/src/content/docs/features/developer-metrics.mddocs-site/src/content/docs/features/inbox.mddocs-site/src/content/docs/features/merge-queue.mddocs-site/src/content/docs/features/notifications.mddocs-site/src/content/docs/guides/storage-adapters.mddocs-site/src/content/docs/index.mdxdocs-site/src/content/docs/reference/cli-auth-config.mddocs-site/src/content/docs/reference/cli-automation-insights.mddocs-site/src/content/docs/reference/cli-commands.mddocs-site/src/content/docs/reference/cli-core-commands.mddocs-site/src/content/docs/reference/cli-merge-queue.mddocs-site/src/content/docs/reference/cli-overview.mddocs-site/src/content/docs/reference/cli-stack-workflows.mddocs-site/src/content/docs/tutorials/ai-powered-reviews.mddocs-site/src/content/docs/tutorials/automated-releases.mddocs-site/src/content/docs/tutorials/disaster-recovery.mddocs-site/src/content/docs/tutorials/self-hosted-docker-nginx.mddocs-site/src/content/docs/tutorials/team-onboarding.mddocs-site/src/content/docs/tutorials/your-first-stack.mddocs-site/src/content/docs/tutorials/zero-to-production.mddocs/features/ai-review.mddocs/features/merge-queue.mddocs/guides/storage-adapters.mddocs/index.mddocs/reference/cli-commands.mddocs/tutorials/ai-powered-reviews.mddocs/tutorials/your-first-stack.mdpublic/docs/index.htmlpublic/favicon.pngpublic/logo-dark.pngpublic/logo-light.pngpublic/logo.pngpublic/stack-workflow.svgsrc/components/layout/Header.astrosrc/components/repo/FileExplorer.astrosrc/components/repo/RepoActions.tsxsrc/components/repo/RepoControls.tsxsrc/components/repo/RepoHeader.astrosrc/components/ui/popover.tsxsrc/components/ui/table.tsxsrc/lib/git.tssrc/pages/[owner]/[repo]/index.astro
▼README.md
+92-20
| @@ -1,7 +1,11 @@ | ||
1 | 1 | # OpenCodeHub |
2 | 2 | |
3 | 3 | <p align="center"> |
4 | - <img src="public/logo.svg" alt="OpenCodeHub Logo" width="300"> | |
4 | + <picture> | |
5 | + <source media="(prefers-color-scheme: dark)" srcset="public/logo-dark.png"> | |
6 | + <source media="(prefers-color-scheme: light)" srcset="public/logo-light.png"> | |
7 | + <img src="public/logo-light.png" alt="OpenCodeHub Logo" width="400"/> | |
8 | + </picture> | |
5 | 9 | <br> |
6 | 10 | </p> |
7 | 11 | |
| @@ -32,10 +36,10 @@ | ||
32 | 36 | |
33 | 37 | **All critical security vulnerabilities are now fixed. OpenCodeHub is production-ready!** 🎊 |
34 | 38 | |
35 | -✅ **Rate Limiting** - Prevents brute force attacks | |
36 | -✅ **CSRF Protection** - Cross-site request forgery defense | |
37 | -✅ **Input Validation** - SQL injection & XSS prevention | |
38 | -✅ **Hook Authentication** - Secure git operations | |
39 | +✅ **Rate Limiting** - Prevents brute force attacks | |
40 | +✅ **CSRF Protection** - Cross-site request forgery defense | |
41 | +✅ **Input Validation** - SQL injection & XSS prevention | |
42 | +✅ **Hook Authentication** - Secure git operations | |
39 | 43 | ✅ **Environment Validation** - Safe configuration management |
40 | 44 | |
41 | 45 | 📚 **See [DEPLOYMENT.md](DEPLOYMENT.md) for complete deployment guide** with SSL setup, Nginx configuration, monitoring, database backups, and production best practices. |
| @@ -195,6 +199,7 @@ och init --url https://git.yourcompany.com | ||
195 | 199 | ### CLI Commands Reference |
196 | 200 | |
197 | 201 | **Authentication:** |
202 | + | |
198 | 203 | ```bash |
199 | 204 | och auth login # Interactive login |
200 | 205 | och auth login --token TOKEN # Login with token |
| @@ -203,6 +208,7 @@ och auth whoami # Show current user | ||
203 | 208 | ``` |
204 | 209 | |
205 | 210 | **Stack Management:** |
211 | + | |
206 | 212 | ```bash |
207 | 213 | och stack create \u003cname\u003e # Create new stacked branch |
208 | 214 | och stack view # View current stack (alias: ls) |
| @@ -213,6 +219,7 @@ och stack reorder # Reorder branches in stack | ||
213 | 219 | ``` |
214 | 220 | |
215 | 221 | **Repository:** |
222 | + | |
216 | 223 | ```bash |
217 | 224 | och init # Initialize repo for OpenCodeHub |
218 | 225 | och sync # Bidirectional sync with remote |
| @@ -236,7 +243,7 @@ och status # Show stack status (alias: st) | ||
236 | 243 | |
237 | 244 | #### How It Works |
238 | 245 | |
239 | - | |
246 | + | |
240 | 247 | <br> |
241 | 248 | |
242 | 249 | Each PR builds on the previous one. When #123 merges, #124 automatically rebases. |
| @@ -244,6 +251,7 @@ Each PR builds on the previous one. When #123 merges, #124 automatically rebases | ||
244 | 251 | #### Creating a Stack |
245 | 252 | |
246 | 253 | **Using CLI:** |
254 | + | |
247 | 255 | ```bash |
248 | 256 | # 1. Create base branch |
249 | 257 | git checkout -b feature/auth |
| @@ -263,15 +271,16 @@ och stack submit | ||
263 | 271 | ``` |
264 | 272 | |
265 | 273 | **Using Web UI:** |
274 | + | |
266 | 275 | 1. Create first PR normally |
267 | 276 | 2. When creating second PR, select "Stack on PR #123" |
268 | 277 | 3. System tracks dependencies automatically |
269 | 278 | |
270 | 279 | #### Stack Features |
271 | 280 | |
272 | -✅ **Auto-Rebasing**: When base PR merges, dependent PRs rebase automatically | |
273 | -✅ **Dependency Visualization**: See entire stack in PR view | |
274 | -✅ **Smart Merge Queue**: Merges in correct order | |
281 | +✅ **Auto-Rebasing**: When base PR merges, dependent PRs rebase automatically | |
282 | +✅ **Dependency Visualization**: See entire stack in PR view | |
283 | +✅ **Smart Merge Queue**: Merges in correct order | |
275 | 284 | ✅ **Conflict Detection**: Alerts on rebase conflicts |
276 | 285 | |
277 | 286 | --- |
| @@ -283,6 +292,7 @@ och stack submit | ||
283 | 292 | #### Setup |
284 | 293 | |
285 | 294 | **1. Configure AI Provider:** |
295 | + | |
286 | 296 | ```env |
287 | 297 | # In .env |
288 | 298 | AI_PROVIDER=openai # or anthropic |
| @@ -293,6 +303,7 @@ ANTHROPIC_API_KEY=sk-ant-... | ||
293 | 303 | |
294 | 304 | **2. Enable for Repository:** |
295 | 305 | Navigate to **Repository → Settings → AI Review** |
306 | + | |
296 | 307 | - ✅ Enable AI Review |
297 | 308 | - Set review triggers (on PR open, on push, manual) |
298 | 309 | - Configure review rules |
| @@ -300,24 +311,28 @@ Navigate to **Repository → Settings → AI Review** | ||
300 | 311 | #### What AI Reviews Catch |
301 | 312 | |
302 | 313 | 🔍 **Security Vulnerabilities** |
314 | + | |
303 | 315 | - SQL injection risks |
304 | 316 | - XSS vulnerabilities |
305 | 317 | - Authentication bypasses |
306 | 318 | - Secrets in code |
307 | 319 | |
308 | 320 | ⚡ **Performance Issues** |
321 | + | |
309 | 322 | - N+1 queries |
310 | 323 | - Inefficient algorithms |
311 | 324 | - Memory leaks |
312 | 325 | - Blocking operations |
313 | 326 | |
314 | 327 | 📝 **Code Quality** |
328 | + | |
315 | 329 | - Best practice violations |
316 | 330 | - Code smells |
317 | 331 | - Incomplete error handling |
318 | 332 | - Missing documentation |
319 | 333 | |
320 | 334 | 🐛 **Potential Bugs** |
335 | + | |
321 | 336 | - Null pointer exceptions |
322 | 337 | - Race conditions |
323 | 338 | - Off-by-one errors |
| @@ -329,6 +344,7 @@ Navigate to **Repository → Settings → AI Review** | ||
329 | 344 | AI review runs automatically on every PR based on configured triggers. |
330 | 345 | |
331 | 346 | **Manual:** |
347 | + | |
332 | 348 | ```bash |
333 | 349 | # Via CLI |
334 | 350 | och review ai \u003cPR_NUMBER\u003e |
| @@ -339,19 +355,21 @@ och review ai \u003cPR_NUMBER\u003e | ||
339 | 355 | #### Review Output |
340 | 356 | |
341 | 357 | AI provides: |
358 | + | |
342 | 359 | - **Severity Score**: Critical / High / Medium / Low / Info |
343 | 360 | - **Specific Line Comments**: Inline code suggestions |
344 | 361 | - **Summary Report**: Overall code quality assessment |
345 | 362 | - **Fix Suggestions**: Actionable improvements |
346 | 363 | |
347 | 364 | **Example:** |
365 | + | |
348 | 366 | ```text |
349 | 367 | 🔴 CRITICAL: SQL Injection Vulnerability |
350 | 368 | File: src/api/users.ts:45 |
351 | 369 | Issue: User input directly concatenated into SQL query |
352 | 370 | Fix: Use parameterized queries or ORM |
353 | 371 | |
354 | -🟡 MEDIUM: Performance Concern | |
372 | +🟡 MEDIUM: Performance Concern | |
355 | 373 | File: src/lib/data.ts:128 |
356 | 374 | Issue: N+1 query in loop (1000+ iterations) |
357 | 375 | Fix: Use batch loading or eager loading |
| @@ -366,21 +384,25 @@ Fix: Use batch loading or eager loading | ||
366 | 384 | #### Features |
367 | 385 | |
368 | 386 | **Stack-Aware Merging:** |
387 | + | |
369 | 388 | - Detects PR dependencies |
370 | 389 | - Merges in correct order automatically |
371 | 390 | - Handles entire stacks atomically |
372 | 391 | |
373 | 392 | **CI Optimization:** |
393 | + | |
374 | 394 | - Batches compatible PRs |
375 | 395 | - Runs CI once for multiple PRs |
376 | 396 | - Reduces CI cost by ~60% |
377 | 397 | |
378 | 398 | **Automatic Rebasing:** |
399 | + | |
379 | 400 | - Detects conflicts early |
380 | 401 | - Auto-rebases on base changes |
381 | 402 | - Alerts author on conflicts |
382 | 403 | |
383 | 404 | **Smart Ordering:** |
405 | + | |
384 | 406 | - Priority-based queue |
385 | 407 | - Dependency resolution |
386 | 408 | - Hotfix fast-tracking |
| @@ -388,6 +410,7 @@ Fix: Use batch loading or eager loading | ||
388 | 410 | #### Using Merge Queue |
389 | 411 | |
390 | 412 | **Add to Queue:** |
413 | + | |
391 | 414 | ```bash |
392 | 415 | # Via CLI |
393 | 416 | och queue add \u003cPR_NUMBER\u003e |
| @@ -397,6 +420,7 @@ Click "Add to Merge Queue" button on approved PR | ||
397 | 420 | ``` |
398 | 421 | |
399 | 422 | **Queue Status:** |
423 | + | |
400 | 424 | ```bash |
401 | 425 | # View queue |
402 | 426 | och queue list |
| @@ -419,6 +443,7 @@ Position PR Title Status | ||
419 | 443 | #### Queue Configuration |
420 | 444 | |
421 | 445 | **Repository → Settings → Merge Queue:** |
446 | + | |
422 | 447 | - **Require CI**: Only merge if all checks pass |
423 | 448 | - **Batch Window**: Wait time to batch PRs (default: 5min) |
424 | 449 | - **Max Batch Size**: Max PRs to merge together (default: 3) |
| @@ -433,24 +458,28 @@ Position PR Title Status | ||
433 | 458 | #### Available Metrics |
434 | 459 | |
435 | 460 | **PR Velocity:** |
461 | + | |
436 | 462 | - PRs merged per week |
437 | 463 | - Average time to merge |
438 | 464 | - Stack success rate |
439 | 465 | - Review cycle time |
440 | 466 | |
441 | 467 | **Review Efficiency:** |
468 | + | |
442 | 469 | - Average review time |
443 | 470 | - Reviews per day |
444 | 471 | - Review quality score |
445 | 472 | - Reviewer responsiveness |
446 | 473 | |
447 | 474 | **Team Performance:** |
475 | + | |
448 | 476 | - Lines changed per PR |
449 | 477 | - PR size distribution |
450 | 478 | - Revert rate |
451 | 479 | - CI success rate |
452 | 480 | |
453 | 481 | **Stack Metrics:** |
482 | + | |
454 | 483 | - % PRs that use stacks |
455 | 484 | - Average stack depth |
456 | 485 | - Stack merge success rate |
| @@ -459,16 +488,19 @@ Position PR Title Status | ||
459 | 488 | #### Viewing Metrics |
460 | 489 | |
461 | 490 | **Personal Dashboard:** |
491 | + | |
462 | 492 | ```text |
463 | 493 | /metrics/me |
464 | 494 | ``` |
465 | 495 | |
466 | 496 | **Team Dashboard (Admin):** |
497 | + | |
467 | 498 | ```text |
468 | 499 | /admin/metrics |
469 | 500 | ``` |
470 | 501 | |
471 | 502 | **Via CLI:** |
503 | + | |
472 | 504 | ```bash |
473 | 505 | och metrics show |
474 | 506 | och metrics show --user @username |
| @@ -500,6 +532,7 @@ Review Efficiency | ||
500 | 532 | #### Setup |
501 | 533 | |
502 | 534 | **1. Create Slack App:** |
535 | + | |
503 | 536 | 1. Go to https://api.slack.com/apps |
504 | 537 | 2. Create New App → From Scratch |
505 | 538 | 3. Add OAuth Scopes: |
| @@ -510,6 +543,7 @@ Review Efficiency | ||
510 | 543 | |
511 | 544 | **2. Connect to OpenCodeHub:** |
512 | 545 | Navigate to **Admin → Integrations → Slack** |
546 | + | |
513 | 547 | ``` |
514 | 548 | Workspace Name: Your Team |
515 | 549 | Bot Token: xoxb-... |
| @@ -518,6 +552,7 @@ Signing Secret: ... | ||
518 | 552 | |
519 | 553 | **3. Map Channels:** |
520 | 554 | **Repository → Settings → Slack** |
555 | + | |
521 | 556 | ``` |
522 | 557 | #engineering → All PR events |
523 | 558 | #deployments → Merges only |
| @@ -531,6 +566,7 @@ Users can link their Slack account: | ||
531 | 566 | #### Notification Types |
532 | 567 | |
533 | 568 | **PR Events:** |
569 | + | |
534 | 570 | - 🎉 PR opened |
535 | 571 | - ✅ PR approved |
536 | 572 | - 🔀 PR merged |
| @@ -539,17 +575,20 @@ Users can link their Slack account: | ||
539 | 575 | - 🔴 Changes requested |
540 | 576 | |
541 | 577 | **CI/CD Events:** |
578 | + | |
542 | 579 | - ✅ Build passed |
543 | 580 | - ❌ Build failed |
544 | 581 | - 🚀 Deployment started |
545 | 582 | - ✅ Deployment successful |
546 | 583 | |
547 | 584 | **Code Review:** |
585 | + | |
548 | 586 | - 🤖 AI review completed |
549 | 587 | - 🔍 Security issue found |
550 | 588 | - ⚠️ Performance concern |
551 | 589 | |
552 | 590 | **Merge Queue:** |
591 | + | |
553 | 592 | - ⏭️ Added to queue |
554 | 593 | - ✅ Merged from queue |
555 | 594 | - ⚠️ Removed (conflict) |
| @@ -557,6 +596,7 @@ Users can link their Slack account: | ||
557 | 596 | #### Interactive Actions |
558 | 597 | |
559 | 598 | Notifications include action buttons: |
599 | + | |
560 | 600 | ``` |
561 | 601 | [swadhin] opened PR#125: Add login UI |
562 | 602 | Stack: #123 → #124 → #125 • files →32 • LoC +420/-18 |
| @@ -565,6 +605,7 @@ Stack: #123 → #124 → #125 • files →32 • LoC +420/-18 | ||
565 | 605 | ``` |
566 | 606 | |
567 | 607 | Click buttons to: |
608 | + | |
568 | 609 | - Approve/reject directly from Slack |
569 | 610 | - Request changes with comment |
570 | 611 | - Add to merge queue |
| @@ -611,12 +652,13 @@ och stack view | ||
611 | 652 | ``` |
612 | 653 | |
613 | 654 | **Output:** |
655 | + | |
614 | 656 | ``` |
615 | 657 | 📚 Current Stack |
616 | 658 | |
617 | 659 | main (base) |
618 | 660 | └─ #301: profile-db ✅ Approved |
619 | - └─ #302: profile-api 🔍 In Review | |
661 | + └─ #302: profile-api 🔍 In Review | |
620 | 662 | └─ #303: profile-ui ⏳ AI Review Running |
621 | 663 | ``` |
622 | 664 | |
| @@ -657,7 +699,7 @@ git push | ||
657 | 699 | |
658 | 700 | # Developer B: Builds on A's work (before merge!) |
659 | 701 | git checkout api/payments |
660 | -git checkout -b api/payment-processing | |
702 | +git checkout -b api/payment-processing | |
661 | 703 | # ... implements payment processing ... |
662 | 704 | git commit -m "Add payment processing" |
663 | 705 | git push |
| @@ -682,7 +724,6 @@ git checkout -b ui/payment-form | ||
682 | 724 | |
683 | 725 | --- |
684 | 726 | |
685 | - | |
686 | 727 | ## ⚙️ Configuration |
687 | 728 | |
688 | 729 | ### Critical Environment Variables |
| @@ -704,18 +745,21 @@ HOST=0.0.0.0 | ||
704 | 745 | ### Database Configuration |
705 | 746 | |
706 | 747 | **PostgreSQL (Recommended for Production):** |
748 | + | |
707 | 749 | ```env |
708 | 750 | DATABASE_DRIVER=postgres |
709 | 751 | DATABASE_URL=postgresql://user:password@localhost:5432/opencodehub |
710 | 752 | ``` |
711 | 753 | |
712 | 754 | **MySQL:** |
755 | + | |
713 | 756 | ```env |
714 | 757 | DATABASE_DRIVER=mysql |
715 | 758 | DATABASE_URL=mysql://user:password@localhost:3306/opencodehub |
716 | 759 | ``` |
717 | 760 | |
718 | 761 | **SQLite (Development Only):** |
762 | + | |
719 | 763 | ```env |
720 | 764 | DATABASE_DRIVER=sqlite |
721 | 765 | DATABASE_URL=./data/opencodehub.db |
| @@ -726,12 +770,14 @@ DATABASE_URL=./data/opencodehub.db | ||
726 | 770 | Storage can be configured via Admin Panel (`/admin/storage`) or environment variables: |
727 | 771 | |
728 | 772 | **Local Storage:** |
773 | + | |
729 | 774 | ```env |
730 | 775 | STORAGE_TYPE=local |
731 | 776 | STORAGE_PATH=./data/storage |
732 | 777 | ``` |
733 | 778 | |
734 | 779 | **S3/MinIO/R2:** |
780 | + | |
735 | 781 | ```env |
736 | 782 | STORAGE_TYPE=s3 |
737 | 783 | STORAGE_BUCKET=opencodehub |
| @@ -742,6 +788,7 @@ S3_SECRET_KEY=your-secret-key | ||
742 | 788 | ``` |
743 | 789 | |
744 | 790 | **Google Drive (via Admin Panel):** |
791 | + | |
745 | 792 | - Navigate to `/admin/storage` |
746 | 793 | - Select "Google Drive" |
747 | 794 | - Enter Client ID, Secret, and Refresh Token |
| @@ -750,6 +797,7 @@ S3_SECRET_KEY=your-secret-key | ||
750 | 797 | ### Security Configuration |
751 | 798 | |
752 | 799 | **Rate Limiting:** |
800 | + | |
753 | 801 | ```env |
754 | 802 | RATE_LIMIT_AUTH=5 # Login attempts per 15 minutes |
755 | 803 | RATE_LIMIT_API=100 # API requests per minute |
| @@ -758,6 +806,7 @@ RATE_LIMIT_SKIP_DEV=false # Never skip in production | ||
758 | 806 | ``` |
759 | 807 | |
760 | 808 | **CSRF Protection:** |
809 | + | |
761 | 810 | ```env |
762 | 811 | CSRF_SKIP_DEV=false # Never skip in production |
763 | 812 | ``` |
| @@ -765,6 +814,7 @@ CSRF_SKIP_DEV=false # Never skip in production | ||
765 | 814 | ### Optional Services |
766 | 815 | |
767 | 816 | **Email (SMTP):** |
817 | + | |
768 | 818 | ```env |
769 | 819 | SMTP_HOST=smtp.example.com |
770 | 820 | SMTP_PORT=587 |
| @@ -774,11 +824,13 @@ [email protected] | ||
774 | 824 | ``` |
775 | 825 | |
776 | 826 | **Redis (Caching & Sessions):** |
827 | + | |
777 | 828 | ```env |
778 | 829 | REDIS_URL=redis://localhost:6379 |
779 | 830 | ``` |
780 | 831 | |
781 | 832 | **Error Monitoring (Sentry):** |
833 | + | |
782 | 834 | ```env |
783 | 835 | SENTRY_DSN=https://... |
784 | 836 | ENABLE_TRACING=true |
| @@ -793,18 +845,21 @@ OpenCodeHub implements **production-grade security** out of the box: | ||
793 | 845 | ### Implemented Security Features |
794 | 846 | |
795 | 847 | ✅ **Authentication & Authorization** |
848 | + | |
796 | 849 | - JWT-based sessions with automatic expiration |
797 | 850 | - Bcrypt password hashing (10 rounds) |
798 | 851 | - 2FA/TOTP support |
799 | 852 | - Role-based access control (RBAC) |
800 | 853 | |
801 | 854 | ✅ **Attack Prevention** |
855 | + | |
802 | 856 | - **Rate Limiting**: Prevents brute force (5 login attempts per 15min) |
803 | 857 | - **CSRF Protection**: Double-submit cookie pattern on all state-changing operations |
804 | 858 | - **Input Validation**: Zod schemas prevent SQL injection and XSS |
805 | 859 | - **XSS Sanitization**: Markdown rendered with `rehype-sanitize` |
806 | 860 | |
807 | 861 | ✅ **Operational Security** |
862 | + | |
808 | 863 | - **Audit Logging**: All admin actions logged to database |
809 | 864 | - **Hook Authentication**: Git hooks protected with shared secrets |
810 | 865 | - **Session Management**: Automatic cleanup and rotation |
| @@ -879,7 +934,7 @@ ENABLE_REGISTRATION=true # Set false for invite-only | ||
879 | 934 | |
880 | 935 | ```yaml |
881 | 936 | # docker-compose.prod.yml |
882 | -version: '3.8' | |
937 | +version: "3.8" | |
883 | 938 | |
884 | 939 | services: |
885 | 940 | app: |
| @@ -977,6 +1032,7 @@ find /backups -name "opencodehub-*.sql.gz" -mtime +30 -delete | ||
977 | 1032 | All authenticated endpoints require a session cookie or `Authorization: Bearer <token>` header. |
978 | 1033 | |
979 | 1034 | **Register:** |
1035 | + | |
980 | 1036 | ```bash |
981 | 1037 | POST /api/auth/register |
982 | 1038 | Content-Type: application/json |
| @@ -997,6 +1053,7 @@ Content-Type: application/json | ||
997 | 1053 | ``` |
998 | 1054 | |
999 | 1055 | **Login:** |
1056 | + | |
1000 | 1057 | ```bash |
1001 | 1058 | POST /api/auth/login |
1002 | 1059 | Content-Type: application/json |
| @@ -1017,6 +1074,7 @@ Content-Type: application/json | ||
1017 | 1074 | ### Repositories |
1018 | 1075 | |
1019 | 1076 | **Create Repository:** |
1077 | + | |
1020 | 1078 | ```bash |
1021 | 1079 | POST /api/repos |
1022 | 1080 | Authorization: Bearer <token> |
| @@ -1030,6 +1088,7 @@ Content-Type: application/json | ||
1030 | 1088 | ``` |
1031 | 1089 | |
1032 | 1090 | **Branch Protection:** |
1091 | + | |
1033 | 1092 | ```bash |
1034 | 1093 | POST /api/repos/{repoId}/branch-protection |
1035 | 1094 | Authorization: Bearer <token> |
| @@ -1046,12 +1105,14 @@ Content-Type: application/json | ||
1046 | 1105 | ### Admin Endpoints |
1047 | 1106 | |
1048 | 1107 | **List Users (Admin Only):** |
1108 | + | |
1049 | 1109 | ```bash |
1050 | 1110 | GET /admin/users?q=search&page=1 |
1051 | 1111 | Cookie: och_session=... |
1052 | 1112 | ``` |
1053 | 1113 | |
1054 | 1114 | **Update Storage Config (Admin Only):** |
1115 | + | |
1055 | 1116 | ```bash |
1056 | 1117 | POST /api/admin/config/storage |
1057 | 1118 | Cookie: och_session=... |
| @@ -1068,6 +1129,7 @@ Content-Type: application/json | ||
1068 | 1129 | ### Rate Limits |
1069 | 1130 | |
1070 | 1131 | All endpoints return rate limit headers: |
1132 | + | |
1071 | 1133 | ```http |
1072 | 1134 | X-RateLimit-Limit: 100 |
1073 | 1135 | X-RateLimit-Remaining: 95 |
| @@ -1096,12 +1158,14 @@ Retry-After: 30 (when rate limited) | ||
1096 | 1158 | oning Repositories |
1097 | 1159 | |
1098 | 1160 | **SSH (Recommended):** |
1161 | + | |
1099 | 1162 | ```bash |
1100 | 1163 | # Add your SSH key in Settings → SSH Keys |
1101 | 1164 | git clone [email protected]:username/repo.git |
1102 | 1165 | ``` |
1103 | 1166 | |
1104 | 1167 | **HTTPS:** |
1168 | + | |
1105 | 1169 | ```bash |
1106 | 1170 | git clone https://your-domain.com/username/repo.git |
1107 | 1171 | # Username: your-username |
| @@ -1137,21 +1201,21 @@ jobs: | ||
1137 | 1201 | runs-on: ubuntu-latest |
1138 | 1202 | steps: |
1139 | 1203 | - uses: actions/checkout@v4 |
1140 | - | |
1204 | + | |
1141 | 1205 | - name: Setup Node.js |
1142 | 1206 | uses: actions/setup-node@v4 |
1143 | 1207 | with: |
1144 | - node-version: '20' | |
1145 | - | |
1208 | + node-version: "20" | |
1209 | + | |
1146 | 1210 | - name: Install |
1147 | 1211 | run: npm ci |
1148 | - | |
1212 | + | |
1149 | 1213 | - name: Test |
1150 | 1214 | run: npm test |
1151 | - | |
1215 | + | |
1152 | 1216 | - name: Build |
1153 | 1217 | run: npm run build |
1154 | - | |
1218 | + | |
1155 | 1219 | - name: Upload Artifacts |
1156 | 1220 | uses: actions/upload-artifact@v3 |
1157 | 1221 | with: |
| @@ -1166,25 +1230,30 @@ jobs: | ||
1166 | 1230 | ### Common Issues |
1167 | 1231 | |
1168 | 1232 | **"Rate limit exceeded" on login:** |
1233 | + | |
1169 | 1234 | - Default: 5 attempts per 15 minutes |
1170 | 1235 | - Solution: Wait 15 minutes or adjust `RATE_LIMIT_AUTH` in `.env` |
1171 | 1236 | |
1172 | 1237 | **"CSRF token validation failed":** |
1238 | + | |
1173 | 1239 | - Ensure cookies are enabled |
1174 | 1240 | - Check `SITE_URL` matches your actual domain |
1175 | 1241 | - Verify HTTPS in production |
1176 | 1242 | |
1177 | 1243 | **"Unauthorized" on git hooks:** |
1244 | + | |
1178 | 1245 | - Check `INTERNAL_HOOK_SECRET` is set |
1179 | 1246 | - Verify hooks have correct secret in curl headers |
1180 | 1247 | - Re-run `git init` on repositories to reinstall hooks |
1181 | 1248 | |
1182 | 1249 | **Database connection errors:** |
1250 | + | |
1183 | 1251 | - Verify `DATABASE_URL` format |
1184 | 1252 | - Check database server is running |
1185 | 1253 | - Ensure user has correct permissions |
1186 | 1254 | |
1187 | 1255 | **Storage upload fails:** |
1256 | + | |
1188 | 1257 | - Test connection in `/admin/storage` |
1189 | 1258 | - Verify S3 credentials and bucket permissions |
1190 | 1259 | - Check firewall rules for external storage |
| @@ -1225,14 +1294,17 @@ curl http://localhost:3000/api/health | ||
1225 | 1294 | We are constantly improving OpenCodeHub to make it the ultimate self-hosted development platform. |
1226 | 1295 | |
1227 | 1296 | ### Q2 2026: Advanced AI Agents |
1297 | + | |
1228 | 1298 | - [ ] **Auto-Fix Agents**: Autonomous agents that can silently fix lint errors and simple bugs. |
1229 | 1299 | - [ ] **Test Generation**: Automatically generate unit and integration tests for new code. |
1230 | 1300 | |
1231 | 1301 | ### Q3 2026: Federation & Social |
1302 | + | |
1232 | 1303 | - [ ] **ActivityPub Support**: Follow users and repositories across different OpenCodeHub instances. |
1233 | 1304 | - [ ] **Federated Merge Requests**: Cross-instance collaboration (like Email but better). |
1234 | 1305 | |
1235 | 1306 | ### Q4 2026: Ecosystem Expansion |
1307 | + | |
1236 | 1308 | - [ ] **Mobile App**: Native iOS and Android apps for reviewing code on the go. |
1237 | 1309 | - [ ] **IDE Extensions**: dedicated VS Code and JetBrains plugins. |
1238 | 1310 | |
1239 | 1311 | |
▼cli/README.md
+15-7
| @@ -1,5 +1,9 @@ | ||
1 | 1 | <p align="center"> |
2 | - <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo.svg" alt="OpenCodeHub CLI" width="120" /> | |
2 | + <picture> | |
3 | + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-dark.png"> | |
4 | + <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png"> | |
5 | + <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png" alt="OpenCodeHub CLI" width="400" /> | |
6 | + </picture> | |
3 | 7 | </p> |
4 | 8 | |
5 | 9 | <h1 align="center">OpenCodeHub CLI</h1> |
| @@ -107,6 +111,7 @@ och repo list | ||
107 | 111 | ## 🎨 Beautiful Output Examples |
108 | 112 | |
109 | 113 | ### Push Command |
114 | + | |
110 | 115 | ``` |
111 | 116 | ℹ Pushing to swadhinbiswas/myrepo |
112 | 117 | Branch: master |
| @@ -122,7 +127,7 @@ och repo list | ||
122 | 127 | ✔ Uploaded 49.17 KB in 0.52s (94.56 KB/s) |
123 | 128 | |
124 | 129 | remote: Processing: 100% (90/90), done. |
125 | -remote: | |
130 | +remote: | |
126 | 131 | To https://opencodehub.com/swadhinbiswas/myrepo.git |
127 | 132 | abc1234..def5678 master -> master |
128 | 133 | |
| @@ -140,6 +145,7 @@ To https://opencodehub.com/swadhinbiswas/myrepo.git | ||
140 | 145 | ``` |
141 | 146 | |
142 | 147 | ### Clone Command |
148 | + | |
143 | 149 | ``` |
144 | 150 | ℹ Cloning swadhinbiswas/awesome-project |
145 | 151 | |
| @@ -164,6 +170,7 @@ Receiving objects: 100% (234/234), 1.23 MiB | 2.45 MiB/s, done. | ||
164 | 170 | ``` |
165 | 171 | |
166 | 172 | ### Create Command |
173 | + | |
167 | 174 | ``` |
168 | 175 | ℹ Creating 🌐 my-new-repo |
169 | 176 | Description: An awesome new project |
| @@ -171,7 +178,7 @@ Receiving objects: 100% (234/234), 1.23 MiB | 2.45 MiB/s, done. | ||
171 | 178 | ✔ Repository created |
172 | 179 | |
173 | 180 | ✨ SUCCESS! ✨ |
174 | - | |
181 | + | |
175 | 182 | Repository swadhinbiswas/my-new-repo is ready! |
176 | 183 | |
177 | 184 | ╭─────────────────────────────────────────╮ |
| @@ -204,15 +211,15 @@ jobs: | ||
204 | 211 | runs-on: ubuntu-latest |
205 | 212 | steps: |
206 | 213 | - uses: actions/checkout@v4 |
207 | - | |
214 | + | |
208 | 215 | - name: Setup Node |
209 | 216 | uses: actions/setup-node@v4 |
210 | 217 | with: |
211 | - node-version: '20' | |
212 | - | |
218 | + node-version: "20" | |
219 | + | |
213 | 220 | - name: Install CLI |
214 | 221 | run: npm install -g opencodehub-cli |
215 | - | |
222 | + | |
216 | 223 | - name: Push to OpenCodeHub |
217 | 224 | run: | |
218 | 225 | och auth login --token ${{ secrets.OCH_TOKEN }} |
| @@ -247,6 +254,7 @@ och config set defaultBranch main | ||
247 | 254 | ## 📦 What's New in v1.1.0 |
248 | 255 | |
249 | 256 | ✨ **Production-Grade UI Overhaul** |
257 | + | |
250 | 258 | - GitHub-like progress indicators |
251 | 259 | - Beautiful ASCII art and gradients |
252 | 260 | - Real-time upload/download speeds |
253 | 261 | |
▼cli/bin/och.ts
+71-21
| @@ -38,6 +38,10 @@ import { stackCommands } from "../src/commands/stack/index.js"; | ||
38 | 38 | const git = simpleGit(); |
39 | 39 | const program = new Command(); |
40 | 40 | |
41 | +if (!process.stdout.isTTY) { | |
42 | + chalk.level = 0; | |
43 | +} | |
44 | + | |
41 | 45 | // Custom help formatter with Dracula theme |
42 | 46 | program.configureHelp({ |
43 | 47 | sortSubcommands: true, |
| @@ -60,33 +64,79 @@ program.addHelpText("beforeAll", () => { | ||
60 | 64 | ); // Dracula comment gray |
61 | 65 | }); |
62 | 66 | |
67 | +function isHiddenCommand(cmd: Command) { | |
68 | + return Boolean((cmd as { hidden?: boolean }).hidden); | |
69 | +} | |
70 | + | |
71 | +function buildCommandTable() { | |
72 | + const commands = program.commands | |
73 | + .filter((cmd) => !isHiddenCommand(cmd)) | |
74 | + .map((cmd) => ({ | |
75 | + name: cmd.name(), | |
76 | + desc: cmd.description() || "", | |
77 | + })) | |
78 | + .sort((a, b) => a.name.localeCompare(b.name)); | |
79 | + | |
80 | + const maxDescWidth = 46; | |
81 | + const nameWidth = Math.max( | |
82 | + "Command".length, | |
83 | + ...commands.map((c) => c.name.length), | |
84 | + ); | |
85 | + const descWidth = Math.min( | |
86 | + maxDescWidth, | |
87 | + Math.max("Description".length, ...commands.map((c) => c.desc.length)), | |
88 | + ); | |
89 | + | |
90 | + const top = ` ┌${"─".repeat(nameWidth + 2)}┬${"─".repeat(descWidth + 2)}┐`; | |
91 | + const header = | |
92 | + ` │ ${chalk.hex("#50fa7b").bold("Command".padEnd(nameWidth))} ` + | |
93 | + `│ ${chalk.hex("#50fa7b").bold("Description".padEnd(descWidth))} │`; | |
94 | + const mid = ` ├${"─".repeat(nameWidth + 2)}┼${"─".repeat(descWidth + 2)}┤`; | |
95 | + | |
96 | + const rows = commands.map(({ name, desc }) => { | |
97 | + const clipped = | |
98 | + desc.length > descWidth ? `${desc.slice(0, descWidth - 1)}…` : desc; | |
99 | + return ( | |
100 | + ` │ ${chalk.hex("#ff79c6")(name.padEnd(nameWidth))} ` + | |
101 | + `│ ${chalk.hex("#f8f8f2")(clipped.padEnd(descWidth))} │` | |
102 | + ); | |
103 | + }); | |
104 | + | |
105 | + const bottom = ` └${"─".repeat(nameWidth + 2)}┴${"─".repeat(descWidth + 2)}┘`; | |
106 | + | |
107 | + return [top, header, mid, ...rows, bottom].join("\n"); | |
108 | +} | |
109 | + | |
63 | 110 | // Customize command help colors with Dracula theme |
64 | 111 | const originalHelp = program.helpInformation.bind(program); |
65 | 112 | program.helpInformation = function () { |
66 | 113 | const help = originalHelp(); |
114 | + const commandsIndex = help.indexOf("Commands:"); | |
115 | + const commandsBlock = | |
116 | + commandsIndex >= 0 ? help.slice(0, commandsIndex) : help; | |
117 | + | |
118 | + const styled = commandsBlock | |
119 | + // Usage line - Dracula cyan | |
120 | + .replace( | |
121 | + /^Usage: (.+)$/gm, | |
122 | + (_, usage) => | |
123 | + chalk.hex("#8be9fd").bold("Usage: ") + chalk.hex("#f8f8f2")(usage), | |
124 | + ) | |
125 | + // Section headers - Dracula green | |
126 | + .replace(/^Options:$/gm, "\n" + chalk.hex("#50fa7b").bold("Options:")) | |
127 | + // Flags - Dracula purple | |
128 | + .replace(/(-[a-zA-Z-]+)/g, chalk.hex("#bd93f9")("$1")) | |
129 | + // Arguments - Dracula yellow | |
130 | + .replace(/<([^>]+)>/g, chalk.hex("#f1fa8c")("<$1>")) | |
131 | + // Optional params - Dracula comment | |
132 | + .replace(/\[([^\]]+)\]/g, chalk.hex("#6272a4")("[$1]")); | |
67 | 133 | |
68 | 134 | return ( |
69 | - help | |
70 | - // Usage line - Dracula cyan | |
71 | - .replace( | |
72 | - /^Usage: (.+)$/gm, | |
73 | - (_, usage) => | |
74 | - chalk.hex("#8be9fd").bold("Usage: ") + chalk.hex("#f8f8f2")(usage), | |
75 | - ) | |
76 | - // Section headers - Dracula green | |
77 | - .replace(/^Options:$/gm, "\n" + chalk.hex("#50fa7b").bold("Options:")) | |
78 | - .replace(/^Commands:$/gm, "\n" + chalk.hex("#50fa7b").bold("Commands:")) | |
79 | - // Flags - Dracula purple | |
80 | - .replace(/(-[a-zA-Z-]+)/g, chalk.hex("#bd93f9")("$1")) | |
81 | - // Arguments - Dracula yellow | |
82 | - .replace(/<([^>]+)>/g, chalk.hex("#f1fa8c")("<$1>")) | |
83 | - // Optional params - Dracula comment | |
84 | - .replace(/\[([^\]]+)\]/g, chalk.hex("#6272a4")("[$1]")) | |
85 | - // Command names at start of lines - Dracula pink | |
86 | - .replace( | |
87 | - /^(\s+)([a-z-]+)(\s)/gm, | |
88 | - (match, space, cmd, after) => space + chalk.hex("#ff79c6")(cmd) + after, | |
89 | - ) | |
135 | + styled + | |
136 | + "\n" + | |
137 | + chalk.hex("#50fa7b").bold("\nCommands:") + | |
138 | + "\n" + | |
139 | + buildCommandTable() | |
90 | 140 | ); |
91 | 141 | }; |
92 | 142 | |
93 | 143 | |
▼cli/src/commands/config/index.ts
+6-6
| @@ -96,7 +96,7 @@ configCommands | ||
96 | 96 | const value = config.get(key as keyof OchConfig); |
97 | 97 | |
98 | 98 | if (key === "token") { |
99 | - if (value) { | |
99 | + if (typeof value === "string" && value) { | |
100 | 100 | console.log(value.slice(0, 12) + "..."); |
101 | 101 | } else { |
102 | 102 | console.log(chalk.dim("(not set)")); |
| @@ -139,11 +139,11 @@ configCommands | ||
139 | 139 | key === "insecure" ? value === "true" || value === "1" : value; |
140 | 140 | |
141 | 141 | config.set(key as keyof OchConfig, normalizedValue as any); |
142 | - console.log( | |
143 | - chalk.green( | |
144 | - `✓ Set ${key} = ${key === "token" ? value.slice(0, 12) + "..." : value}`, | |
145 | - ), | |
146 | - ); | |
142 | + const displayValue = | |
143 | + key === "token" && typeof normalizedValue === "string" | |
144 | + ? `${normalizedValue.slice(0, 12)}...` | |
145 | + : String(normalizedValue); | |
146 | + console.log(chalk.green(`✓ Set ${key} = ${displayValue}`)); | |
147 | 147 | }); |
148 | 148 | |
149 | 149 | // Config Unset |
150 | 150 | |
▼docs-site/astro.config.mjs
+106-57
| @@ -1,62 +1,111 @@ | ||
1 | 1 | // @ts-check |
2 | -import { defineConfig } from 'astro/config'; | |
3 | -import starlight from '@astrojs/starlight'; | |
2 | +import starlight from "@astrojs/starlight"; | |
3 | +import { defineConfig } from "astro/config"; | |
4 | 4 | |
5 | 5 | // https://astro.build/config |
6 | 6 | export default defineConfig({ |
7 | - integrations: [ | |
8 | - starlight({ | |
9 | - title: 'OpenCodeHub Docs', | |
10 | - social: [ | |
11 | - { icon: 'github', label: 'GitHub', href: 'https://github.com/swadhinbiswas/OpencodeHub' }, | |
12 | - ], | |
13 | - sidebar: [ | |
14 | - { | |
15 | - label: 'Getting Started', | |
16 | - items: [ | |
17 | - { label: 'Installation', slug: 'getting-started/installation' }, | |
18 | - { label: 'Quick Start', slug: 'getting-started/quick-start' }, | |
19 | - { label: 'First Repository', slug: 'getting-started/first-repository' }, | |
20 | - ], | |
21 | - }, | |
22 | - { | |
23 | - label: 'Guides', | |
24 | - items: [ | |
25 | - { label: 'Team Workflows', slug: 'guides/team-workflows' }, | |
26 | - { label: 'Branch Protection', slug: 'guides/branch-protection' }, | |
27 | - { label: 'Webhooks', slug: 'guides/webhooks' }, | |
28 | - { label: 'Storage Adapters', slug: 'guides/storage-adapters' }, | |
29 | - ], | |
30 | - }, | |
31 | - { | |
32 | - label: 'Features', | |
33 | - items: [ | |
34 | - { label: 'Stacked PRs', slug: 'features/stacked-prs' }, | |
35 | - { label: 'AI Review', slug: 'features/ai-review' }, | |
36 | - { label: 'Merge Queue', slug: 'features/merge-queue' }, | |
37 | - ], | |
38 | - }, | |
39 | - { | |
40 | - label: 'Tutorials', | |
41 | - autogenerate: { directory: 'tutorials' }, | |
42 | - }, | |
43 | - { | |
44 | - label: 'Administration', | |
45 | - autogenerate: { directory: 'administration' }, | |
46 | - }, | |
47 | - { | |
48 | - label: 'API Reference', | |
49 | - autogenerate: { directory: 'api' }, | |
50 | - }, | |
51 | - { | |
52 | - label: 'Development', | |
53 | - autogenerate: { directory: 'development' }, | |
54 | - }, | |
55 | - { | |
56 | - label: 'Reference', | |
57 | - autogenerate: { directory: 'reference' }, | |
58 | - }, | |
59 | - ], | |
60 | - }), | |
61 | - ], | |
7 | + integrations: [ | |
8 | + starlight({ | |
9 | + title: "OpenCodeHub Docs", | |
10 | + logo: { | |
11 | + light: "../../public/logo-light.png", | |
12 | + dark: "../../public/logo-dark.png", | |
13 | + replacesTitle: false, | |
14 | + }, | |
15 | + social: [ | |
16 | + { | |
17 | + icon: "github", | |
18 | + label: "GitHub", | |
19 | + href: "https://github.com/swadhinbiswas/OpencodeHub", | |
20 | + }, | |
21 | + ], | |
22 | + sidebar: [ | |
23 | + { | |
24 | + label: "Getting Started", | |
25 | + items: [ | |
26 | + { label: "Installation", slug: "getting-started/installation" }, | |
27 | + { label: "Quick Start", slug: "getting-started/quick-start" }, | |
28 | + { | |
29 | + label: "First Repository", | |
30 | + slug: "getting-started/first-repository", | |
31 | + }, | |
32 | + ], | |
33 | + }, | |
34 | + { | |
35 | + label: "Guides", | |
36 | + items: [ | |
37 | + { label: "Team Workflows", slug: "guides/team-workflows" }, | |
38 | + { label: "Branch Protection", slug: "guides/branch-protection" }, | |
39 | + { label: "Webhooks", slug: "guides/webhooks" }, | |
40 | + { label: "Storage Adapters", slug: "guides/storage-adapters" }, | |
41 | + ], | |
42 | + }, | |
43 | + { | |
44 | + label: "CLI", | |
45 | + items: [ | |
46 | + { label: "CLI Overview", slug: "reference/cli-overview" }, | |
47 | + { label: "Auth & Config", slug: "reference/cli-auth-config" }, | |
48 | + { label: "Core Commands", slug: "reference/cli-core-commands" }, | |
49 | + { label: "Stack Workflows", slug: "reference/cli-stack-workflows" }, | |
50 | + { label: "Merge Queue", slug: "reference/cli-merge-queue" }, | |
51 | + { | |
52 | + label: "Automation & Insights", | |
53 | + slug: "reference/cli-automation-insights", | |
54 | + }, | |
55 | + { label: "CLI Command Reference", slug: "reference/cli-commands" }, | |
56 | + ], | |
57 | + }, | |
58 | + { | |
59 | + label: "Features", | |
60 | + items: [ | |
61 | + { label: "Stacked PRs", slug: "features/stacked-prs" }, | |
62 | + { label: "AI Review", slug: "features/ai-review" }, | |
63 | + { label: "Merge Queue", slug: "features/merge-queue" }, | |
64 | + { label: "Automation Rules", slug: "features/automations" }, | |
65 | + { label: "PR Inbox", slug: "features/inbox" }, | |
66 | + { label: "Developer Metrics", slug: "features/developer-metrics" }, | |
67 | + { label: "Notifications", slug: "features/notifications" }, | |
68 | + { label: "CI/CD Actions", slug: "features/ci-actions" }, | |
69 | + { label: "CLI Workflows", slug: "features/cli" }, | |
70 | + ], | |
71 | + }, | |
72 | + { | |
73 | + label: "Tutorials", | |
74 | + autogenerate: { directory: "tutorials" }, | |
75 | + }, | |
76 | + { | |
77 | + label: "Deployment", | |
78 | + items: [ | |
79 | + { label: "Docker", slug: "administration/deploy-docker" }, | |
80 | + { label: "Podman", slug: "administration/deploy-podman" }, | |
81 | + { label: "Nginx Proxy", slug: "administration/deploy-nginx" }, | |
82 | + { label: "cPanel", slug: "administration/deploy-cpanel" }, | |
83 | + { label: "CyberPanel", slug: "administration/deploy-cyberpanel" }, | |
84 | + { label: "Cloudflare", slug: "administration/deploy-cloudflare" }, | |
85 | + ], | |
86 | + }, | |
87 | + { | |
88 | + label: "Administration", | |
89 | + items: [ | |
90 | + { label: "Production Guide", slug: "administration/deployment" }, | |
91 | + { label: "Configuration", slug: "administration/configuration" }, | |
92 | + { label: "Monitoring", slug: "administration/monitoring" }, | |
93 | + { label: "Security", slug: "administration/security" }, | |
94 | + ], | |
95 | + }, | |
96 | + { | |
97 | + label: "API Reference", | |
98 | + autogenerate: { directory: "api" }, | |
99 | + }, | |
100 | + { | |
101 | + label: "Development", | |
102 | + autogenerate: { directory: "development" }, | |
103 | + }, | |
104 | + { | |
105 | + label: "Reference", | |
106 | + autogenerate: { directory: "reference" }, | |
107 | + }, | |
108 | + ], | |
109 | + }), | |
110 | + ], | |
62 | 111 | }); |
63 | 112 | |
▼docs-site/public/favicon.png
+0-0
Binary file not shown.
▼docs-site/src/content/docs/administration/deploy-cloudflare.md
+311-0
| @@ -0,0 +1,311 @@ | ||
1 | +--- | |
2 | +title: "Deploy with Cloudflare" | |
3 | +description: "Deploy OpenCodeHub using Cloudflare services (Tunnel, R2, Pages)" | |
4 | +--- | |
5 | + | |
6 | +Cloudflare provides several services that integrate well with OpenCodeHub. This guide covers using Cloudflare Tunnel for secure access and R2 for storage. | |
7 | + | |
8 | +## Cloudflare Services Overview | |
9 | + | |
10 | +| Service | Use Case | | |
11 | +| --------------------- | ------------------------------------------------- | | |
12 | +| **Cloudflare Tunnel** | Securely expose your server without opening ports | | |
13 | +| **Cloudflare R2** | S3-compatible object storage | | |
14 | +| **Cloudflare DNS** | Fast DNS with DDoS protection | | |
15 | +| **Cloudflare Pages** | Deploy docs-site (static) | | |
16 | + | |
17 | +--- | |
18 | + | |
19 | +## Part 1: Cloudflare Tunnel | |
20 | + | |
21 | +Cloudflare Tunnel (formerly Argo Tunnel) creates an encrypted tunnel from your server to Cloudflare's edge, eliminating the need to open ports. | |
22 | + | |
23 | +### Prerequisites | |
24 | + | |
25 | +- OpenCodeHub running on `localhost:3000` | |
26 | +- Cloudflare account with a domain | |
27 | +- `cloudflared` CLI installed | |
28 | + | |
29 | +### Install cloudflared | |
30 | + | |
31 | +```bash | |
32 | +# Debian/Ubuntu | |
33 | +curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb | |
34 | +sudo dpkg -i cloudflared.deb | |
35 | + | |
36 | +# Or via package manager | |
37 | +curl -L https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg | |
38 | +echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list | |
39 | +sudo apt update && sudo apt install cloudflared | |
40 | +``` | |
41 | + | |
42 | +### Authenticate | |
43 | + | |
44 | +```bash | |
45 | +cloudflared tunnel login | |
46 | +# This opens a browser to authenticate with your Cloudflare account | |
47 | +``` | |
48 | + | |
49 | +### Create Tunnel | |
50 | + | |
51 | +```bash | |
52 | +# Create tunnel | |
53 | +cloudflared tunnel create opencodehub | |
54 | + | |
55 | +# Note the tunnel ID and credentials file path | |
56 | +# Example: ~/.cloudflared/<tunnel-id>.json | |
57 | +``` | |
58 | + | |
59 | +### Configure Tunnel | |
60 | + | |
61 | +Create `~/.cloudflared/config.yml`: | |
62 | + | |
63 | +```yaml | |
64 | +tunnel: <your-tunnel-id> | |
65 | +credentials-file: /home/user/.cloudflared/<tunnel-id>.json | |
66 | + | |
67 | +ingress: | |
68 | + # Main application | |
69 | + - hostname: git.yourdomain.com | |
70 | + service: http://localhost:3000 | |
71 | + originRequest: | |
72 | + noTLSVerify: true | |
73 | + connectTimeout: 30s | |
74 | + # Important for git operations | |
75 | + disableChunkedEncoding: true | |
76 | + | |
77 | + # Catch-all (required) | |
78 | + - service: http_status:404 | |
79 | +``` | |
80 | + | |
81 | +### Route DNS | |
82 | + | |
83 | +```bash | |
84 | +cloudflared tunnel route dns opencodehub git.yourdomain.com | |
85 | +``` | |
86 | + | |
87 | +### Run Tunnel | |
88 | + | |
89 | +```bash | |
90 | +# Test | |
91 | +cloudflared tunnel run opencodehub | |
92 | + | |
93 | +# Install as service | |
94 | +sudo cloudflared service install | |
95 | +sudo systemctl start cloudflared | |
96 | +sudo systemctl enable cloudflared | |
97 | +``` | |
98 | + | |
99 | +### Verify | |
100 | + | |
101 | +Visit `https://git.yourdomain.com` - it should load OpenCodeHub! | |
102 | + | |
103 | +--- | |
104 | + | |
105 | +## Part 2: Cloudflare R2 Storage | |
106 | + | |
107 | +R2 is S3-compatible object storage with no egress fees. | |
108 | + | |
109 | +### Create R2 Bucket | |
110 | + | |
111 | +1. Go to Cloudflare Dashboard → **R2** | |
112 | +2. Click **Create bucket** | |
113 | +3. Name: `opencodehub-repos` | |
114 | +4. Location: Choose nearest region | |
115 | + | |
116 | +### Create API Tokens | |
117 | + | |
118 | +1. Go to **R2 → Manage R2 API Tokens** | |
119 | +2. Click **Create API Token** | |
120 | +3. Permissions: **Object Read & Write** | |
121 | +4. Specify bucket: `opencodehub-repos` | |
122 | +5. Copy the Access Key ID and Secret Access Key | |
123 | + | |
124 | +### Configure OpenCodeHub | |
125 | + | |
126 | +Add to your `.env`: | |
127 | + | |
128 | +```bash | |
129 | +STORAGE_TYPE=s3 | |
130 | +STORAGE_BUCKET=opencodehub-repos | |
131 | +STORAGE_REGION=auto | |
132 | +STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com | |
133 | +S3_ACCESS_KEY=<your-r2-access-key-id> | |
134 | +S3_SECRET_KEY=<your-r2-secret-access-key> | |
135 | +``` | |
136 | + | |
137 | +Find your account ID in Cloudflare Dashboard → R2 → Overview. | |
138 | + | |
139 | +### Performance Note | |
140 | + | |
141 | +:::caution[R2 Latency] | |
142 | +R2 has the same latency characteristics as other object storage. See [Storage Adapters - Performance Considerations](/guides/storage-adapters/#️-performance-considerations) for details and recommendations. | |
143 | +::: | |
144 | + | |
145 | +--- | |
146 | + | |
147 | +## Part 3: Cloudflare DNS | |
148 | + | |
149 | +### Recommended DNS Settings | |
150 | + | |
151 | +| Type | Name | Content | Proxy | | |
152 | +| ----- | ---- | ------------------------------ | ---------------- | | |
153 | +| CNAME | git | `<tunnel-id>.cfargotunnel.com` | Proxied (orange) | | |
154 | + | |
155 | +If using Tunnel, the CNAME is created automatically. | |
156 | + | |
157 | +### Security Settings | |
158 | + | |
159 | +1. Go to **SSL/TLS → Overview** | |
160 | +2. Set mode to **Full (strict)** | |
161 | + | |
162 | +3. Go to **Security → WAF** | |
163 | +4. Enable managed rules | |
164 | + | |
165 | +--- | |
166 | + | |
167 | +## Part 4: Deploy Docs to Cloudflare Pages | |
168 | + | |
169 | +The documentation site can be deployed to Cloudflare Pages for free. | |
170 | + | |
171 | +### Connect Repository | |
172 | + | |
173 | +1. Go to **Workers & Pages → Create application** | |
174 | +2. Select **Pages → Connect to Git** | |
175 | +3. Choose your OpenCodeHub repository | |
176 | + | |
177 | +### Build Settings | |
178 | + | |
179 | +| Setting | Value | | |
180 | +| ---------------------- | ---------------------------------------------- | | |
181 | +| Build command | `cd docs-site && npm install && npm run build` | | |
182 | +| Build output directory | `docs-site/dist` | | |
183 | +| Root directory | `/` | | |
184 | + | |
185 | +### Environment Variables | |
186 | + | |
187 | +| Variable | Value | | |
188 | +| -------------- | ----- | | |
189 | +| `NODE_VERSION` | `20` | | |
190 | + | |
191 | +### Custom Domain | |
192 | + | |
193 | +1. Go to Pages project → **Custom domains** | |
194 | +2. Add `docs.yourdomain.com` | |
195 | +3. Cloudflare will configure DNS automatically | |
196 | + | |
197 | +--- | |
198 | + | |
199 | +## Complete Architecture | |
200 | + | |
201 | +``` | |
202 | +┌─────────────────────────────────────────────────────────┐ | |
203 | +│ Cloudflare Edge │ | |
204 | +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ | |
205 | +│ │ DNS │ │ WAF │ │ CDN │ │ | |
206 | +│ └─────────────┘ └─────────────┘ └─────────────┘ │ | |
207 | +│ │ │ │ | |
208 | +│ ▼ ▼ │ | |
209 | +│ ┌─────────────────┐ ┌─────────────────┐ │ | |
210 | +│ │ Cloudflare │ │ Cloudflare │ │ | |
211 | +│ │ Tunnel │ │ Pages │ │ | |
212 | +│ │ (git.domain) │ │ (docs.domain) │ │ | |
213 | +│ └────────┬────────┘ └─────────────────┘ │ | |
214 | +└───────────┼─────────────────────────────────────────────┘ | |
215 | + │ | |
216 | + ▼ (encrypted tunnel) | |
217 | + ┌───────────────────┐ | |
218 | + │ Your Server │ | |
219 | + │ ┌─────────────┐ │ | |
220 | + │ │ OpenCodeHub │ │ | |
221 | + │ │ :3000 │ │ | |
222 | + │ └──────┬──────┘ │ | |
223 | + │ │ │ | |
224 | + │ ▼ │ | |
225 | + │ ┌─────────────┐ │ | |
226 | + │ │ PostgreSQL │ │ | |
227 | + │ └─────────────┘ │ | |
228 | + └───────────────────┘ | |
229 | + │ | |
230 | + ▼ (S3 API) | |
231 | + ┌───────────────────┐ | |
232 | + │ Cloudflare R2 │ | |
233 | + │ (repo storage) │ | |
234 | + └───────────────────┘ | |
235 | +``` | |
236 | + | |
237 | +--- | |
238 | + | |
239 | +## Benefits of Cloudflare Setup | |
240 | + | |
241 | +| Benefit | Description | | |
242 | +| ------------------- | ---------------------------- | | |
243 | +| **No open ports** | Tunnel handles all ingress | | |
244 | +| **DDoS protection** | Free, always-on | | |
245 | +| **Free SSL** | Automatic via Cloudflare | | |
246 | +| **No egress fees** | R2 has zero egress costs | | |
247 | +| **Global CDN** | Static assets cached at edge | | |
248 | +| **Analytics** | Built-in traffic analytics | | |
249 | + | |
250 | +--- | |
251 | + | |
252 | +## Troubleshooting | |
253 | + | |
254 | +### Tunnel Connection Drops | |
255 | + | |
256 | +```bash | |
257 | +# Check tunnel status | |
258 | +cloudflared tunnel info opencodehub | |
259 | + | |
260 | +# View logs | |
261 | +sudo journalctl -u cloudflared -f | |
262 | +``` | |
263 | + | |
264 | +### 502 Bad Gateway | |
265 | + | |
266 | +1. Verify OpenCodeHub is running on port 3000 | |
267 | +2. Check tunnel config points to correct port | |
268 | +3. Verify `localhost:3000` is accessible locally | |
269 | + | |
270 | +### R2 Access Denied | |
271 | + | |
272 | +1. Verify API token has correct bucket permissions | |
273 | +2. Check Access Key ID and Secret are correct | |
274 | +3. Verify endpoint URL format | |
275 | + | |
276 | +--- | |
277 | + | |
278 | +## Environment Example | |
279 | + | |
280 | +Complete `.env` for Cloudflare setup: | |
281 | + | |
282 | +```bash | |
283 | +NODE_ENV=production | |
284 | +PORT=3000 | |
285 | +SITE_URL=https://git.yourdomain.com | |
286 | + | |
287 | +# Database (local PostgreSQL) | |
288 | +DATABASE_DRIVER=postgres | |
289 | +DATABASE_URL=postgresql://opencodehub:password@localhost:5432/opencodehub | |
290 | + | |
291 | +# Security | |
292 | +JWT_SECRET=<64-char-hex> | |
293 | +SESSION_SECRET=<64-char-hex> | |
294 | +INTERNAL_HOOK_SECRET=<64-char-hex> | |
295 | + | |
296 | +# Cloudflare R2 Storage | |
297 | +STORAGE_TYPE=s3 | |
298 | +STORAGE_BUCKET=opencodehub-repos | |
299 | +STORAGE_REGION=auto | |
300 | +STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com | |
301 | +S3_ACCESS_KEY=<r2-access-key> | |
302 | +S3_SECRET_KEY=<r2-secret-key> | |
303 | +``` | |
304 | + | |
305 | +--- | |
306 | + | |
307 | +## Next Steps | |
308 | + | |
309 | +- [Configure monitoring](/administration/monitoring/) | |
310 | +- [Set up backups](/administration/deploy-docker/#backup--restore) | |
311 | +- [Enable AI code review](/features/ai-review/) | |
0 | 312 | |
▼docs-site/src/content/docs/administration/deploy-cpanel.md
+221-0
| @@ -0,0 +1,221 @@ | ||
1 | +--- | |
2 | +title: "Deploy with cPanel" | |
3 | +description: "Deploy OpenCodeHub on shared hosting using cPanel's Node.js Application feature" | |
4 | +--- | |
5 | + | |
6 | +cPanel supports Node.js applications through its Application Manager. This guide covers deploying OpenCodeHub on cPanel-based shared hosting. | |
7 | + | |
8 | +:::caution[Limitations] | |
9 | +Shared hosting has significant limitations for Git servers: | |
10 | + | |
11 | +- No Docker support | |
12 | +- Limited process control | |
13 | +- Shared resources with other users | |
14 | +- May not support persistent processes | |
15 | + | |
16 | +For production use, consider a VPS or dedicated server instead. | |
17 | +::: | |
18 | + | |
19 | +## Prerequisites | |
20 | + | |
21 | +- cPanel hosting with Node.js support (v18+) | |
22 | +- PostgreSQL database addon (or external database) | |
23 | +- SSH access recommended | |
24 | +- Domain configured in cPanel | |
25 | + | |
26 | +--- | |
27 | + | |
28 | +## Step 1: Create PostgreSQL Database | |
29 | + | |
30 | +1. Login to cPanel | |
31 | +2. Go to **Databases → PostgreSQL Databases** | |
32 | +3. Create a new database: `opencodehub` | |
33 | +4. Create a new user with a strong password | |
34 | +5. Add user to database with ALL PRIVILEGES | |
35 | +6. Note the connection string: | |
36 | + ``` | |
37 | + postgresql://username:password@localhost:5432/opencodehub | |
38 | + ``` | |
39 | + | |
40 | +--- | |
41 | + | |
42 | +## Step 2: Upload Application Files | |
43 | + | |
44 | +### Option A: Git Clone (if SSH available) | |
45 | + | |
46 | +```bash | |
47 | +ssh [email protected] | |
48 | +cd ~/ | |
49 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git opencodehub | |
50 | +``` | |
51 | + | |
52 | +### Option B: File Manager Upload | |
53 | + | |
54 | +1. Download the repository as ZIP from GitHub | |
55 | +2. Go to cPanel → **File Manager** | |
56 | +3. Navigate to your home directory | |
57 | +4. Upload and extract the ZIP file | |
58 | +5. Rename to `opencodehub` | |
59 | + | |
60 | +--- | |
61 | + | |
62 | +## Step 3: Create Node.js Application | |
63 | + | |
64 | +1. Go to cPanel → **Setup Node.js App** | |
65 | +2. Click **Create Application** | |
66 | +3. Configure: | |
67 | + | |
68 | +| Setting | Value | | |
69 | +| ------------------------ | ----------------------- | | |
70 | +| Node.js version | 18.x or 20.x | | |
71 | +| Application mode | Production | | |
72 | +| Application root | `opencodehub` | | |
73 | +| Application URL | `git.yourdomain.com` | | |
74 | +| Application startup file | `dist/server/entry.mjs` | | |
75 | + | |
76 | +4. Click **Create** | |
77 | + | |
78 | +--- | |
79 | + | |
80 | +## Step 4: Configure Environment Variables | |
81 | + | |
82 | +In the Node.js Application page: | |
83 | + | |
84 | +1. Scroll to **Environment Variables** | |
85 | +2. Add the following: | |
86 | + | |
87 | +```bash | |
88 | +NODE_ENV=production | |
89 | +PORT=3000 | |
90 | +SITE_URL=https://git.yourdomain.com | |
91 | + | |
92 | +# Database | |
93 | +DATABASE_DRIVER=postgres | |
94 | +DATABASE_URL=postgresql://cpuser_dbuser:password@localhost:5432/cpuser_opencodehub | |
95 | + | |
96 | +# Security (generate with: openssl rand -hex 32) | |
97 | +JWT_SECRET=<64-char-hex> | |
98 | +SESSION_SECRET=<64-char-hex> | |
99 | +INTERNAL_HOOK_SECRET=<64-char-hex> | |
100 | + | |
101 | +# Storage | |
102 | +STORAGE_TYPE=local | |
103 | +STORAGE_PATH=./data/repos | |
104 | +``` | |
105 | + | |
106 | +3. Click **Save** | |
107 | + | |
108 | +--- | |
109 | + | |
110 | +## Step 5: Install Dependencies and Build | |
111 | + | |
112 | +1. In the Node.js app page, click **Run NPM Install** | |
113 | +2. Connect via SSH and run: | |
114 | + | |
115 | +```bash | |
116 | +cd ~/opencodehub | |
117 | + | |
118 | +# Enter virtual environment | |
119 | +source /home/username/nodevenv/opencodehub/18/bin/activate | |
120 | + | |
121 | +# Install dependencies | |
122 | +npm install | |
123 | + | |
124 | +# Build the application | |
125 | +npm run build | |
126 | + | |
127 | +# Initialize database | |
128 | +npm run db:push | |
129 | + | |
130 | +# Create admin user (if script exists) | |
131 | +npm run scripts/seed-admin.ts | |
132 | +``` | |
133 | + | |
134 | +--- | |
135 | + | |
136 | +## Step 6: Start Application | |
137 | + | |
138 | +1. In cPanel Node.js app manager, click **Start App** | |
139 | +2. Verify it's running by visiting your domain | |
140 | + | |
141 | +--- | |
142 | + | |
143 | +## Alternative: Using PM2 | |
144 | + | |
145 | +If your host supports PM2: | |
146 | + | |
147 | +```bash | |
148 | +# Install PM2 globally | |
149 | +npm install -g pm2 | |
150 | + | |
151 | +# Start with PM2 | |
152 | +pm2 start dist/server/entry.mjs --name opencodehub | |
153 | + | |
154 | +# Save process list | |
155 | +pm2 save | |
156 | + | |
157 | +# Setup startup script | |
158 | +pm2 startup | |
159 | +``` | |
160 | + | |
161 | +--- | |
162 | + | |
163 | +## Handling Git Operations | |
164 | + | |
165 | +:::warning[Important] | |
166 | +cPanel's Node.js apps may not handle long-running Git operations well. For push/pull with large repos, you may experience timeouts. | |
167 | +::: | |
168 | + | |
169 | +### Workarounds | |
170 | + | |
171 | +1. **Increase PHP/Node timeout** (if available in hosting settings) | |
172 | +2. **Use external storage** (S3) to reduce local I/O | |
173 | +3. **Consider VPS** for better control | |
174 | + | |
175 | +--- | |
176 | + | |
177 | +## Troubleshooting | |
178 | + | |
179 | +### Application Won't Start | |
180 | + | |
181 | +1. Check error logs in cPanel → **Errors** | |
182 | +2. Verify Node.js version compatibility | |
183 | +3. Check if all dependencies installed | |
184 | +4. Verify startup file path is correct | |
185 | + | |
186 | +### Database Connection Errors | |
187 | + | |
188 | +```bash | |
189 | +# Test connection via SSH | |
190 | +psql "postgresql://user:pass@localhost/dbname" | |
191 | +``` | |
192 | + | |
193 | +### 503 Service Unavailable | |
194 | + | |
195 | +- Application may have crashed | |
196 | +- Check logs and restart via cPanel | |
197 | + | |
198 | +--- | |
199 | + | |
200 | +## Limitations Summary | |
201 | + | |
202 | +| Feature | cPanel Support | | |
203 | +| --------------- | ----------------------------- | | |
204 | +| Web UI | ✅ Works | | |
205 | +| Git clone/push | ⚠️ May timeout on large repos | | |
206 | +| WebSockets | ❌ Usually not supported | | |
207 | +| Background jobs | ❌ Limited | | |
208 | +| Custom domains | ✅ Works | | |
209 | +| SSL | ✅ Via AutoSSL | | |
210 | + | |
211 | +--- | |
212 | + | |
213 | +## Recommended Alternative | |
214 | + | |
215 | +For a Git server, we strongly recommend: | |
216 | + | |
217 | +1. **VPS** (DigitalOcean, Linode, Vultr) - Full control | |
218 | +2. **Docker** on any cloud provider - Easy deployment | |
219 | +3. **Vercel/Cloudflare** - For serverless approach | |
220 | + | |
221 | +See [Docker Deployment Guide](/administration/deploy-docker/) for production setups. | |
0 | 222 | |
▼docs-site/src/content/docs/administration/deploy-cyberpanel.md
+330-0
| @@ -0,0 +1,330 @@ | ||
1 | +--- | |
2 | +title: "Deploy with CyberPanel" | |
3 | +description: "Deploy OpenCodeHub using CyberPanel with OpenLiteSpeed web server" | |
4 | +--- | |
5 | + | |
6 | +CyberPanel is a hosting control panel that uses OpenLiteSpeed (OLS) as its web server. This guide covers deploying OpenCodeHub on a CyberPanel server. | |
7 | + | |
8 | +## Prerequisites | |
9 | + | |
10 | +- CyberPanel installed on a VPS | |
11 | +- Root SSH access | |
12 | +- Domain pointed to your server | |
13 | +- PostgreSQL or MySQL available | |
14 | + | |
15 | +--- | |
16 | + | |
17 | +## Step 1: Create Website in CyberPanel | |
18 | + | |
19 | +1. Login to CyberPanel admin panel | |
20 | +2. Go to **Websites → Create Website** | |
21 | +3. Configure: | |
22 | + - Domain: `git.yourdomain.com` | |
23 | + - Email: your email | |
24 | + - PHP: None (we'll use Node.js) | |
25 | + - SSL: Issue SSL via Let's Encrypt | |
26 | + | |
27 | +--- | |
28 | + | |
29 | +## Step 2: Install Node.js | |
30 | + | |
31 | +CyberPanel doesn't include Node.js by default. Install via SSH: | |
32 | + | |
33 | +```bash | |
34 | +# Connect via SSH as root | |
35 | +ssh root@your-server-ip | |
36 | + | |
37 | +# Install Node.js 20.x | |
38 | +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - | |
39 | +apt install -y nodejs | |
40 | + | |
41 | +# Verify | |
42 | +node --version # Should be 20.x | |
43 | +npm --version | |
44 | +``` | |
45 | + | |
46 | +--- | |
47 | + | |
48 | +## Step 3: Install PostgreSQL | |
49 | + | |
50 | +```bash | |
51 | +# Install PostgreSQL | |
52 | +apt install -y postgresql postgresql-contrib | |
53 | + | |
54 | +# Create database and user | |
55 | +sudo -u postgres psql << EOF | |
56 | +CREATE DATABASE opencodehub; | |
57 | +CREATE USER opencodehub WITH ENCRYPTED PASSWORD 'your-secure-password'; | |
58 | +GRANT ALL PRIVILEGES ON DATABASE opencodehub TO opencodehub; | |
59 | +\c opencodehub | |
60 | +GRANT ALL ON SCHEMA public TO opencodehub; | |
61 | +EOF | |
62 | +``` | |
63 | + | |
64 | +--- | |
65 | + | |
66 | +## Step 4: Clone and Setup Application | |
67 | + | |
68 | +```bash | |
69 | +# Create app directory | |
70 | +mkdir -p /home/git.yourdomain.com/app | |
71 | +cd /home/git.yourdomain.com/app | |
72 | + | |
73 | +# Clone repository | |
74 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git . | |
75 | + | |
76 | +# Install dependencies | |
77 | +npm install | |
78 | + | |
79 | +# Create environment file | |
80 | +cat > .env << 'EOF' | |
81 | +NODE_ENV=production | |
82 | +PORT=3000 | |
83 | +SITE_URL=https://git.yourdomain.com | |
84 | + | |
85 | +# Database | |
86 | +DATABASE_DRIVER=postgres | |
87 | +DATABASE_URL=postgresql://opencodehub:your-secure-password@localhost:5432/opencodehub | |
88 | + | |
89 | +# Security (generate with: openssl rand -hex 32) | |
90 | +JWT_SECRET=your-64-char-jwt-secret | |
91 | +SESSION_SECRET=your-64-char-session-secret | |
92 | +INTERNAL_HOOK_SECRET=your-64-char-hook-secret | |
93 | + | |
94 | +# Storage | |
95 | +STORAGE_TYPE=local | |
96 | +STORAGE_PATH=./data/repos | |
97 | +EOF | |
98 | + | |
99 | +# Build | |
100 | +npm run build | |
101 | + | |
102 | +# Initialize database | |
103 | +npm run db:push | |
104 | + | |
105 | +# Create admin user | |
106 | +npm run scripts/seed-admin.ts | |
107 | +``` | |
108 | + | |
109 | +--- | |
110 | + | |
111 | +## Step 5: Configure OpenLiteSpeed | |
112 | + | |
113 | +CyberPanel uses OpenLiteSpeed. We need to set up reverse proxy. | |
114 | + | |
115 | +### Create Proxy Configuration | |
116 | + | |
117 | +1. SSH into server | |
118 | +2. Edit the vhost config: | |
119 | + | |
120 | +```bash | |
121 | +nano /usr/local/lsws/conf/vhosts/git.yourdomain.com/vhconf.conf | |
122 | +``` | |
123 | + | |
124 | +3. Add or modify the config: | |
125 | + | |
126 | +```apache | |
127 | +docRoot $VH_ROOT/public_html | |
128 | +enableGzip 1 | |
129 | + | |
130 | +context / { | |
131 | + type proxy | |
132 | + handler localhost:3000 | |
133 | + addDefaultCharset off | |
134 | +} | |
135 | + | |
136 | +rewrite { | |
137 | + enable 1 | |
138 | + autoLoadHtaccess 1 | |
139 | +} | |
140 | +``` | |
141 | + | |
142 | +### Alternative: Via CyberPanel UI | |
143 | + | |
144 | +1. Go to **Websites → List Websites** | |
145 | +2. Click on your website → **vHost Conf** | |
146 | +3. Add the proxy context as shown above | |
147 | +4. Click **Save** | |
148 | + | |
149 | +### Restart LiteSpeed | |
150 | + | |
151 | +```bash | |
152 | +systemctl restart lsws | |
153 | +``` | |
154 | + | |
155 | +--- | |
156 | + | |
157 | +## Step 6: Setup Process Manager (PM2) | |
158 | + | |
159 | +```bash | |
160 | +# Install PM2 | |
161 | +npm install -g pm2 | |
162 | + | |
163 | +# Start OpenCodeHub | |
164 | +cd /home/git.yourdomain.com/app | |
165 | +pm2 start dist/server/entry.mjs --name opencodehub | |
166 | + | |
167 | +# Save process list | |
168 | +pm2 save | |
169 | + | |
170 | +# Setup startup script | |
171 | +pm2 startup systemd | |
172 | +# Run the command it outputs | |
173 | +``` | |
174 | + | |
175 | +--- | |
176 | + | |
177 | +## Step 7: Configure Firewall | |
178 | + | |
179 | +CyberPanel uses firewalld: | |
180 | + | |
181 | +```bash | |
182 | +# Allow necessary ports (if not already open) | |
183 | +firewall-cmd --permanent --add-port=3000/tcp | |
184 | +firewall-cmd --reload | |
185 | +``` | |
186 | + | |
187 | +:::note | |
188 | +Port 3000 only needs to be open if accessing directly. With the reverse proxy, only 80/443 need to be open. | |
189 | +::: | |
190 | + | |
191 | +--- | |
192 | + | |
193 | +## SSL Configuration | |
194 | + | |
195 | +CyberPanel can automatically issue SSL via Let's Encrypt: | |
196 | + | |
197 | +1. Go to **SSL → Manage SSL** | |
198 | +2. Select your website | |
199 | +3. Click **Issue SSL** | |
200 | + | |
201 | +The SSL will be automatically configured for OpenLiteSpeed. | |
202 | + | |
203 | +--- | |
204 | + | |
205 | +## OpenLiteSpeed Optimizations | |
206 | + | |
207 | +### Increase Timeouts for Git Operations | |
208 | + | |
209 | +Edit `/usr/local/lsws/conf/vhosts/git.yourdomain.com/vhconf.conf`: | |
210 | + | |
211 | +```apache | |
212 | +context / { | |
213 | + type proxy | |
214 | + handler localhost:3000 | |
215 | + addDefaultCharset off | |
216 | + | |
217 | + # Extended timeouts for git operations | |
218 | + extraparam { | |
219 | + ProxyTimeout 600 | |
220 | + } | |
221 | +} | |
222 | +``` | |
223 | + | |
224 | +### Enable WebSocket Support | |
225 | + | |
226 | +```apache | |
227 | +context /ws { | |
228 | + type proxy | |
229 | + handler localhost:3000 | |
230 | + addDefaultCharset off | |
231 | + | |
232 | + extraparam { | |
233 | + WebSocket 1 | |
234 | + } | |
235 | +} | |
236 | +``` | |
237 | + | |
238 | +--- | |
239 | + | |
240 | +## Monitoring with CyberPanel | |
241 | + | |
242 | +### View Logs | |
243 | + | |
244 | +1. Go to **Logs → Access Logs** or **Error Logs** | |
245 | +2. Select your website | |
246 | + | |
247 | +### PM2 Monitoring | |
248 | + | |
249 | +```bash | |
250 | +# View status | |
251 | +pm2 status | |
252 | + | |
253 | +# View logs | |
254 | +pm2 logs opencodehub | |
255 | + | |
256 | +# Monitor resources | |
257 | +pm2 monit | |
258 | +``` | |
259 | + | |
260 | +--- | |
261 | + | |
262 | +## Troubleshooting | |
263 | + | |
264 | +### 503 Bad Gateway | |
265 | + | |
266 | +1. Check if Node.js app is running: `pm2 status` | |
267 | +2. Verify port 3000 is listening: `netstat -tlnp | grep 3000` | |
268 | +3. Check LiteSpeed error logs: `/usr/local/lsws/logs/error.log` | |
269 | + | |
270 | +### Application Crashes on Restart | |
271 | + | |
272 | +```bash | |
273 | +# Check PM2 logs | |
274 | +pm2 logs opencodehub --lines 100 | |
275 | + | |
276 | +# Restart application | |
277 | +pm2 restart opencodehub | |
278 | +``` | |
279 | + | |
280 | +### Database Connection Issues | |
281 | + | |
282 | +```bash | |
283 | +# Test PostgreSQL connection | |
284 | +psql -U opencodehub -h localhost -d opencodehub | |
285 | +``` | |
286 | + | |
287 | +--- | |
288 | + | |
289 | +## Updating OpenCodeHub | |
290 | + | |
291 | +```bash | |
292 | +cd /home/git.yourdomain.com/app | |
293 | + | |
294 | +# Pull latest changes | |
295 | +git pull origin main | |
296 | + | |
297 | +# Install dependencies | |
298 | +npm install | |
299 | + | |
300 | +# Rebuild | |
301 | +npm run build | |
302 | + | |
303 | +# Run migrations | |
304 | +npm run db:push | |
305 | + | |
306 | +# Restart | |
307 | +pm2 restart opencodehub | |
308 | +``` | |
309 | + | |
310 | +--- | |
311 | + | |
312 | +## Comparison: OpenLiteSpeed vs Nginx | |
313 | + | |
314 | +| Feature | OpenLiteSpeed | Nginx | | |
315 | +|---------|---------------|-------| | |
316 | +| Performance | Excellent | Excellent | | |
317 | +| .htaccess support | ✅ | ❌ | | |
318 | +| WebSocket | ✅ | ✅ | | |
319 | +| Configuration | GUI + files | Files only | | |
320 | +| Resource usage | Low | Very low | | |
321 | + | |
322 | +Both work well for OpenCodeHub. | |
323 | + | |
324 | +--- | |
325 | + | |
326 | +## Next Steps | |
327 | + | |
328 | +- [Set up monitoring](/administration/monitoring/) | |
329 | +- [Configure backups](/administration/deploy-docker/#backup--restore) | |
330 | +- [Enable AI code review](/features/ai-review/) | |
0 | 331 | |
▼docs-site/src/content/docs/administration/deploy-docker.md
+303-0
| @@ -0,0 +1,303 @@ | ||
1 | +--- | |
2 | +title: "Deploy with Docker" | |
3 | +description: "Complete guide to deploying OpenCodeHub using Docker and Docker Compose" | |
4 | +--- | |
5 | + | |
6 | +Docker is the recommended deployment method for OpenCodeHub. This guide covers everything from basic setup to production-ready configurations. | |
7 | + | |
8 | +## Prerequisites | |
9 | + | |
10 | +- Docker Engine 24.0+ | |
11 | +- Docker Compose v2.20+ | |
12 | +- 2GB RAM minimum (4GB recommended) | |
13 | +- 20GB disk space | |
14 | + | |
15 | +```bash | |
16 | +# Verify installation | |
17 | +docker --version | |
18 | +docker compose version | |
19 | +``` | |
20 | + | |
21 | +--- | |
22 | + | |
23 | +## Quick Start (Development) | |
24 | + | |
25 | +For testing or development, use the minimal configuration: | |
26 | + | |
27 | +```bash | |
28 | +# Clone the repository | |
29 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
30 | +cd OpenCodeHub | |
31 | + | |
32 | +# Copy environment file | |
33 | +cp .env.example .env | |
34 | + | |
35 | +# Start with Docker Compose | |
36 | +docker compose up -d | |
37 | + | |
38 | +# View logs | |
39 | +docker compose logs -f | |
40 | +``` | |
41 | + | |
42 | +Access at `http://localhost:3000` | |
43 | + | |
44 | +--- | |
45 | + | |
46 | +## Production Setup | |
47 | + | |
48 | +### 1. Create Directory Structure | |
49 | + | |
50 | +```bash | |
51 | +mkdir -p /opt/opencodehub/{data,postgres,redis} | |
52 | +cd /opt/opencodehub | |
53 | +``` | |
54 | + | |
55 | +### 2. Create docker-compose.yml | |
56 | + | |
57 | +```yaml | |
58 | +version: '3.8' | |
59 | + | |
60 | +services: | |
61 | + app: | |
62 | + image: ghcr.io/swadhinbiswas/opencodehub:latest | |
63 | + # Or build from source: | |
64 | + # build: . | |
65 | + restart: unless-stopped | |
66 | + ports: | |
67 | + - "3000:3000" | |
68 | + environment: | |
69 | + - NODE_ENV=production | |
70 | + env_file: | |
71 | + - .env | |
72 | + volumes: | |
73 | + - ./data:/app/data | |
74 | + depends_on: | |
75 | + postgres: | |
76 | + condition: service_healthy | |
77 | + redis: | |
78 | + condition: service_started | |
79 | + healthcheck: | |
80 | + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] | |
81 | + interval: 30s | |
82 | + timeout: 10s | |
83 | + retries: 3 | |
84 | + start_period: 40s | |
85 | + | |
86 | + postgres: | |
87 | + image: postgres:16-alpine | |
88 | + restart: unless-stopped | |
89 | + environment: | |
90 | + POSTGRES_DB: opencodehub | |
91 | + POSTGRES_USER: opencodehub | |
92 | + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} | |
93 | + volumes: | |
94 | + - ./postgres:/var/lib/postgresql/data | |
95 | + healthcheck: | |
96 | + test: ["CMD-SHELL", "pg_isready -U opencodehub"] | |
97 | + interval: 10s | |
98 | + timeout: 5s | |
99 | + retries: 5 | |
100 | + | |
101 | + redis: | |
102 | + image: redis:7-alpine | |
103 | + restart: unless-stopped | |
104 | + command: redis-server --requirepass ${REDIS_PASSWORD} | |
105 | + volumes: | |
106 | + - redis_data:/data | |
107 | + | |
108 | +volumes: | |
109 | + redis_data: | |
110 | +``` | |
111 | + | |
112 | +### 3. Create Production .env | |
113 | + | |
114 | +```bash | |
115 | +# Generate secure secrets | |
116 | +JWT_SECRET=$(openssl rand -hex 32) | |
117 | +SESSION_SECRET=$(openssl rand -hex 32) | |
118 | +INTERNAL_HOOK_SECRET=$(openssl rand -hex 32) | |
119 | +DATABASE_PASSWORD=$(openssl rand -hex 16) | |
120 | +REDIS_PASSWORD=$(openssl rand -hex 16) | |
121 | + | |
122 | +cat > .env << EOF | |
123 | +# Application | |
124 | +NODE_ENV=production | |
125 | +PORT=3000 | |
126 | +SITE_URL=https://git.yourcompany.com | |
127 | + | |
128 | +# Security (NEVER commit these!) | |
129 | +JWT_SECRET=${JWT_SECRET} | |
130 | +SESSION_SECRET=${SESSION_SECRET} | |
131 | +INTERNAL_HOOK_SECRET=${INTERNAL_HOOK_SECRET} | |
132 | + | |
133 | +# Database | |
134 | +DATABASE_DRIVER=postgres | |
135 | +DATABASE_URL=postgresql://opencodehub:${DATABASE_PASSWORD}@postgres:5432/opencodehub | |
136 | +DATABASE_PASSWORD=${DATABASE_PASSWORD} | |
137 | + | |
138 | +# Redis | |
139 | +REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379 | |
140 | +REDIS_PASSWORD=${REDIS_PASSWORD} | |
141 | + | |
142 | +# Storage (use local for best performance) | |
143 | +STORAGE_TYPE=local | |
144 | +STORAGE_PATH=/app/data/repos | |
145 | +EOF | |
146 | +``` | |
147 | + | |
148 | +### 4. Start Services | |
149 | + | |
150 | +```bash | |
151 | +docker compose up -d | |
152 | + | |
153 | +# Verify all services are healthy | |
154 | +docker compose ps | |
155 | + | |
156 | +# Initialize database | |
157 | +docker compose exec app bun run db:push | |
158 | + | |
159 | +# Create admin user | |
160 | +docker compose exec app bun run scripts/seed-admin.ts | |
161 | +``` | |
162 | + | |
163 | +--- | |
164 | + | |
165 | +## Using with Nginx (Recommended) | |
166 | + | |
167 | +See the [Nginx Deployment Guide](/administration/deploy-nginx/) for reverse proxy configuration. | |
168 | + | |
169 | +### Quick Nginx Setup | |
170 | + | |
171 | +```nginx | |
172 | +server { | |
173 | + listen 80; | |
174 | + server_name git.yourcompany.com; | |
175 | + return 301 https://$host$request_uri; | |
176 | +} | |
177 | + | |
178 | +server { | |
179 | + listen 443 ssl http2; | |
180 | + server_name git.yourcompany.com; | |
181 | + | |
182 | + ssl_certificate /etc/letsencrypt/live/git.yourcompany.com/fullchain.pem; | |
183 | + ssl_certificate_key /etc/letsencrypt/live/git.yourcompany.com/privkey.pem; | |
184 | + | |
185 | + client_max_body_size 500M; | |
186 | + | |
187 | + location / { | |
188 | + proxy_pass http://localhost:3000; | |
189 | + proxy_set_header Host $host; | |
190 | + proxy_set_header X-Real-IP $remote_addr; | |
191 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
192 | + proxy_set_header X-Forwarded-Proto $scheme; | |
193 | + | |
194 | + # WebSocket support | |
195 | + proxy_http_version 1.1; | |
196 | + proxy_set_header Upgrade $http_upgrade; | |
197 | + proxy_set_header Connection "upgrade"; | |
198 | + } | |
199 | +} | |
200 | +``` | |
201 | + | |
202 | +--- | |
203 | + | |
204 | +## Docker Swarm Deployment | |
205 | + | |
206 | +For high availability: | |
207 | + | |
208 | +```yaml | |
209 | +version: '3.8' | |
210 | + | |
211 | +services: | |
212 | + app: | |
213 | + image: ghcr.io/swadhinbiswas/opencodehub:latest | |
214 | + deploy: | |
215 | + replicas: 3 | |
216 | + update_config: | |
217 | + parallelism: 1 | |
218 | + delay: 10s | |
219 | + restart_policy: | |
220 | + condition: on-failure | |
221 | + # ... rest of config | |
222 | +``` | |
223 | + | |
224 | +```bash | |
225 | +docker stack deploy -c docker-compose.yml opencodehub | |
226 | +``` | |
227 | + | |
228 | +--- | |
229 | + | |
230 | +## Backup & Restore | |
231 | + | |
232 | +### Backup | |
233 | + | |
234 | +```bash | |
235 | +#!/bin/bash | |
236 | +DATE=$(date +%Y%m%d) | |
237 | +BACKUP_DIR=/backups/opencodehub | |
238 | + | |
239 | +mkdir -p $BACKUP_DIR | |
240 | + | |
241 | +# Backup PostgreSQL | |
242 | +docker compose exec -T postgres pg_dump -U opencodehub opencodehub | gzip > $BACKUP_DIR/db_$DATE.sql.gz | |
243 | + | |
244 | +# Backup repositories | |
245 | +tar -czf $BACKUP_DIR/repos_$DATE.tar.gz -C /opt/opencodehub/data . | |
246 | + | |
247 | +# Keep last 7 days | |
248 | +find $BACKUP_DIR -name "*.gz" -mtime +7 -delete | |
249 | +``` | |
250 | + | |
251 | +### Restore | |
252 | + | |
253 | +```bash | |
254 | +# Restore database | |
255 | +gunzip -c db_backup.sql.gz | docker compose exec -T postgres psql -U opencodehub opencodehub | |
256 | + | |
257 | +# Restore repositories | |
258 | +tar -xzf repos_backup.tar.gz -C /opt/opencodehub/data | |
259 | +``` | |
260 | + | |
261 | +--- | |
262 | + | |
263 | +## Troubleshooting | |
264 | + | |
265 | +### Container won't start | |
266 | + | |
267 | +```bash | |
268 | +# Check logs | |
269 | +docker compose logs app | |
270 | + | |
271 | +# Common issues: | |
272 | +# 1. Database not ready - increase healthcheck start_period | |
273 | +# 2. Port in use - change ports mapping | |
274 | +# 3. Missing env vars - check .env file | |
275 | +``` | |
276 | + | |
277 | +### Database connection issues | |
278 | + | |
279 | +```bash | |
280 | +# Verify PostgreSQL is accessible | |
281 | +docker compose exec app nc -zv postgres 5432 | |
282 | + | |
283 | +# Check database exists | |
284 | +docker compose exec postgres psql -U opencodehub -l | |
285 | +``` | |
286 | + | |
287 | +### Out of disk space | |
288 | + | |
289 | +```bash | |
290 | +# Clean Docker resources | |
291 | +docker system prune -a --volumes | |
292 | + | |
293 | +# Check disk usage | |
294 | +docker system df | |
295 | +``` | |
296 | + | |
297 | +--- | |
298 | + | |
299 | +## Next Steps | |
300 | + | |
301 | +- [Configure Nginx reverse proxy](/administration/deploy-nginx/) | |
302 | +- [Set up SSL with Let's Encrypt](/administration/deploy-nginx/#ssl-setup) | |
303 | +- [Configure monitoring](/administration/monitoring/) | |
0 | 304 | |
▼docs-site/src/content/docs/administration/deploy-nginx.md
+351-0
| @@ -0,0 +1,351 @@ | ||
1 | +--- | |
2 | +title: "Deploy with Nginx" | |
3 | +description: "Configure Nginx as a reverse proxy for OpenCodeHub with SSL and Git optimizations" | |
4 | +--- | |
5 | + | |
6 | +Nginx is the recommended reverse proxy for OpenCodeHub in production. This guide covers complete configuration including SSL, Git-specific optimizations, and performance tuning. | |
7 | + | |
8 | +## Prerequisites | |
9 | + | |
10 | +- Nginx 1.18+ installed | |
11 | +- Domain name with DNS configured | |
12 | +- OpenCodeHub running on `localhost:3000` | |
13 | + | |
14 | +```bash | |
15 | +# Install Nginx (Ubuntu/Debian) | |
16 | +sudo apt update | |
17 | +sudo apt install -y nginx | |
18 | + | |
19 | +# Install Nginx (Fedora/RHEL) | |
20 | +sudo dnf install -y nginx | |
21 | + | |
22 | +# Start and enable | |
23 | +sudo systemctl start nginx | |
24 | +sudo systemctl enable nginx | |
25 | +``` | |
26 | + | |
27 | +--- | |
28 | + | |
29 | +## Basic Configuration | |
30 | + | |
31 | +### Minimal Reverse Proxy | |
32 | + | |
33 | +```nginx | |
34 | +# /etc/nginx/sites-available/opencodehub | |
35 | +server { | |
36 | + listen 80; | |
37 | + server_name git.yourcompany.com; | |
38 | + | |
39 | + location / { | |
40 | + proxy_pass http://localhost:3000; | |
41 | + proxy_set_header Host $host; | |
42 | + proxy_set_header X-Real-IP $remote_addr; | |
43 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
44 | + proxy_set_header X-Forwarded-Proto $scheme; | |
45 | + } | |
46 | +} | |
47 | +``` | |
48 | + | |
49 | +```bash | |
50 | +# Enable site | |
51 | +sudo ln -s /etc/nginx/sites-available/opencodehub /etc/nginx/sites-enabled/ | |
52 | +sudo nginx -t | |
53 | +sudo systemctl reload nginx | |
54 | +``` | |
55 | + | |
56 | +--- | |
57 | + | |
58 | +## SSL Setup with Let's Encrypt | |
59 | + | |
60 | +### Install Certbot | |
61 | + | |
62 | +```bash | |
63 | +# Ubuntu/Debian | |
64 | +sudo apt install -y certbot python3-certbot-nginx | |
65 | + | |
66 | +# Fedora/RHEL | |
67 | +sudo dnf install -y certbot python3-certbot-nginx | |
68 | +``` | |
69 | + | |
70 | +### Obtain Certificate | |
71 | + | |
72 | +```bash | |
73 | +sudo certbot --nginx -d git.yourcompany.com | |
74 | +``` | |
75 | + | |
76 | +Certbot will automatically configure SSL in your Nginx config. | |
77 | + | |
78 | +### Manual SSL Configuration | |
79 | + | |
80 | +If you prefer manual setup or have existing certificates: | |
81 | + | |
82 | +```nginx | |
83 | +server { | |
84 | + listen 80; | |
85 | + server_name git.yourcompany.com; | |
86 | + return 301 https://$host$request_uri; | |
87 | +} | |
88 | + | |
89 | +server { | |
90 | + listen 443 ssl http2; | |
91 | + server_name git.yourcompany.com; | |
92 | + | |
93 | + # SSL Configuration | |
94 | + ssl_certificate /etc/letsencrypt/live/git.yourcompany.com/fullchain.pem; | |
95 | + ssl_certificate_key /etc/letsencrypt/live/git.yourcompany.com/privkey.pem; | |
96 | + | |
97 | + # Modern SSL settings | |
98 | + ssl_protocols TLSv1.2 TLSv1.3; | |
99 | + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; | |
100 | + ssl_prefer_server_ciphers off; | |
101 | + ssl_session_cache shared:SSL:10m; | |
102 | + ssl_session_timeout 1d; | |
103 | + ssl_stapling on; | |
104 | + ssl_stapling_verify on; | |
105 | + | |
106 | + location / { | |
107 | + proxy_pass http://localhost:3000; | |
108 | + proxy_set_header Host $host; | |
109 | + proxy_set_header X-Real-IP $remote_addr; | |
110 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
111 | + proxy_set_header X-Forwarded-Proto $scheme; | |
112 | + } | |
113 | +} | |
114 | +``` | |
115 | + | |
116 | +--- | |
117 | + | |
118 | +## Production Configuration | |
119 | + | |
120 | +Complete production-ready configuration: | |
121 | + | |
122 | +```nginx | |
123 | +# /etc/nginx/sites-available/opencodehub | |
124 | + | |
125 | +# Upstream for load balancing (optional) | |
126 | +upstream opencodehub { | |
127 | + server localhost:3000; | |
128 | + keepalive 64; | |
129 | +} | |
130 | + | |
131 | +# Rate limiting zones | |
132 | +limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; | |
133 | +limit_req_zone $binary_remote_addr zone=git:10m rate=5r/s; | |
134 | + | |
135 | +# HTTP to HTTPS redirect | |
136 | +server { | |
137 | + listen 80; | |
138 | + listen [::]:80; | |
139 | + server_name git.yourcompany.com; | |
140 | + return 301 https://$host$request_uri; | |
141 | +} | |
142 | + | |
143 | +# Main HTTPS server | |
144 | +server { | |
145 | + listen 443 ssl http2; | |
146 | + listen [::]:443 ssl http2; | |
147 | + server_name git.yourcompany.com; | |
148 | + | |
149 | + # SSL | |
150 | + ssl_certificate /etc/letsencrypt/live/git.yourcompany.com/fullchain.pem; | |
151 | + ssl_certificate_key /etc/letsencrypt/live/git.yourcompany.com/privkey.pem; | |
152 | + ssl_protocols TLSv1.2 TLSv1.3; | |
153 | + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; | |
154 | + ssl_prefer_server_ciphers off; | |
155 | + | |
156 | + # Security headers | |
157 | + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; | |
158 | + add_header X-Frame-Options "SAMEORIGIN" always; | |
159 | + add_header X-Content-Type-Options "nosniff" always; | |
160 | + add_header X-XSS-Protection "1; mode=block" always; | |
161 | + add_header Referrer-Policy "strict-origin-when-cross-origin" always; | |
162 | + | |
163 | + # Logging | |
164 | + access_log /var/log/nginx/opencodehub.access.log; | |
165 | + error_log /var/log/nginx/opencodehub.error.log; | |
166 | + | |
167 | + # Git operations (larger limits and timeouts) | |
168 | + location ~ ^/[^/]+/[^/]+\.git/ { | |
169 | + # Rate limiting for git operations | |
170 | + limit_req zone=git burst=10 nodelay; | |
171 | + | |
172 | + # Large file support (for push) | |
173 | + client_max_body_size 500M; | |
174 | + | |
175 | + # Extended timeouts for large repos | |
176 | + proxy_read_timeout 600s; | |
177 | + proxy_send_timeout 600s; | |
178 | + proxy_connect_timeout 60s; | |
179 | + | |
180 | + # Buffer settings for packfiles | |
181 | + proxy_buffer_size 128k; | |
182 | + proxy_buffers 8 256k; | |
183 | + proxy_busy_buffers_size 256k; | |
184 | + | |
185 | + # Disable buffering for real-time output | |
186 | + proxy_request_buffering off; | |
187 | + | |
188 | + proxy_pass http://opencodehub; | |
189 | + proxy_set_header Host $host; | |
190 | + proxy_set_header X-Real-IP $remote_addr; | |
191 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
192 | + proxy_set_header X-Forwarded-Proto $scheme; | |
193 | + } | |
194 | + | |
195 | + # API endpoints | |
196 | + location /api/ { | |
197 | + limit_req zone=api burst=20 nodelay; | |
198 | + | |
199 | + proxy_pass http://opencodehub; | |
200 | + proxy_set_header Host $host; | |
201 | + proxy_set_header X-Real-IP $remote_addr; | |
202 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
203 | + proxy_set_header X-Forwarded-Proto $scheme; | |
204 | + } | |
205 | + | |
206 | + # WebSocket support (for live updates) | |
207 | + location /ws { | |
208 | + proxy_pass http://opencodehub; | |
209 | + proxy_http_version 1.1; | |
210 | + proxy_set_header Upgrade $http_upgrade; | |
211 | + proxy_set_header Connection "upgrade"; | |
212 | + proxy_set_header Host $host; | |
213 | + proxy_read_timeout 86400; | |
214 | + } | |
215 | + | |
216 | + # Static files (if serving separately) | |
217 | + location /_astro/ { | |
218 | + proxy_pass http://opencodehub; | |
219 | + proxy_cache_valid 200 1d; | |
220 | + add_header Cache-Control "public, max-age=86400"; | |
221 | + } | |
222 | + | |
223 | + # Default location | |
224 | + location / { | |
225 | + proxy_pass http://opencodehub; | |
226 | + proxy_set_header Host $host; | |
227 | + proxy_set_header X-Real-IP $remote_addr; | |
228 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
229 | + proxy_set_header X-Forwarded-Proto $scheme; | |
230 | + | |
231 | + # WebSocket support | |
232 | + proxy_http_version 1.1; | |
233 | + proxy_set_header Upgrade $http_upgrade; | |
234 | + proxy_set_header Connection "upgrade"; | |
235 | + } | |
236 | +} | |
237 | +``` | |
238 | + | |
239 | +--- | |
240 | + | |
241 | +## Performance Tuning | |
242 | + | |
243 | +### Worker Configuration | |
244 | + | |
245 | +```nginx | |
246 | +# /etc/nginx/nginx.conf | |
247 | + | |
248 | +worker_processes auto; | |
249 | +worker_rlimit_nofile 65535; | |
250 | + | |
251 | +events { | |
252 | + worker_connections 4096; | |
253 | + use epoll; | |
254 | + multi_accept on; | |
255 | +} | |
256 | + | |
257 | +http { | |
258 | + # Basic settings | |
259 | + sendfile on; | |
260 | + tcp_nopush on; | |
261 | + tcp_nodelay on; | |
262 | + keepalive_timeout 65; | |
263 | + types_hash_max_size 2048; | |
264 | + | |
265 | + # Gzip compression | |
266 | + gzip on; | |
267 | + gzip_vary on; | |
268 | + gzip_proxied any; | |
269 | + gzip_comp_level 6; | |
270 | + gzip_types text/plain text/css text/xml application/json application/javascript; | |
271 | + | |
272 | + # Include site configs | |
273 | + include /etc/nginx/sites-enabled/*; | |
274 | +} | |
275 | +``` | |
276 | + | |
277 | +--- | |
278 | + | |
279 | +## Common Issues | |
280 | + | |
281 | +### 413 Request Entity Too Large | |
282 | + | |
283 | +Increase `client_max_body_size`: | |
284 | + | |
285 | +```nginx | |
286 | +location ~ ^/[^/]+/[^/]+\.git/ { | |
287 | + client_max_body_size 500M; | |
288 | + # ... | |
289 | +} | |
290 | +``` | |
291 | + | |
292 | +### 504 Gateway Timeout | |
293 | + | |
294 | +Increase proxy timeouts for large operations: | |
295 | + | |
296 | +```nginx | |
297 | +proxy_read_timeout 600s; | |
298 | +proxy_send_timeout 600s; | |
299 | +``` | |
300 | + | |
301 | +### WebSocket Connection Drops | |
302 | + | |
303 | +Ensure WebSocket headers are set: | |
304 | + | |
305 | +```nginx | |
306 | +proxy_http_version 1.1; | |
307 | +proxy_set_header Upgrade $http_upgrade; | |
308 | +proxy_set_header Connection "upgrade"; | |
309 | +``` | |
310 | + | |
311 | +--- | |
312 | + | |
313 | +## Load Balancing Multiple Instances | |
314 | + | |
315 | +```nginx | |
316 | +upstream opencodehub { | |
317 | + least_conn; | |
318 | + server 10.0.0.1:3000 weight=5; | |
319 | + server 10.0.0.2:3000 weight=5; | |
320 | + server 10.0.0.3:3000 backup; | |
321 | + keepalive 64; | |
322 | +} | |
323 | +``` | |
324 | + | |
325 | +--- | |
326 | + | |
327 | +## Monitoring | |
328 | + | |
329 | +### Enable Nginx Status | |
330 | + | |
331 | +```nginx | |
332 | +server { | |
333 | + listen 127.0.0.1:8080; | |
334 | + | |
335 | + location /nginx_status { | |
336 | + stub_status on; | |
337 | + allow 127.0.0.1; | |
338 | + deny all; | |
339 | + } | |
340 | +} | |
341 | +``` | |
342 | + | |
343 | +Access metrics: `curl http://127.0.0.1:8080/nginx_status` | |
344 | + | |
345 | +--- | |
346 | + | |
347 | +## Next Steps | |
348 | + | |
349 | +- [Configure OpenCodeHub for production](/administration/deployment/) | |
350 | +- [Set up monitoring with Grafana](/administration/monitoring/) | |
351 | +- [Configure backups](/administration/deploy-docker/#backup--restore) | |
0 | 352 | |
▼docs-site/src/content/docs/administration/deploy-podman.md
+324-0
| @@ -0,0 +1,324 @@ | ||
1 | +--- | |
2 | +title: "Deploy with Podman" | |
3 | +description: "Deploy OpenCodeHub using Podman as a rootless, daemonless Docker alternative" | |
4 | +--- | |
5 | + | |
6 | +Podman is a drop-in replacement for Docker that runs without a daemon and supports rootless containers. This guide covers deploying OpenCodeHub with Podman. | |
7 | + | |
8 | +## Why Podman? | |
9 | + | |
10 | +| Feature | Docker | Podman | | |
11 | +|---------|--------|--------| | |
12 | +| Daemon | Required | Daemonless | | |
13 | +| Root access | Required by default | Rootless by default | | |
14 | +| Systemd integration | Limited | Native | | |
15 | +| Security | Good | Better (no root daemon) | | |
16 | +| CLI compatibility | Native | Docker-compatible | | |
17 | + | |
18 | +--- | |
19 | + | |
20 | +## Prerequisites | |
21 | + | |
22 | +```bash | |
23 | +# Install Podman (Ubuntu/Debian) | |
24 | +sudo apt update | |
25 | +sudo apt install -y podman podman-compose | |
26 | + | |
27 | +# Install Podman (Fedora/RHEL/CentOS) | |
28 | +sudo dnf install -y podman podman-compose | |
29 | + | |
30 | +# Verify installation | |
31 | +podman --version | |
32 | +podman-compose --version | |
33 | +``` | |
34 | + | |
35 | +--- | |
36 | + | |
37 | +## Quick Start | |
38 | + | |
39 | +```bash | |
40 | +# Clone repository | |
41 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
42 | +cd OpenCodeHub | |
43 | + | |
44 | +# Copy environment | |
45 | +cp .env.example .env | |
46 | + | |
47 | +# Start with Podman Compose | |
48 | +podman-compose up -d | |
49 | + | |
50 | +# View logs | |
51 | +podman-compose logs -f | |
52 | +``` | |
53 | + | |
54 | +--- | |
55 | + | |
56 | +## Production Setup (Rootless) | |
57 | + | |
58 | +### 1. Create User and Directories | |
59 | + | |
60 | +```bash | |
61 | +# Create dedicated user (optional but recommended) | |
62 | +sudo useradd -r -m -d /opt/opencodehub opencodehub | |
63 | +sudo -u opencodehub mkdir -p /opt/opencodehub/{data,postgres,redis} | |
64 | + | |
65 | +# Enable lingering (keeps user services running after logout) | |
66 | +sudo loginctl enable-linger opencodehub | |
67 | +``` | |
68 | + | |
69 | +### 2. Create Pod Configuration | |
70 | + | |
71 | +Podman pods group containers like Kubernetes: | |
72 | + | |
73 | +```bash | |
74 | +# Create a pod for all services | |
75 | +podman pod create \ | |
76 | + --name opencodehub \ | |
77 | + --publish 3000:3000 \ | |
78 | + --network bridge | |
79 | +``` | |
80 | + | |
81 | +### 3. Create Containers | |
82 | + | |
83 | +```bash | |
84 | +# PostgreSQL | |
85 | +podman run -d \ | |
86 | + --pod opencodehub \ | |
87 | + --name och-postgres \ | |
88 | + -e POSTGRES_DB=opencodehub \ | |
89 | + -e POSTGRES_USER=opencodehub \ | |
90 | + -e POSTGRES_PASSWORD=securepassword \ | |
91 | + -v /opt/opencodehub/postgres:/var/lib/postgresql/data:Z \ | |
92 | + postgres:16-alpine | |
93 | + | |
94 | +# Redis | |
95 | +podman run -d \ | |
96 | + --pod opencodehub \ | |
97 | + --name och-redis \ | |
98 | + redis:7-alpine \ | |
99 | + redis-server --requirepass redispassword | |
100 | + | |
101 | +# Wait for DB to be ready | |
102 | +sleep 10 | |
103 | + | |
104 | +# OpenCodeHub App | |
105 | +podman run -d \ | |
106 | + --pod opencodehub \ | |
107 | + --name och-app \ | |
108 | + --env-file /opt/opencodehub/.env \ | |
109 | + -v /opt/opencodehub/data:/app/data:Z \ | |
110 | + ghcr.io/swadhinbiswas/opencodehub:latest | |
111 | +``` | |
112 | + | |
113 | +### 4. Production .env | |
114 | + | |
115 | +```bash | |
116 | +cat > /opt/opencodehub/.env << 'EOF' | |
117 | +NODE_ENV=production | |
118 | +PORT=3000 | |
119 | +SITE_URL=https://git.yourcompany.com | |
120 | + | |
121 | +# Secrets | |
122 | +JWT_SECRET=your-64-char-hex-secret | |
123 | +SESSION_SECRET=your-64-char-hex-secret | |
124 | +INTERNAL_HOOK_SECRET=your-64-char-hex-secret | |
125 | + | |
126 | +# Database (localhost because same pod) | |
127 | +DATABASE_DRIVER=postgres | |
128 | +DATABASE_URL=postgresql://opencodehub:securepassword@localhost:5432/opencodehub | |
129 | + | |
130 | +# Redis | |
131 | +REDIS_URL=redis://:redispassword@localhost:6379 | |
132 | + | |
133 | +# Storage | |
134 | +STORAGE_TYPE=local | |
135 | +STORAGE_PATH=/app/data/repos | |
136 | +EOF | |
137 | +``` | |
138 | + | |
139 | +--- | |
140 | + | |
141 | +## Systemd Integration | |
142 | + | |
143 | +The key advantage of Podman is native systemd support. | |
144 | + | |
145 | +### Generate Systemd Units | |
146 | + | |
147 | +```bash | |
148 | +# Generate unit files for the pod | |
149 | +podman generate systemd --new --files --name opencodehub | |
150 | + | |
151 | +# Move to user systemd directory | |
152 | +mkdir -p ~/.config/systemd/user/ | |
153 | +mv *.service ~/.config/systemd/user/ | |
154 | + | |
155 | +# Reload and enable | |
156 | +systemctl --user daemon-reload | |
157 | +systemctl --user enable pod-opencodehub.service | |
158 | +systemctl --user start pod-opencodehub.service | |
159 | +``` | |
160 | + | |
161 | +### Verify Service Status | |
162 | + | |
163 | +```bash | |
164 | +systemctl --user status pod-opencodehub.service | |
165 | +systemctl --user status container-och-app.service | |
166 | +``` | |
167 | + | |
168 | +### Auto-start on Boot | |
169 | + | |
170 | +```bash | |
171 | +# Enable lingering for the user | |
172 | +sudo loginctl enable-linger $(whoami) | |
173 | + | |
174 | +# Now services start at boot, not login | |
175 | +``` | |
176 | + | |
177 | +--- | |
178 | + | |
179 | +## Using podman-compose | |
180 | + | |
181 | +If you prefer the Compose workflow: | |
182 | + | |
183 | +```yaml | |
184 | +# podman-compose.yml | |
185 | +version: '3.8' | |
186 | + | |
187 | +services: | |
188 | + app: | |
189 | + image: ghcr.io/swadhinbiswas/opencodehub:latest | |
190 | + ports: | |
191 | + - "3000:3000" | |
192 | + env_file: | |
193 | + - .env | |
194 | + volumes: | |
195 | + - ./data:/app/data:Z | |
196 | + depends_on: | |
197 | + - postgres | |
198 | + - redis | |
199 | + | |
200 | + postgres: | |
201 | + image: postgres:16-alpine | |
202 | + environment: | |
203 | + POSTGRES_DB: opencodehub | |
204 | + POSTGRES_USER: opencodehub | |
205 | + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} | |
206 | + volumes: | |
207 | + - ./postgres:/var/lib/postgresql/data:Z | |
208 | + | |
209 | + redis: | |
210 | + image: redis:7-alpine | |
211 | + command: redis-server --requirepass ${REDIS_PASSWORD} | |
212 | +``` | |
213 | + | |
214 | +```bash | |
215 | +podman-compose up -d | |
216 | +``` | |
217 | + | |
218 | +:::note[SELinux Note] | |
219 | +The `:Z` suffix on volume mounts is required for SELinux systems (Fedora, RHEL) to set the correct context. | |
220 | +::: | |
221 | + | |
222 | +--- | |
223 | + | |
224 | +## Networking | |
225 | + | |
226 | +### Using CNI Plugins | |
227 | + | |
228 | +```bash | |
229 | +# Create a dedicated network | |
230 | +podman network create opencodehub-net | |
231 | + | |
232 | +# Use in pod | |
233 | +podman pod create \ | |
234 | + --name opencodehub \ | |
235 | + --network opencodehub-net \ | |
236 | + --publish 3000:3000 | |
237 | +``` | |
238 | + | |
239 | +### Exposing via Nginx | |
240 | + | |
241 | +```bash | |
242 | +# On host, configure Nginx to proxy to localhost:3000 | |
243 | +sudo apt install nginx | |
244 | +``` | |
245 | + | |
246 | +See [Nginx Deployment Guide](/administration/deploy-nginx/) for configuration. | |
247 | + | |
248 | +--- | |
249 | + | |
250 | +## Updating Containers | |
251 | + | |
252 | +```bash | |
253 | +# Pull latest image | |
254 | +podman pull ghcr.io/swadhinbiswas/opencodehub:latest | |
255 | + | |
256 | +# Recreate container | |
257 | +podman stop och-app | |
258 | +podman rm och-app | |
259 | +podman run -d \ | |
260 | + --pod opencodehub \ | |
261 | + --name och-app \ | |
262 | + --env-file /opt/opencodehub/.env \ | |
263 | + -v /opt/opencodehub/data:/app/data:Z \ | |
264 | + ghcr.io/swadhinbiswas/opencodehub:latest | |
265 | +``` | |
266 | + | |
267 | +### With Systemd | |
268 | + | |
269 | +```bash | |
270 | +systemctl --user restart container-och-app.service | |
271 | +``` | |
272 | + | |
273 | +--- | |
274 | + | |
275 | +## Troubleshooting | |
276 | + | |
277 | +### Permission Denied Errors | |
278 | + | |
279 | +```bash | |
280 | +# Fix SELinux context | |
281 | +sudo chcon -R -t container_file_t /opt/opencodehub/data | |
282 | + | |
283 | +# Or use :Z suffix on volumes | |
284 | +-v /path:/container/path:Z | |
285 | +``` | |
286 | + | |
287 | +### Container Can't Reach Database | |
288 | + | |
289 | +```bash | |
290 | +# In a pod, use localhost, not container names | |
291 | +DATABASE_URL=postgresql://user:pass@localhost:5432/db | |
292 | +``` | |
293 | + | |
294 | +### Rootless Networking Issues | |
295 | + | |
296 | +```bash | |
297 | +# Check slirp4netns is installed | |
298 | +sudo apt install slirp4netns | |
299 | + | |
300 | +# Or use pasta (newer, faster) | |
301 | +sudo apt install passt | |
302 | +``` | |
303 | + | |
304 | +--- | |
305 | + | |
306 | +## Comparison with Docker | |
307 | + | |
308 | +| Command | Docker | Podman | | |
309 | +|---------|--------|--------| | |
310 | +| Run container | `docker run` | `podman run` | | |
311 | +| Compose | `docker compose` | `podman-compose` | | |
312 | +| Build image | `docker build` | `podman build` | | |
313 | +| View logs | `docker logs` | `podman logs` | | |
314 | +| Systemd units | Manual | `podman generate systemd` | | |
315 | + | |
316 | +Most Docker commands work identically with Podman. | |
317 | + | |
318 | +--- | |
319 | + | |
320 | +## Next Steps | |
321 | + | |
322 | +- [Configure Nginx reverse proxy](/administration/deploy-nginx/) | |
323 | +- [Set up monitoring](/administration/monitoring/) | |
324 | +- [Backup and restore](/administration/deploy-docker/#backup--restore) | |
0 | 325 | |
▼docs-site/src/content/docs/development/database-migrations.md
+38-0
| @@ -0,0 +1,38 @@ | ||
1 | +--- | |
2 | +title: "Database Migrations" | |
3 | +description: "How to generate, apply, and inspect schema changes." | |
4 | +--- | |
5 | + | |
6 | +OpenCodeHub uses Drizzle for schema management. | |
7 | + | |
8 | +## Generate migrations | |
9 | + | |
10 | +```bash | |
11 | +npm run db:generate | |
12 | +``` | |
13 | + | |
14 | +Generates SQL migrations from the current schema. | |
15 | + | |
16 | +## Apply migrations | |
17 | + | |
18 | +```bash | |
19 | +npm run db:migrate | |
20 | +``` | |
21 | + | |
22 | +Applies pending migrations to the configured database. | |
23 | + | |
24 | +## Push schema (dev) | |
25 | + | |
26 | +```bash | |
27 | +npm run db:push | |
28 | +``` | |
29 | + | |
30 | +Pushes schema changes directly (useful for local development). | |
31 | + | |
32 | +## Inspect schema | |
33 | + | |
34 | +```bash | |
35 | +npm run db:studio | |
36 | +``` | |
37 | + | |
38 | +Opens the Drizzle Studio UI for inspecting tables. | |
0 | 39 | |
▼docs-site/src/content/docs/development/local-dev-setup.md
+52-0
| @@ -0,0 +1,52 @@ | ||
1 | +--- | |
2 | +title: "Local Development Setup" | |
3 | +description: "Set up a productive local dev environment for OpenCodeHub." | |
4 | +--- | |
5 | + | |
6 | +## Prerequisites | |
7 | + | |
8 | +- Node.js 18+ (project `engines` specify Node 20+ for runtime) | |
9 | +- Git 2.30+ | |
10 | +- PostgreSQL/MySQL/SQLite (PostgreSQL recommended) | |
11 | + | |
12 | +## Install dependencies | |
13 | + | |
14 | +```bash | |
15 | +npm install | |
16 | +``` | |
17 | + | |
18 | +## Environment | |
19 | + | |
20 | +```bash | |
21 | +cp .env.example .env | |
22 | +``` | |
23 | + | |
24 | +Set required values like `JWT_SECRET`, `SESSION_SECRET`, `INTERNAL_HOOK_SECRET`, and `SITE_URL`. | |
25 | + | |
26 | +## Database | |
27 | + | |
28 | +```bash | |
29 | +npm run db:push | |
30 | +``` | |
31 | + | |
32 | +## Seed admin user | |
33 | + | |
34 | +```bash | |
35 | +bun run scripts/seed-admin.ts | |
36 | +``` | |
37 | + | |
38 | +## Run dev server | |
39 | + | |
40 | +```bash | |
41 | +npm run dev | |
42 | +``` | |
43 | + | |
44 | +Open the app at `http://localhost:3000`. | |
45 | + | |
46 | +## Useful scripts | |
47 | + | |
48 | +- `npm run db:generate` | |
49 | +- `npm run db:migrate` | |
50 | +- `npm run db:studio` | |
51 | +- `npm run lint` | |
52 | +- `npm run test` | |
0 | 53 | |
▼docs-site/src/content/docs/development/observability.md
+29-0
| @@ -0,0 +1,29 @@ | ||
1 | +--- | |
2 | +title: "Observability" | |
3 | +description: "Tracing, metrics, and logging for OpenCodeHub." | |
4 | +--- | |
5 | + | |
6 | +OpenCodeHub ships with built‑in metrics and optional OpenTelemetry tracing. | |
7 | + | |
8 | +## Metrics (Prometheus) | |
9 | + | |
10 | +The server exposes Prometheus‑compatible metrics via: | |
11 | + | |
12 | +- `GET /api/metrics` | |
13 | + | |
14 | +Default metrics include HTTP duration and active runner counts. | |
15 | + | |
16 | +## Tracing (OpenTelemetry) | |
17 | + | |
18 | +Enable tracing by setting: | |
19 | + | |
20 | +```bash | |
21 | +ENABLE_TRACING=true | |
22 | +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces | |
23 | +``` | |
24 | + | |
25 | +The system uses the OpenTelemetry Node SDK and auto‑instrumentations to emit traces. | |
26 | + | |
27 | +## Logging | |
28 | + | |
29 | +Server logs are emitted using `pino`. In development, use `pino-pretty` or your preferred log formatter. | |
0 | 30 | |
▼docs-site/src/content/docs/development/security-hardening.md
+28-0
| @@ -0,0 +1,28 @@ | ||
1 | +--- | |
2 | +title: "Security Hardening" | |
3 | +description: "Security settings and production safeguards." | |
4 | +--- | |
5 | + | |
6 | +OpenCodeHub includes multiple built‑in security protections and requires strong secrets in production. | |
7 | + | |
8 | +## Required secrets | |
9 | + | |
10 | +Set strong values in `.env`: | |
11 | + | |
12 | +- `JWT_SECRET` (minimum 32 chars) | |
13 | +- `SESSION_SECRET` (minimum 32 chars) | |
14 | +- `INTERNAL_HOOK_SECRET` | |
15 | + | |
16 | +## Built‑in protections | |
17 | + | |
18 | +- Rate limiting for authentication | |
19 | +- CSRF protection for state‑changing requests | |
20 | +- Input validation for API payloads | |
21 | +- Hook authentication for internal Git hooks | |
22 | + | |
23 | +## Recommendations | |
24 | + | |
25 | +- Rotate tokens periodically. | |
26 | +- Use HTTPS in production. | |
27 | +- Enable and monitor audit logs. | |
28 | +- Restrict access to runner and storage secrets. | |
0 | 29 | |
▼docs-site/src/content/docs/development/testing-pipelines.md
+29-0
| @@ -0,0 +1,29 @@ | ||
1 | +--- | |
2 | +title: "Testing & CI" | |
3 | +description: "Run tests locally and understand the pipeline model." | |
4 | +--- | |
5 | + | |
6 | +## Test commands | |
7 | + | |
8 | +```bash | |
9 | +npm run test | |
10 | +npm run test:coverage | |
11 | +``` | |
12 | + | |
13 | +## Type checking & lint | |
14 | + | |
15 | +```bash | |
16 | +npm run typecheck | |
17 | +npm run lint | |
18 | +``` | |
19 | + | |
20 | +## CI pipelines | |
21 | + | |
22 | +OpenCodeHub supports GitHub Actions–compatible workflows under `.github/workflows/`. Use the Actions UI to inspect runs and view logs. | |
23 | + | |
24 | +For CLI access: | |
25 | + | |
26 | +```bash | |
27 | +och ci list | |
28 | +och ci view <run-id> --jobs | |
29 | +``` | |
0 | 30 | |
▼docs-site/src/content/docs/features/ai-review.md
+81-88
| @@ -2,7 +2,6 @@ | ||
2 | 2 | title: "AI Code Review" |
3 | 3 | --- |
4 | 4 | |
5 | - | |
6 | 5 | > Get instant, intelligent code reviews powered by GPT-4 or Claude |
7 | 6 | |
8 | 7 | AI Code Review uses advanced language models to automatically review your pull requests, catching bugs, security issues, and code quality problems before they reach production. |
| @@ -27,6 +26,7 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
27 | 26 | ### What AI Review Catches |
28 | 27 | |
29 | 28 | 🔒 **Security Vulnerabilities** |
29 | + | |
30 | 30 | - SQL injection risks |
31 | 31 | - XSS (Cross-Site Scripting) vectors |
32 | 32 | - Authentication bypasses |
| @@ -34,13 +34,15 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
34 | 34 | - Insecure dependencies |
35 | 35 | |
36 | 36 | ⚡ **Performance Issues** |
37 | + | |
37 | 38 | - N+1 database queries |
38 | 39 | - Inefficient algorithms (O(n²) → O(n log n)) |
39 | 40 | - Memory leaks |
40 | 41 | - Blocking operations in async code |
41 | 42 | - Missing indexes |
42 | 43 | |
43 | -📝 **Code Quality** | |
44 | +📝 **Code Quality** | |
45 | + | |
44 | 46 | - Best practice violations |
45 | 47 | - Code smells (long functions, deep nesting) |
46 | 48 | - Incomplete error handling |
| @@ -48,6 +50,7 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
48 | 50 | - Poor naming conventions |
49 | 51 | |
50 | 52 | 🐛 **Potential Bugs** |
53 | + | |
51 | 54 | - Null/undefined pointer exceptions |
52 | 55 | - Race conditions |
53 | 56 | - Off-by-one errors |
| @@ -81,11 +84,12 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
81 | 84 | 1. Get API key from [platform.openai.com](https://platform.openai.com/api-keys) |
82 | 85 | |
83 | 86 | 2. Add to `.env`: |
84 | - ```env | |
85 | - AI_PROVIDER=openai | |
86 | - OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx | |
87 | - OPENAI_MODEL=gpt-4-turbo-preview # or gpt-4, gpt-3.5-turbo | |
88 | - ``` | |
87 | + | |
88 | +```bash | |
89 | + AI_PROVIDER=openai | |
90 | + OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx | |
91 | + OPENAI_MODEL=gpt-4-turbo-preview # or gpt-4, gpt-3.5-turbo | |
92 | +``` | |
89 | 93 | |
90 | 94 | 3. Restart OpenCodeHub |
91 | 95 | |
| @@ -94,17 +98,18 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
94 | 98 | 1. Get API key from [console.anthropic.com](https://console.anthropic.com/) |
95 | 99 | |
96 | 100 | 2. Add to `.env`: |
97 | - ```env | |
98 | - AI_PROVIDER=anthropic | |
99 | - ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx | |
100 | - ANTHROPIC_MODEL=claude-3-opus-20240229 # or claude-3-sonnet | |
101 | - ``` | |
101 | + | |
102 | +```bash | |
103 | + AI_PROVIDER=anthropic | |
104 | + ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx | |
105 | + ANTHROPIC_MODEL=claude-3-opus-20240229 # or claude-3-sonnet | |
106 | +``` | |
102 | 107 | |
103 | 108 | 3. Restart OpenCodeHub |
104 | 109 | |
105 | 110 | **Option 3: Azure OpenAI** |
106 | 111 | |
107 | -```env | |
112 | +```bash | |
108 | 113 | AI_PROVIDER=azure-openai |
109 | 114 | AZURE_OPENAI_API_KEY=xxxxx |
110 | 115 | AZURE_OPENAI_ENDPOINT=https://yourinstance.openai.azure.com/ |
| @@ -114,7 +119,8 @@ AZURE_OPENAI_DEPLOYMENT=your-deployment-name | ||
114 | 119 | **Option 4: Self-Hosted (Coming Soon)** |
115 | 120 | |
116 | 121 | OpenCodeHub will support local models via Ollama: |
117 | -```env | |
122 | + | |
123 | +```bash | |
118 | 124 | AI_PROVIDER=ollama |
119 | 125 | OLLAMA_ENDPOINT=http://localhost:11434 |
120 | 126 | OLLAMA_MODEL=codellama:13b |
| @@ -173,6 +179,7 @@ git push origin feature-branch | ||
173 | 179 | ``` |
174 | 180 | |
175 | 181 | You'll see: |
182 | + | |
176 | 183 | - ✅ **Status check**: "AI Review: In Progress" → "AI Review: Complete" |
177 | 184 | - 💬 **Inline comments**: On specific lines with issues |
178 | 185 | - 📊 **Summary comment**: Overall assessment |
| @@ -182,26 +189,26 @@ You'll see: | ||
182 | 189 | Request AI review on-demand: |
183 | 190 | |
184 | 191 | **Via Web UI:** |
192 | + | |
185 | 193 | 1. Open PR page |
186 | 194 | 2. Click **"Request AI Review"** button |
187 | 195 | 3. Wait ~30-60 seconds |
188 | 196 | 4. Review appears as comments |
189 | 197 | |
190 | 198 | **Via CLI:** |
199 | + | |
191 | 200 | ```bash |
192 | 201 | # Review specific PR |
193 | 202 | och review ai 125 |
194 | 203 | |
195 | -# Review with custom prompt | |
196 | -och review ai 125 --focus security | |
197 | - | |
198 | -# Re-review after changes | |
199 | -och review ai 125 --force | |
204 | +# Wait for the review to complete | |
205 | +och review ai 125 --wait | |
200 | 206 | ``` |
201 | 207 | |
202 | 208 | **Via API:** |
209 | + | |
203 | 210 | ```bash |
204 | -curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \ | |
211 | +curl -X POST https://git.yourcompany.com/api/repos/OWNER/REPO/pulls/125/ai-review \ | |
205 | 212 | -H "Authorization: Bearer $TOKEN" |
206 | 213 | ``` |
207 | 214 | |
| @@ -213,30 +220,33 @@ curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \ | ||
213 | 220 | |
214 | 221 | AI assigns severity to each finding: |
215 | 222 | |
216 | -| Severity | Icon | Meaning | Example | | |
217 | -|----------|------|---------|---------| | |
218 | -| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key | | |
219 | -| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling | | |
220 | -| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification | | |
223 | +| Severity | Icon | Meaning | Example | | |
224 | +| --------------- | ---------- | -------------------------------------- | ----------------------------------------- | | |
225 | +| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key | | |
226 | +| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling | | |
227 | +| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification | | |
221 | 228 | |
222 | 229 | ### Example Review Comment |
223 | 230 | |
224 | -```markdown | |
231 | +````markdown | |
225 | 232 | 🔴 CRITICAL: SQL Injection Vulnerability |
226 | 233 | |
227 | -**File:** `src/api/users.ts` | |
234 | +**File:** `src/api/users.ts` | |
228 | 235 | **Line:** 45 |
229 | 236 | |
230 | -**Issue:** | |
237 | +**Issue:** | |
231 | 238 | User input is directly concatenated into SQL query without sanitization. |
232 | 239 | |
233 | 240 | **Current Code:** |
241 | + | |
234 | 242 | ```typescript |
235 | 243 | const query = `SELECT * FROM users WHERE email = '${userEmail}'`; |
236 | 244 | ``` |
245 | +```` | |
237 | 246 | |
238 | 247 | **Risk:** |
239 | 248 | An attacker could inject malicious SQL: |
249 | + | |
240 | 250 | ``` |
241 | 251 | userEmail = "' OR '1'='1" |
242 | 252 | → Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1' |
| @@ -245,6 +255,7 @@ userEmail = "' OR '1'='1" | ||
245 | 255 | |
246 | 256 | **Fix:** |
247 | 257 | Use parameterized queries or ORM: |
258 | + | |
248 | 259 | ```typescript |
249 | 260 | const query = `SELECT * FROM users WHERE email = ?`; |
250 | 261 | const results = await db.query(query, [userEmail]); |
| @@ -254,9 +265,11 @@ const user = await db.users.findOne({ email: userEmail }); | ||
254 | 265 | ``` |
255 | 266 | |
256 | 267 | **References:** |
268 | + | |
257 | 269 | - [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) |
258 | 270 | - [Node.js SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) |
259 | -``` | |
271 | + | |
272 | +```` | |
260 | 273 | |
261 | 274 | ### Summary Comment |
262 | 275 | |
| @@ -288,7 +301,7 @@ AI posts an overall summary: | ||
288 | 301 | - ✅ Comprehensive TypeScript types |
289 | 302 | |
290 | 303 | **Estimated fix time:** 2-3 hours |
291 | -``` | |
304 | +```` | |
292 | 305 | |
293 | 306 | --- |
294 | 307 | |
| @@ -316,7 +329,7 @@ exclude: | ||
316 | 329 | |
317 | 330 | # Focus areas (weighted 1-10) |
318 | 331 | focus: |
319 | - security: 10 # Highest priority | |
332 | + security: 10 # Highest priority | |
320 | 333 | performance: 8 |
321 | 334 | bugs: 8 |
322 | 335 | code_quality: 5 |
| @@ -326,7 +339,7 @@ focus: | ||
326 | 339 | report: |
327 | 340 | critical: always |
328 | 341 | warning: always |
329 | - info: on_request # Only if --verbose flag used | |
342 | + info: on_request # Only if --verbose flag used | |
330 | 343 | |
331 | 344 | # Custom rules |
332 | 345 | custom_rules: |
| @@ -334,7 +347,7 @@ custom_rules: | ||
334 | 347 | pattern: 'console\\.log\\(' |
335 | 348 | severity: warning |
336 | 349 | message: "Remove console.log before merging" |
337 | - | |
350 | + | |
338 | 351 | - name: "Require error handling" |
339 | 352 | pattern: 'await.*\\(.*\\)' |
340 | 353 | check: must_have_try_catch |
| @@ -347,7 +360,7 @@ instructions: > | ||
347 | 360 | 2. Database query efficiency |
348 | 361 | 3. Input validation |
349 | 362 | 4. Proper error handling |
350 | - | |
363 | + | |
351 | 364 | Our coding standards: |
352 | 365 | - TypeScript strict mode |
353 | 366 | - Functional programming preferred |
| @@ -357,18 +370,7 @@ instructions: > | ||
357 | 370 | |
358 | 371 | ### Custom Prompts |
359 | 372 | |
360 | -**Focus on specific aspects:** | |
361 | - | |
362 | -```bash | |
363 | -# Security-focused review | |
364 | -och review ai 125 --focus security | |
365 | - | |
366 | -# Performance-focused review | |
367 | -och review ai 125 --focus performance | |
368 | - | |
369 | -# Architecture review | |
370 | -och review ai 125 --prompt "Review system architecture and design patterns" | |
371 | -``` | |
373 | +Use **Settings → AI Review Rules** to add repository‑specific prompts and checks. Rules are applied automatically when AI reviews run. | |
372 | 374 | |
373 | 375 | ### Language-Specific Reviews |
374 | 376 | |
| @@ -389,36 +391,31 @@ AI automatically adapts to programming language: | ||
389 | 391 | AI review costs depend on provider and model: |
390 | 392 | |
391 | 393 | **OpenAI (GPT-4 Turbo):** |
394 | + | |
392 | 395 | - ~$0.01 per 1K tokens (input) |
393 | 396 | - ~$0.03 per 1K tokens (output) |
394 | 397 | - **Typical PR**: 500-2000 tokens = **$0.02-0.10 per review** |
395 | 398 | |
396 | 399 | **Anthropic (Claude 3 Opus):** |
400 | + | |
397 | 401 | - ~$0.015 per 1K tokens (input) |
398 | 402 | - ~$0.075 per 1K tokens (output) |
399 | 403 | - **Typical PR**: **$0.03-0.15 per review** |
400 | 404 | |
401 | 405 | **Example monthly cost:** |
406 | + | |
402 | 407 | - 50 PRs/month × $0.05/review = **$2.50/month** |
403 | 408 | - 500 PRs/month × $0.05/review = **$25/month** |
404 | 409 | |
405 | 410 | ### Cost Optimization |
406 | 411 | |
407 | -**1. Limit file count:** | |
408 | -```yaml | |
409 | -max_files_per_review: 10 # Review only 10 largest changed files | |
410 | -``` | |
412 | +To reduce cost: | |
411 | 413 | |
412 | -**2. Use cheaper models for simple changes:** | |
413 | -```yaml | |
414 | -rules: | |
415 | - - if: changed_files < 3 | |
416 | - model: gpt-3.5-turbo # 10x cheaper | |
417 | - - if: changed_files >= 3 | |
418 | - model: gpt-4-turbo | |
419 | -``` | |
414 | +- Keep PRs small and focused. | |
415 | +- Avoid very large diffs in a single review. | |
420 | 416 | |
421 | 417 | **3. Skip test files:** |
418 | + | |
422 | 419 | ```yaml |
423 | 420 | exclude: |
424 | 421 | - "**/*.test.*" |
| @@ -426,13 +423,15 @@ exclude: | ||
426 | 423 | ``` |
427 | 424 | |
428 | 425 | **4. Review only on-request:** |
426 | + | |
429 | 427 | ```yaml |
430 | -trigger: manual # Don't auto-review every push | |
428 | +trigger: manual # Don't auto-review every push | |
431 | 429 | ``` |
432 | 430 | |
433 | 431 | **5. Use severity threshold:** |
432 | + | |
434 | 433 | ```yaml |
435 | -severity_threshold: warning # Skip "info" level comments | |
434 | +severity_threshold: warning # Skip "info" level comments | |
436 | 435 | ``` |
437 | 436 | |
438 | 437 | --- |
| @@ -442,6 +441,7 @@ severity_threshold: warning # Skip "info" level comments | ||
442 | 441 | ### 💡 When to Use AI Review |
443 | 442 | |
444 | 443 | **✅ Good Use Cases:** |
444 | + | |
445 | 445 | - Security-sensitive code (auth, payments) |
446 | 446 | - Complex algorithms |
447 | 447 | - Database queries |
| @@ -449,6 +449,7 @@ severity_threshold: warning # Skip "info" level comments | ||
449 | 449 | - Third-party integrations |
450 | 450 | |
451 | 451 | **❌ Less Useful:** |
452 | + | |
452 | 453 | - Generated code |
453 | 454 | - Configuration files |
454 | 455 | - Simple refactoring |
| @@ -457,19 +458,22 @@ severity_threshold: warning # Skip "info" level comments | ||
457 | 458 | ### 💡 Responding to AI Feedback |
458 | 459 | |
459 | 460 | **1. Don't auto-fix everything** |
461 | + | |
460 | 462 | - AI can be wrong |
461 | 463 | - Context matters |
462 | 464 | - Use judgment |
463 | 465 | |
464 | 466 | **2. Engage in discussion** |
467 | + | |
465 | 468 | ```markdown |
466 | -@ai-review That's a good catch, but in this case we're using a | |
469 | +@ai-review That's a good catch, but in this case we're using a | |
467 | 470 | trusted source and the data is already sanitized earlier in the pipeline. |
468 | 471 | |
469 | 472 | See line 23 where we validate input. |
470 | 473 | ``` |
471 | 474 | |
472 | 475 | **3. Track false positives** |
476 | + | |
473 | 477 | ```yaml |
474 | 478 | # .opencodehub/ai-review.yml |
475 | 479 | ignore: |
| @@ -489,6 +493,7 @@ AI complements, doesn't replace humans: | ||
489 | 493 | ``` |
490 | 494 | |
491 | 495 | This workflow: |
496 | + | |
492 | 497 | - Reduces human reviewer burden |
493 | 498 | - Catches more bugs |
494 | 499 | - Faster feedback cycles |
| @@ -502,23 +507,25 @@ This workflow: | ||
502 | 507 | **Cause:** Large PR (many files/lines). |
503 | 508 | |
504 | 509 | **Solution:** |
510 | + | |
505 | 511 | ```bash |
506 | 512 | # Check review status |
507 | 513 | och review status 125 |
508 | 514 | |
509 | -# Cancel and retry | |
510 | -och review cancel 125 | |
511 | -och review ai 125 --max-files 5 | |
515 | +# Retry the review | |
516 | +och review ai 125 | |
512 | 517 | ``` |
513 | 518 | |
514 | 519 | ### "AI review failed" |
515 | 520 | |
516 | 521 | **Causes:** |
522 | + | |
517 | 523 | 1. API key invalid/expired |
518 | 524 | 2. Rate limit exceeded |
519 | 525 | 3. Timeout (very large PR) |
520 | 526 | |
521 | 527 | **Solutions:** |
528 | + | |
522 | 529 | ```bash |
523 | 530 | # 1. Verify API key |
524 | 531 | echo $OPENAI_API_KEY |
| @@ -527,35 +534,18 @@ echo $OPENAI_API_KEY | ||
527 | 534 | curl https://api.openai.com/v1/usage \ |
528 | 535 | -H "Authorization: Bearer $OPENAI_API_KEY" |
529 | 536 | |
530 | -# 3. Split large PR or review manually: | |
531 | -och review ai 125 --files src/api/users.ts src/api/auth.ts | |
537 | +# 3. Split large PR or review manually. | |
532 | 538 | ``` |
533 | 539 | |
534 | 540 | ### "Too many false positives" |
535 | 541 | |
536 | -**Solution:** Tune detection thresholds: | |
537 | - | |
538 | -```yaml | |
539 | -# .opencodehub/ai-review.yml | |
540 | -sensitivity: | |
541 | - security: high # Strict | |
542 | - performance: medium | |
543 | - code_quality: low # Lenient | |
544 | -``` | |
542 | +**Solution:** Use **Settings → AI Review Rules** to refine prompts and reduce false positives. | |
545 | 543 | |
546 | 544 | ### "Missing obvious bugs" |
547 | 545 | |
548 | 546 | **Cause:** AI missed context or edge case. |
549 | 547 | |
550 | -**Solution:** Add custom instructions: | |
551 | - | |
552 | -```yaml | |
553 | -instructions: > | |
554 | - Our codebase uses a custom ORM. When reviewing database code: | |
555 | - 1. `db.raw()` is already parameterized | |
556 | - 2. All user input goes through `sanitize()` middleware | |
557 | - 3. Check for missing `await` on async functions | |
558 | -``` | |
548 | +**Solution:** Add custom instructions in **Settings → AI Review Rules** so reviews incorporate your conventions. | |
559 | 549 | |
560 | 550 | --- |
561 | 551 | |
| @@ -576,11 +566,14 @@ jobs: | ||
576 | 566 | run: och review ai ${{ github.event.pull_request.number }} |
577 | 567 | env: |
578 | 568 | OCH_TOKEN: ${{ secrets.OCH_TOKEN }} |
579 | - | |
569 | + | |
580 | 570 | - name: Check for critical issues |
581 | 571 | run: | |
582 | - CRITICAL=$(och review status ${{ github.event.pull_request.number }} --json | jq '.critical_count') | |
583 | - if [ "$CRITICAL" -gt 0 ]; then | |
572 | + REVIEW=$(curl -s \ | |
573 | + -H "Authorization: Bearer $OCH_TOKEN" \ | |
574 | + https://git.yourcompany.com/api/repos/OWNER/REPO/pulls/${{ github.event.pull_request.number }}/ai-review/latest) | |
575 | + SEVERITY=$(echo "$REVIEW" | jq -r '.data.overallSeverity') | |
576 | + if [ "$SEVERITY" = "critical" ]; then | |
584 | 577 | echo "❌ Critical issues found" |
585 | 578 | exit 1 |
586 | 579 | fi |
| @@ -592,7 +585,7 @@ jobs: | ||
592 | 585 | |
593 | 586 | - [Stacked Pull Requests](stacked-prs.md) |
594 | 587 | - [Smart Merge Queue](merge-queue.md) |
595 | -- [CI/CD Integration](ci-cd.md) | |
588 | +- [CI/CD Actions](ci-actions.md) | |
596 | 589 | |
597 | 590 | --- |
598 | 591 | |
599 | 592 | |
▼docs-site/src/content/docs/features/automations.md
+59-0
| @@ -0,0 +1,59 @@ | ||
1 | +--- | |
2 | +title: "Automation Rules" | |
3 | +description: "Automate PR workflows with triggers, conditions, and actions." | |
4 | +--- | |
5 | + | |
6 | +Automation rules let you react to repository events and apply consistent workflow policies. | |
7 | + | |
8 | +## Triggers | |
9 | + | |
10 | +Supported trigger types include: | |
11 | + | |
12 | +- `pr_opened` | |
13 | +- `pr_updated` | |
14 | +- `pr_review_requested` | |
15 | +- `pr_approved` | |
16 | +- `pr_changes_requested` | |
17 | +- `pr_merged` | |
18 | +- `pr_closed` | |
19 | +- `ci_passed` | |
20 | +- `ci_failed` | |
21 | +- `label_added` | |
22 | +- `label_removed` | |
23 | +- `comment_added` | |
24 | +- `stack_created` | |
25 | +- `stack_updated` | |
26 | + | |
27 | +## Actions | |
28 | + | |
29 | +Supported actions include: | |
30 | + | |
31 | +- `add_label` | |
32 | +- `remove_label` | |
33 | +- `assign_reviewer` | |
34 | +- `assign_user` | |
35 | +- `add_to_merge_queue` | |
36 | +- `remove_from_merge_queue` | |
37 | +- `add_comment` | |
38 | +- `trigger_ai_review` | |
39 | +- `notify_slack` | |
40 | +- `close_pr` | |
41 | +- `request_changes` | |
42 | + | |
43 | +## Example rules | |
44 | + | |
45 | +- When a PR is opened, add `needs-review` and trigger AI review. | |
46 | +- When CI passes, add the PR to the merge queue with `squash`. | |
47 | +- When a label is added, assign a reviewer. | |
48 | + | |
49 | +## Manage rules | |
50 | + | |
51 | +You can configure rules in the UI under **Settings → Automations** or via the CLI: | |
52 | + | |
53 | +```bash | |
54 | +och automate list | |
55 | +och automate create | |
56 | +och automate enable "AI Review on PR" | |
57 | +och automate disable "AI Review on PR" | |
58 | +och automate delete "AI Review on PR" | |
59 | +``` | |
0 | 60 | |
▼docs-site/src/content/docs/features/ci-actions.md
+50-0
| @@ -0,0 +1,50 @@ | ||
1 | +--- | |
2 | +title: "CI/CD Actions" | |
3 | +description: "GitHub Actions–compatible workflows and self‑hosted runners." | |
4 | +--- | |
5 | + | |
6 | +OpenCodeHub supports GitHub Actions–style workflows defined in `.github/workflows/*.yml`. | |
7 | + | |
8 | +## Example workflow | |
9 | + | |
10 | +```yaml | |
11 | +name: CI | |
12 | +on: [push, pull_request] | |
13 | +jobs: | |
14 | + build: | |
15 | + runs-on: ubuntu-latest | |
16 | + steps: | |
17 | + - uses: actions/checkout@v4 | |
18 | + - run: npm install | |
19 | + - run: npm test | |
20 | +``` | |
21 | + | |
22 | +## View runs | |
23 | + | |
24 | +- Global runs: `/actions` | |
25 | +- Repo runs: `/owner/repo/actions` | |
26 | + | |
27 | +## Runner settings | |
28 | + | |
29 | +Set these environment flags to enable runners: | |
30 | + | |
31 | +```bash | |
32 | +RUNNER_ENABLED=true | |
33 | +RUNNER_DOCKER_SOCKET=/var/run/docker.sock | |
34 | +RUNNER_CONCURRENT_JOBS=2 | |
35 | +RUNNER_TIMEOUT=3600 | |
36 | +``` | |
37 | + | |
38 | +And ensure actions are enabled: | |
39 | + | |
40 | +```bash | |
41 | +ENABLE_ACTIONS=true | |
42 | +``` | |
43 | + | |
44 | +## CLI workflows | |
45 | + | |
46 | +```bash | |
47 | +och ci list --branch main | |
48 | +och ci view <run-id> --jobs | |
49 | +och ci trace <job-id> --follow | |
50 | +``` | |
0 | 51 | |
▼docs-site/src/content/docs/features/cli.md
+20-0
| @@ -0,0 +1,20 @@ | ||
1 | +--- | |
2 | +title: "CLI Workflows" | |
3 | +description: "Stack‑first workflows, reviews, and automation from the terminal." | |
4 | +--- | |
5 | + | |
6 | +The `och` CLI is the fastest way to manage stacks, PRs, and queue workflows. | |
7 | + | |
8 | +## Highlights | |
9 | + | |
10 | +- Stack creation and submission (`och stack create`, `och stack submit`) | |
11 | +- Merge queue operations (`och queue add`, `och queue list`) | |
12 | +- AI review and approvals (`och review ai`, `och review approve`) | |
13 | +- Repository‑level automations (`och automate create`) | |
14 | +- Metrics and insights (`och metrics show`, `och insights team`) | |
15 | + | |
16 | +## Start here | |
17 | + | |
18 | +- [reference/cli-overview](../reference/cli-overview) | |
19 | +- [reference/cli-stack-workflows](../reference/cli-stack-workflows) | |
20 | +- [reference/cli-merge-queue](../reference/cli-merge-queue) | |
0 | 21 | |
▼docs-site/src/content/docs/features/developer-metrics.md
+38-0
| @@ -0,0 +1,38 @@ | ||
1 | +--- | |
2 | +title: "Developer Metrics" | |
3 | +description: "Track PR velocity, review efficiency, and team health." | |
4 | +--- | |
5 | + | |
6 | +OpenCodeHub tracks PR and review metrics so teams can measure flow and spot bottlenecks. | |
7 | + | |
8 | +## What’s tracked | |
9 | + | |
10 | +- PRs opened and merged | |
11 | +- Time to first review | |
12 | +- Time to merge | |
13 | +- Review rounds (number of change‑request cycles) | |
14 | +- Review counts (approvals and changes requested) | |
15 | +- PR size (lines added/removed, files changed) | |
16 | +- Stack awareness (stack position and stacked vs non‑stacked) | |
17 | + | |
18 | +## Where metrics appear | |
19 | + | |
20 | +- **User insights**: your personal throughput and review cadence. | |
21 | +- **Repository metrics**: activity trends, top authors, and top reviewers. | |
22 | + | |
23 | +## CLI access | |
24 | + | |
25 | +```bash | |
26 | +och metrics show --weeks 4 | |
27 | +och metrics show --repo --weeks 8 | |
28 | + | |
29 | +och insights show --period 4w | |
30 | +och insights team --period 4w --limit 10 | |
31 | +och insights repo | |
32 | +``` | |
33 | + | |
34 | +## Tips | |
35 | + | |
36 | +- Use smaller PRs to reduce time to first review. | |
37 | +- Automate AI review for early feedback. | |
38 | +- Monitor review rounds to detect flaky CI or unclear PR scope. | |
0 | 39 | |
▼docs-site/src/content/docs/features/inbox.md
+33-0
| @@ -0,0 +1,33 @@ | ||
1 | +--- | |
2 | +title: "PR Inbox" | |
3 | +description: "Organize reviews with inbox sections and filters." | |
4 | +--- | |
5 | + | |
6 | +The PR inbox is a focused view for reviewing and triaging pull requests. | |
7 | + | |
8 | +## Inbox sections | |
9 | + | |
10 | +Sections group PRs using reusable filters. Default filters include: | |
11 | + | |
12 | +- **Needs my review** | |
13 | +- **Authored by me** | |
14 | +- **CI passing** | |
15 | +- **Ready to merge** | |
16 | +- **Has conflicts** | |
17 | + | |
18 | +## CLI support | |
19 | + | |
20 | +```bash | |
21 | +och inbox list | |
22 | +och inbox section list | |
23 | +och inbox section create | |
24 | +och inbox section delete "Needs Review" | |
25 | +``` | |
26 | + | |
27 | +Use `och inbox list --section "Section Name"` to filter by a section. | |
28 | + | |
29 | +## Best practices | |
30 | + | |
31 | +- Keep a **Needs my review** section pinned. | |
32 | +- Create a **Ready to merge** section for final checks. | |
33 | +- Use **Has conflicts** to surface blocked work quickly. | |
0 | 34 | |
▼docs-site/src/content/docs/features/merge-queue.md
+73-191
| @@ -2,12 +2,11 @@ | ||
2 | 2 | title: "Smart Merge Queue" |
3 | 3 | --- |
4 | 4 | |
5 | - | |
6 | 5 | > Automate merging with stack-aware CI optimization and conflict resolution |
7 | 6 | |
8 | 7 | The Smart Merge Queue automatically merges approved pull requests in the correct order, running CI efficiently and handling rebases automatically. |
9 | 8 | |
10 | -## �� Table of Contents | |
9 | +## Table of Contents | |
11 | 10 | |
12 | 11 | - [Overview](#overview) |
13 | 12 | - [How It Works](#how-it-works) |
| @@ -24,6 +23,7 @@ The Smart Merge Queue automatically merges approved pull requests in the correct | ||
24 | 23 | ### The Problem |
25 | 24 | |
26 | 25 | Manual merging is: |
26 | + | |
27 | 27 | - ⏰ **Slow** - Wait for CI, merge, repeat |
28 | 28 | - 🐛 **Error-prone** - Merge conflicts, wrong order |
29 | 29 | - 💸 **Expensive** - Run CI for every PR individually |
| @@ -33,31 +33,33 @@ Manual merging is: | ||
33 | 33 | |
34 | 34 | Smart Merge Queue: |
35 | 35 | |
36 | -✅ **Automatic Merging** - Set it and forget it | |
37 | -✅ **Stack-Aware** - Merges in dependency order | |
38 | -✅ **CI Optimization** - Batches compatible PRs | |
39 | -✅ **Conflict Resolution** - Auto-rebases or alerts | |
40 | -✅ **Priority Handling** - Hotfixes jump the queue | |
36 | +✅ **Automatic Merging** - Set it and forget it | |
37 | +✅ **Stack-Aware** - Merges in dependency order | |
38 | +✅ **CI Validation** - Ensures checks pass for latest commits | |
39 | +✅ **Priority Handling** - Higher priority merges sooner | |
41 | 40 | |
42 | 41 | ### Real-World Impact |
43 | 42 | |
44 | 43 | **Without Queue:** |
44 | + | |
45 | 45 | ``` |
46 | -PR #123 → Wait for CI (5 min) → Merge → | |
46 | +PR #123 → Wait for CI (5 min) → Merge → | |
47 | 47 | PR #124 → Rebase → Wait for CI (5 min) → Merge → |
48 | 48 | PR #125 → Rebase → Wait for CI (5 min) → Merge |
49 | 49 | Total: ~20 minutes, 3 CI runs |
50 | 50 | ``` |
51 | 51 | |
52 | 52 | **With Queue:** |
53 | + | |
53 | 54 | ``` |
54 | 55 | Add #123, #124, #125 to queue → |
55 | 56 | Queue detects dependencies → |
56 | 57 | Merges #123 → |
57 | -Auto-rebases #124 → | |
58 | -Batches #124 + #125 for CI (if compatible) → | |
59 | -Merges both | |
60 | -Total: ~8 minutes, 1-2 CI runs 🎉 | |
58 | +Validates CI for #124 → | |
59 | +Merges #124 → | |
60 | +Validates CI for #125 → | |
61 | +Merges #125 | |
62 | +Total: fewer manual steps and a consistent, ordered merge flow 🎉 | |
61 | 63 | ``` |
62 | 64 | |
63 | 65 | --- |
| @@ -74,7 +76,7 @@ Total: ~8 minutes, 1-2 CI runs 🎉 | ||
74 | 76 | 3. Queue checks: |
75 | 77 | - All approvals? ✓ |
76 | 78 | - CI passing? ✓ |
77 | - - Conflicts? Auto-rebase | |
79 | + - Conflicts? Block and report | |
78 | 80 | - Dependencies? Order correctly |
79 | 81 | ↓ |
80 | 82 | 4. Run final CI check |
| @@ -105,24 +107,13 @@ Queue automatically: | ||
105 | 107 | All automatic! |
106 | 108 | ``` |
107 | 109 | |
108 | -### CI Optimization | |
110 | +### CI Validation | |
109 | 111 | |
110 | -**Batching:** | |
111 | -``` | |
112 | -PR #126: Fix typo in docs (no code changes) | |
113 | -PR #127: Update README | |
114 | -PR #128: Fix CSS bug | |
115 | - | |
116 | -Queue batches #126 + #127 (docs only) | |
117 | -→ Single CI run | |
118 | -→ Merge both together | |
119 | -→ 50% CI cost savings! | |
120 | -``` | |
112 | +Before merging, the queue verifies: | |
121 | 113 | |
122 | -**Smart Scheduling:** | |
123 | -- Long CI jobs run in parallel | |
124 | -- Short jobs batch together | |
125 | -- Stack dependencies respected | |
114 | +- All required approvals are present | |
115 | +- Latest CI results for the PR’s head SHA are successful | |
116 | +- Stack dependencies are merged in order | |
126 | 117 | |
127 | 118 | --- |
128 | 119 | |
| @@ -149,22 +140,16 @@ Queue batches #126 + #127 (docs only) | ||
149 | 140 | # Add single PR |
150 | 141 | och queue add 125 |
151 | 142 | |
152 | -# Add multiple PRs | |
153 | -och queue add 123 124 125 | |
154 | - | |
155 | -# Add with priority (hotfix) | |
156 | -och queue add 126 --priority high | |
157 | - | |
158 | -# Add when approved | |
159 | -och queue add 127 --when approved | |
143 | +# Add with numeric priority (higher = sooner) | |
144 | +och queue add 126 --priority 5 | |
160 | 145 | ``` |
161 | 146 | |
162 | 147 | **Via API:** |
163 | 148 | |
164 | 149 | ```bash |
165 | -curl -X POST https://git.yourcompany.com/api/queue \ | |
150 | +curl -X POST https://git.yourcompany.com/api/repos/OWNER/REPO/merge-queue \ | |
166 | 151 | -H "Authorization: Bearer $TOKEN" \ |
167 | - -d '{"pr_id": 125, "priority": "normal"}' | |
152 | + -d '{"pullRequestNumber": 125, "priority": 0, "mergeMethod": "merge"}' | |
168 | 153 | ``` |
169 | 154 | |
170 | 155 | ### Viewing Queue Status |
| @@ -173,24 +158,16 @@ curl -X POST https://git.yourcompany.com/api/queue \ | ||
173 | 158 | Navigate to `/queue` or click "Merge Queue" in repository navigation |
174 | 159 | |
175 | 160 | **CLI:** |
161 | + | |
176 | 162 | ```bash |
177 | 163 | # View entire queue |
178 | 164 | och queue list |
179 | - | |
180 | -# Output: | |
181 | -# Position PR Title Status ETA | |
182 | -# 1 #125 Login UI 🏃 Running CI 2 min | |
183 | -# 2 #124 Auth service ⏳ Waiting 5 min | |
184 | -# 3 #123 Database schema ✅ Ready 7 min | |
185 | -# 4 #126 Hotfix: XSS 🔥 Priority 1 min | |
186 | - | |
187 | -# Watch queue in real-time | |
188 | -och queue watch | |
189 | 165 | ``` |
190 | 166 | |
191 | 167 | **API:** |
168 | + | |
192 | 169 | ```bash |
193 | -curl https://git.yourcompany.com/api/queue \ | |
170 | +curl https://git.yourcompany.com/api/repos/OWNER/REPO/merge-queue \ | |
194 | 171 | -H "Authorization: Bearer $TOKEN" |
195 | 172 | ``` |
196 | 173 | |
| @@ -198,21 +175,12 @@ curl https://git.yourcompany.com/api/queue \ | ||
198 | 175 | |
199 | 176 | ## Queue Management |
200 | 177 | |
201 | -### Priority Levels | |
178 | +### Priority | |
202 | 179 | |
203 | -| Priority | Use Case | Behavior | | |
204 | -|----------|----------|----------| | |
205 | -| 🔥 **Critical** | Production outage | Jumps to front, blocks other merges | | |
206 | -| ⚡ **High** | Security fix, hotfix | Merges before normal PRs | | |
207 | -| 📄 **Normal** | Regular features | Default, FIFO order | | |
208 | -| 🐌 **Low** | Refactoring, docs | Merges when queue is empty | | |
180 | +Priority is numeric. Higher values are processed sooner. Default is `0`. | |
209 | 181 | |
210 | -**Setting priority:** | |
211 | 182 | ```bash |
212 | -# CLI | |
213 | -och queue add 126 --priority critical | |
214 | - | |
215 | -# Web UI: Select priority dropdown when adding to queue | |
183 | +och queue add 126 --priority 5 | |
216 | 184 | ``` |
217 | 185 | |
218 | 186 | ### Removing from Queue |
| @@ -220,23 +188,11 @@ och queue add 126 --priority critical | ||
220 | 188 | ```bash |
221 | 189 | # Remove specific PR |
222 | 190 | och queue remove 125 |
223 | - | |
224 | -# Remove all your PRs | |
225 | -och queue remove --mine | |
226 | - | |
227 | -# Clear entire queue (admin only) | |
228 | -och queue clear | |
229 | 191 | ``` |
230 | 192 | |
231 | 193 | ### Pausing the Queue |
232 | 194 | |
233 | -```bash | |
234 | -# Pause (emergency) | |
235 | -och queue pause --reason "Investigating production issue" | |
236 | - | |
237 | -# Resume | |
238 | -och queue resume | |
239 | -``` | |
195 | +Pausing and resuming the queue is managed in the web UI. | |
240 | 196 | |
241 | 197 | --- |
242 | 198 | |
| @@ -244,36 +200,13 @@ och queue resume | ||
244 | 200 | |
245 | 201 | ### Repository Settings |
246 | 202 | |
247 | -**Repository → Settings → Merge Queue** | |
203 | +Queue behavior is enforced by: | |
248 | 204 | |
249 | -```yaml | |
250 | -# Merge strategy | |
251 | -merge_method: squash # or merge, rebase | |
252 | - | |
253 | -# CI requirements | |
254 | -require_ci: true | |
255 | -required_checks: | |
256 | - - "test" | |
257 | - - "lint" | |
258 | - - "build" | |
259 | - | |
260 | -# Approval requirements | |
261 | -require_approvals: 1 | |
262 | -dismiss_stale_reviews: true | |
263 | - | |
264 | -# Batching | |
265 | -enable_batching: true | |
266 | -batch_window: 5m # Wait 5 min to batch PRs | |
267 | -max_batch_size: 3 # Max 3 PRs per batch | |
268 | - | |
269 | -# Rebasing | |
270 | -auto_rebase: true # Auto-rebase on conflicts | |
271 | -rebase_strategy: merge # or rebase, squash | |
272 | - | |
273 | -# Priorities | |
274 | -allow_priority_override: true # Let users set priority | |
275 | -max_queue_size: 50 # Max PRs in queue | |
276 | -``` | |
205 | +- **Branch protection rules** (required approvals, required checks) | |
206 | +- **CI status** for the PR’s latest commit | |
207 | +- **Per‑entry merge method** chosen when adding to the queue | |
208 | + | |
209 | +Configuration is managed in the repository settings UI. | |
277 | 210 | |
278 | 211 | ### Branch Protection Rules |
279 | 212 | |
| @@ -288,7 +221,7 @@ branches: | ||
288 | 221 | - lint |
289 | 222 | required_approvals: 1 |
290 | 223 | allow_force_push: false |
291 | - allow_queue_bypass: false # Even admins use queue! | |
224 | + allow_queue_bypass: false # Even admins use queue! | |
292 | 225 | ``` |
293 | 226 | |
294 | 227 | ### Webhook Notifications |
| @@ -297,7 +230,7 @@ Get notified when PRs merge: | ||
297 | 230 | |
298 | 231 | ```bash |
299 | 232 | # Configure webhook |
300 | -curl -X POST https://git.yourcompany.com/api/webhooks \ | |
233 | +curl -X POST https://git.yourcompany.com/api/repos/OWNER/REPO/webhooks \ | |
301 | 234 | -d '{ |
302 | 235 | "url": "https://yourapp.com/webhook", |
303 | 236 | "events": ["merge_queue.merged", "merge_queue.failed"], |
| @@ -306,6 +239,7 @@ curl -X POST https://git.yourcompany.com/api/webhooks \ | ||
306 | 239 | ``` |
307 | 240 | |
308 | 241 | **Payload:** |
242 | + | |
309 | 243 | ```json |
310 | 244 | { |
311 | 245 | "event": "merge_queue.merged", |
| @@ -318,7 +252,7 @@ curl -X POST https://git.yourcompany.com/api/webhooks \ | ||
318 | 252 | "position": 1, |
319 | 253 | "wait_time_seconds": 180 |
320 | 254 | }, |
321 | - "timestamp": "2024-01-01T12:00:00Z" | |
255 | + "timestamp": "2024-01-01T12:00:00Z" | |
322 | 256 | } |
323 | 257 | ``` |
324 | 258 | |
| @@ -329,61 +263,34 @@ curl -X POST https://git.yourcompany.com/api/webhooks \ | ||
329 | 263 | ### 💡 When to Use the Queue |
330 | 264 | |
331 | 265 | **✅ Always use for:** |
266 | + | |
332 | 267 | - Feature branches → main |
333 | 268 | - Release branches |
334 | 269 | - Any PR affecting production |
335 | 270 | |
336 | 271 | **❌ Don't use for:** |
272 | + | |
337 | 273 | - Draft PRs |
338 | -- WIP branches | |
274 | +- WIP branches | |
339 | 275 | - Experimental features |
340 | 276 | |
341 | 277 | ### 💡 Optimizing Queue Performance |
342 | 278 | |
343 | 279 | **1. Fast CI:** |
344 | -```yaml | |
345 | -# Run quick tests in queue | |
346 | -queue_ci: | |
347 | - - unit_tests # 2 min | |
348 | - | |
349 | -# Run slow tests post-merge | |
350 | -post_merge_ci: | |
351 | - - integration_tests # 10 min | |
352 | - - e2e_tests # 15 min | |
353 | -``` | |
280 | +Keep queue checks short and focused. Run slower or optional suites after merge. | |
354 | 281 | |
355 | -**2. Parallel CIs:** | |
356 | -```yaml | |
357 | -# Run multiple jobs in parallel | |
358 | -ci: | |
359 | - - test (3 min) | |
360 | - - lint (1 min) | |
361 | - - build (2 min) | |
362 | -# Total: 3 min (not 6 min!) | |
363 | -``` | |
364 | - | |
365 | -**3. Smart batching:** | |
366 | -```bash | |
367 | -# Group related PRs | |
368 | -och queue add 123 124 125 --batch-id feature-auth | |
282 | +**2. Parallel CI:** | |
283 | +Split workflows into parallel jobs to reduce total wall‑clock time. | |
369 | 284 | |
370 | -# Queue runs CI once for entire batch | |
371 | -``` | |
285 | +**3. Small PRs:** | |
286 | +Smaller, focused PRs reduce queue time and review delays. | |
372 | 287 | |
373 | 288 | ### 💡 Handling Conflicts |
374 | 289 | |
375 | -**Automatic resolution:** | |
376 | -```yaml | |
377 | -auto_rebase: true | |
378 | -rebase_strategy: merge # Creates merge commit | |
379 | - | |
380 | -# Or: | |
381 | -rebase_strategy: rebase # Cleaner history | |
382 | -``` | |
383 | - | |
384 | 290 | **Manual resolution:** |
291 | + | |
385 | 292 | ```bash |
386 | -# If auto-rebase fails, you'll get notification | |
293 | +# If a conflict blocks the queue, you'll get a notification | |
387 | 294 | # Fix locally: |
388 | 295 | git checkout feature-branch |
389 | 296 | git fetch origin |
| @@ -397,18 +304,20 @@ git push --force-with-lease | ||
397 | 304 | ### 💡 Queue Monitoring |
398 | 305 | |
399 | 306 | **Set up alerts:** |
307 | + | |
400 | 308 | ```yaml |
401 | 309 | # Slack notification if queue stalled |
402 | 310 | alerts: |
403 | 311 | - type: queue_stalled |
404 | 312 | threshold: 30m |
405 | 313 | channel: "#eng-deployments" |
406 | - | |
314 | + | |
407 | 315 | - type: merge_failed |
408 | 316 | channel: "#eng-alerts" |
409 | 317 | ``` |
410 | 318 | |
411 | 319 | **Dashboard example:** |
320 | + | |
412 | 321 | ```bash |
413 | 322 | # View queue health |
414 | 323 | och queue stats |
| @@ -428,12 +337,14 @@ och queue stats | ||
428 | 337 | ### "PR stuck in queue" |
429 | 338 | |
430 | 339 | **Causes:** |
340 | + | |
431 | 341 | 1. CI failing |
432 | 342 | 2. Waiting for approval |
433 | 343 | 3. Merge conflict |
434 | 344 | 4. Dependency not merged yet |
435 | 345 | |
436 | 346 | **Solutions:** |
347 | + | |
437 | 348 | ```bash |
438 | 349 | # Check status |
439 | 350 | och queue status 125 |
| @@ -452,6 +363,7 @@ och queue add 125 | ||
452 | 363 | **Cause:** Complex merge conflicts. |
453 | 364 | |
454 | 365 | **Solution:** |
366 | + | |
455 | 367 | ```bash |
456 | 368 | # Remove from queue |
457 | 369 | och queue remove 125 |
| @@ -469,65 +381,35 @@ och queue add 125 | ||
469 | 381 | ### "Queue not processing" |
470 | 382 | |
471 | 383 | **Causes:** |
472 | -1. Queue paused | |
473 | -2. CI service down | |
474 | -3. Rate limit reached | |
475 | 384 | |
476 | -**Solutions:** | |
477 | -```bash | |
478 | -# Check queue status | |
479 | -och queue info | |
385 | +1. CI checks failing or still running | |
386 | +2. Required approvals missing | |
387 | +3. The PR is not in the queue | |
480 | 388 | |
481 | -# Resume if paused | |
482 | -och queue resume | |
389 | +**Solutions:** | |
483 | 390 | |
484 | -# Check CI status | |
485 | -curl https://ci-service/status | |
391 | +```bash | |
392 | +# Check the queue | |
393 | +och queue list | |
486 | 394 | |
487 | -# View queue system logs (admin) | |
488 | -och admin logs queue | |
395 | +# Inspect the PR | |
396 | +och pr view 125 | |
489 | 397 | ``` |
490 | 398 | |
491 | 399 | ### "Wrong merge order" |
492 | 400 | |
493 | -**Cause:** Dependencies not detected. | |
401 | +**Cause:** Stack dependency is missing or incorrect. | |
494 | 402 | |
495 | 403 | **Solution:** |
496 | -```bash | |
497 | -# Manually set dependency | |
498 | -och pr edit 125 --depends-on 124 | |
499 | 404 | |
500 | -# Queue will automatically reorder | |
501 | -``` | |
405 | +- Ensure PRs are created as a stack (base PR first, then dependents). | |
406 | +- Re‑submit the stack with the CLI if needed. | |
502 | 407 | |
503 | 408 | --- |
504 | 409 | |
505 | -## Advanced Features | |
410 | +## Roadmap ideas | |
506 | 411 | |
507 | -### Merge Train | |
508 | - | |
509 | -For high-velocity teams: | |
510 | - | |
511 | -```yaml | |
512 | -# Merge up to 5 PRs simultaneously | |
513 | -merge_train: | |
514 | - enabled: true | |
515 | - max_concurrent: 5 | |
516 | - | |
517 | -# Creates temporary integration branches | |
518 | -# Runs CI in parallel | |
519 | -# Merges in rapid succession | |
520 | -``` | |
521 | - | |
522 | -### Scheduled Merges | |
523 | - | |
524 | -```bash | |
525 | -# Merge during business hours only | |
526 | -och queue add 125 --schedule "Mon-Fri 9am-5pm EST" | |
527 | - | |
528 | -# Merge during maintenance window | |
529 | -och queue add 126 --schedule "Sat 2am-4am EST" | |
530 | -``` | |
412 | +Future improvements may include merge trains and scheduled merges. If these are important for your team, open an issue with your use case. | |
531 | 413 | |
532 | 414 | ### Queue Hooks |
533 | 415 | |
| @@ -541,11 +423,11 @@ module.exports = { | ||
541 | 423 | // Update JIRA ticket |
542 | 424 | // Trigger deployment |
543 | 425 | }, |
544 | - | |
426 | + | |
545 | 427 | afterMerge(pr) { |
546 | 428 | // Create release notes |
547 | 429 | // Update documentation |
548 | - } | |
430 | + }, | |
549 | 431 | }; |
550 | 432 | ``` |
551 | 433 | |
552 | 434 | |
▼docs-site/src/content/docs/features/notifications.md
+55-0
| @@ -0,0 +1,55 @@ | ||
1 | +--- | |
2 | +title: "Notifications" | |
3 | +description: "Stay informed across in‑app, email, Slack, and browser channels." | |
4 | +--- | |
5 | + | |
6 | +OpenCodeHub supports multi‑channel notifications with per‑event preferences. | |
7 | + | |
8 | +## Channels | |
9 | + | |
10 | +- In‑app notifications | |
11 | +- Email | |
12 | +- Slack | |
13 | +- Browser push | |
14 | + | |
15 | +## Event types | |
16 | + | |
17 | +Common events include: | |
18 | + | |
19 | +- `mention` | |
20 | +- `assign` | |
21 | +- `review_request` | |
22 | +- `review_submitted` | |
23 | +- `pr_approved` | |
24 | +- `pr_changes_requested` | |
25 | +- `pr_merged` | |
26 | +- `pr_closed` | |
27 | +- `comment` | |
28 | +- `ci_passed` | |
29 | +- `ci_failed` | |
30 | +- `merge_queue` | |
31 | +- `stack_update` | |
32 | +- `ai_review` | |
33 | +- `star` | |
34 | +- `watching` | |
35 | + | |
36 | +## Quiet hours & digest | |
37 | + | |
38 | +You can define quiet hours and digest schedules to reduce noise. These settings are available in user notification preferences and can be managed via the CLI. | |
39 | + | |
40 | +```bash | |
41 | +och notify settings --show | |
42 | +``` | |
43 | + | |
44 | +## CLI usage | |
45 | + | |
46 | +```bash | |
47 | +och notify list --unread | |
48 | +och notify read --all | |
49 | +``` | |
50 | + | |
51 | +## Tips | |
52 | + | |
53 | +- Turn on Slack for `review_request` and `merge_queue` events. | |
54 | +- Use quiet hours to avoid off‑hours alerting. | |
55 | +- Combine with automation rules for consistent team practices. | |
0 | 56 | |
▼docs-site/src/content/docs/guides/storage-adapters.md
+130-7
| @@ -2,7 +2,6 @@ | ||
2 | 2 | title: "Storage Adapters" |
3 | 3 | --- |
4 | 4 | |
5 | - | |
6 | 5 | OpenCodeHub supports a pluggable storage system, allowing you to store repository data (git objects, LFS files, artifacts) on various backends. |
7 | 6 | |
8 | 7 | ## Supported Adapters |
| @@ -19,7 +18,8 @@ OpenCodeHub supports a pluggable storage system, allowing you to store repositor | ||
19 | 18 | Data is stored in the `data/` directory relative to the application root. |
20 | 19 | |
21 | 20 | **Configuration:** |
22 | -```env | |
21 | + | |
22 | +```bash | |
23 | 23 | STORAGE_TYPE=local |
24 | 24 | STORAGE_PATH=./data/storage # Optional, default is ./data |
25 | 25 | ``` |
| @@ -31,7 +31,8 @@ STORAGE_PATH=./data/storage # Optional, default is ./data | ||
31 | 31 | Store data in any S3-compatible bucket. This is recommended for production scalablity. |
32 | 32 | |
33 | 33 | **Configuration:** |
34 | -```env | |
34 | + | |
35 | +```bash | |
35 | 36 | STORAGE_TYPE=s3 |
36 | 37 | STORAGE_BUCKET=my-opencodehub-bucket |
37 | 38 | STORAGE_REGION=us-east-1 # or auto |
| @@ -43,14 +44,16 @@ S3_SECRET_KEY=your-secret-key | ||
43 | 44 | ### Examples |
44 | 45 | |
45 | 46 | **MinIO (Self-hosted):** |
46 | -```env | |
47 | + | |
48 | +```bash | |
47 | 49 | STORAGE_ENDPOINT=http://minio:9000 |
48 | 50 | STORAGE_REGION=us-east-1 |
49 | 51 | S3_FORCE_PATH_STYLE=true |
50 | 52 | ``` |
51 | 53 | |
52 | 54 | **Cloudflare R2:** |
53 | -```env | |
55 | + | |
56 | +```bash | |
54 | 57 | STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com |
55 | 58 | STORAGE_REGION=auto |
56 | 59 | ``` |
| @@ -69,7 +72,7 @@ This stack is ideal for cost-effective, serverless-style deployments where you w | ||
69 | 72 | |
70 | 73 | ### Configuration |
71 | 74 | |
72 | -```env | |
75 | +```bash | |
73 | 76 | STORAGE_TYPE=gdrive |
74 | 77 | GOOGLE_CLIENT_ID=your-client-id |
75 | 78 | GOOGLE_CLIENT_SECRET=your-client-secret |
| @@ -78,6 +81,7 @@ GOOGLE_FOLDER_ID=your-folder-id | ||
78 | 81 | ``` |
79 | 82 | |
80 | 83 | **How to get Credentials:** |
84 | + | |
81 | 85 | 1. Go to [Google Cloud Console](https://console.cloud.google.com). |
82 | 86 | 2. Create a project -> Enable **Google Drive API**. |
83 | 87 | 3. Create **OAuth Client ID**. |
| @@ -88,8 +92,127 @@ GOOGLE_FOLDER_ID=your-folder-id | ||
88 | 92 | ## 🔷 Azure Blob Storage |
89 | 93 | |
90 | 94 | **Configuration:** |
91 | -```env | |
95 | + | |
96 | +```bash | |
92 | 97 | STORAGE_TYPE=azure |
93 | 98 | AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=... |
94 | 99 | AZURE_CONTAINER_NAME=opencodehub |
95 | 100 | ``` |
101 | + | |
102 | +--- | |
103 | + | |
104 | +## ⚠️ Performance Considerations | |
105 | + | |
106 | +:::caution[Important for Production] | |
107 | +Object storage (S3/R2/MinIO) introduces **significant latency** compared to local disk storage. This is a fundamental architectural trade-off you should understand before deploying. | |
108 | +::: | |
109 | + | |
110 | +### Latency Comparison | |
111 | + | |
112 | +| Operation | Local SSD | S3/R2 Object Storage | | |
113 | +| ----------------------- | --------- | -------------------- | | |
114 | +| Repository page load | ~50-100ms | **5-30 seconds** | | |
115 | +| Clone (small repo) | ~100ms | **2-10 seconds** | | |
116 | +| Push (10 files) | ~200ms | **1-5 seconds** | | |
117 | +| File browser navigation | ~30ms | **500ms-2s** | | |
118 | + | |
119 | +### Why Does This Happen? | |
120 | + | |
121 | +When using object storage, every Git operation requires **multiple HTTP requests**: | |
122 | + | |
123 | +1. **List objects** - 1 API call | |
124 | +2. **Download each file** - 40+ API calls for a typical repo | |
125 | +3. **Each request has ~50-100ms latency** (network + S3 processing) | |
126 | + | |
127 | +A repository page load might download: | |
128 | + | |
129 | +- `HEAD`, `config`, `refs/heads/*` (branch info) | |
130 | +- `objects/pack/*.idx`, `*.pack` (git objects) | |
131 | +- All hook files, info files, etc. | |
132 | + | |
133 | +**40 files × 100ms = 4+ seconds minimum** | |
134 | + | |
135 | +### Recommendations by Use Case | |
136 | + | |
137 | +#### 🏢 Production (Self-Hosted) | |
138 | + | |
139 | +**Best:** Local SSD storage + S3 for backups only | |
140 | + | |
141 | +```bash | |
142 | +STORAGE_TYPE=local | |
143 | +STORAGE_PATH=/var/lib/opencodehub/repos | |
144 | +``` | |
145 | + | |
146 | +Use a cron job or background process to sync to S3 for disaster recovery. | |
147 | + | |
148 | +#### ☁️ Serverless (Vercel, Cloudflare Workers) | |
149 | + | |
150 | +**Trade-off:** Accept latency or implement caching | |
151 | + | |
152 | +```bash | |
153 | +STORAGE_TYPE=s3 | |
154 | +# Enable local cache (coming soon) | |
155 | +ENABLE_REPO_CACHE=true | |
156 | +CACHE_TTL_SECONDS=300 | |
157 | +``` | |
158 | + | |
159 | +#### 💰 Budget-Conscious (Google Drive) | |
160 | + | |
161 | +**Trade-off:** Slower but very cheap | |
162 | + | |
163 | +```bash | |
164 | +STORAGE_TYPE=gdrive | |
165 | +``` | |
166 | + | |
167 | +Google Drive has similar latency characteristics to S3. | |
168 | + | |
169 | +### How GitHub/GitLab Solve This | |
170 | + | |
171 | +| Company | Approach | | |
172 | +| ------------- | ------------------------------------------------- | | |
173 | +| **GitHub** | Repos on local NVMe clusters, S3 for backups only | | |
174 | +| **GitLab** | Gitaly (dedicated Git servers with local storage) | | |
175 | +| **Bitbucket** | Sharded storage servers, repos cached in memory | | |
176 | + | |
177 | +**Key insight:** Major Git hosts never serve live traffic from object storage. They use local disk + memory caching for hot repos. | |
178 | + | |
179 | +### Future Improvements (Roadmap) | |
180 | + | |
181 | +We're working on: | |
182 | + | |
183 | +- **Local disk cache** with TTL-based invalidation | |
184 | +- **Parallel S3 downloads** (70% faster initial loads) | |
185 | +- **Redis metadata cache** (instant branch/tree info) | |
186 | +- **Gitaly-like architecture** for enterprise deployments | |
187 | + | |
188 | +--- | |
189 | + | |
190 | +## 🔄 Migrating Between Storage Types | |
191 | + | |
192 | +To migrate from local to S3: | |
193 | + | |
194 | +```bash | |
195 | +# 1. Backup current repos | |
196 | +tar -czf repos-backup.tar.gz data/repos/ | |
197 | + | |
198 | +# 2. Upload to S3 | |
199 | +aws s3 sync data/repos/ s3://your-bucket/repos/ | |
200 | + | |
201 | +# 3. Update .env | |
202 | +STORAGE_TYPE=s3 | |
203 | +STORAGE_BUCKET=your-bucket | |
204 | + | |
205 | +# 4. Clear local cache | |
206 | +rm -rf .tmp/repos/ | |
207 | +``` | |
208 | + | |
209 | +To migrate from S3 to local: | |
210 | + | |
211 | +```bash | |
212 | +# 1. Download from S3 | |
213 | +aws s3 sync s3://your-bucket/repos/ data/repos/ | |
214 | + | |
215 | +# 2. Update .env | |
216 | +STORAGE_TYPE=local | |
217 | +STORAGE_PATH=./data/repos | |
218 | +``` | |
96 | 219 | |
▼docs-site/src/content/docs/index.mdx
+68-1
| @@ -16,7 +16,7 @@ hero: | ||
16 | 16 | variant: minimal |
17 | 17 | --- |
18 | 18 | |
19 | -import { Card, CardGrid } from '@astrojs/starlight/components'; | |
19 | +import { Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components'; | |
20 | 20 | |
21 | 21 | ## Why OpenCodeHub? |
22 | 22 | |
| @@ -35,6 +35,60 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; | ||
35 | 35 | </Card> |
36 | 36 | </CardGrid> |
37 | 37 | |
38 | +## Architecture Overview | |
39 | + | |
40 | +``` | |
41 | +┌─────────────────────────────────────────────────────────────────┐ | |
42 | +│ OpenCodeHub │ | |
43 | +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ | |
44 | +│ │ Astro │ │ Git │ │ CLI │ │ | |
45 | +│ │ Web UI │ │ Server │ │ (och) │ │ | |
46 | +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ | |
47 | +│ └────────────────┼────────────────┘ │ | |
48 | +│ ▼ │ | |
49 | +│ ┌───────────────────────┐ │ | |
50 | +│ │ REST/GraphQL API │ │ | |
51 | +│ └───────────┬───────────┘ │ | |
52 | +│ ▼ │ | |
53 | +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ | |
54 | +│ │ PostgreSQL │ │ Redis │ │ Storage │ │ | |
55 | +│ │ (Turso) │ │ (optional) │ │ (S3/Local) │ │ | |
56 | +│ └─────────────┘ └─────────────┘ └─────────────┘ │ | |
57 | +└─────────────────────────────────────────────────────────────────┘ | |
58 | +``` | |
59 | + | |
60 | +## Deploy Anywhere | |
61 | + | |
62 | +<CardGrid> | |
63 | + <Card title="🐳 Docker" icon="add-document"> | |
64 | + The recommended way to deploy. Full Docker Compose setup with PostgreSQL and Redis. | |
65 | + [Deploy with Docker →](/administration/deploy-docker/) | |
66 | + </Card> | |
67 | + <Card title="🦭 Podman" icon="add-document"> | |
68 | + Rootless containers with native systemd integration. Ideal for security-focused deployments. | |
69 | + [Deploy with Podman →](/administration/deploy-podman/) | |
70 | + </Card> | |
71 | + <Card title="☁️ Cloudflare" icon="add-document"> | |
72 | + Use Cloudflare Tunnel + R2 for zero-port exposure and S3-compatible storage. | |
73 | + [Deploy with Cloudflare →](/administration/deploy-cloudflare/) | |
74 | + </Card> | |
75 | + <Card title="🌐 Nginx" icon="add-document"> | |
76 | + Production-ready reverse proxy config with SSL and Git-specific optimizations. | |
77 | + [Configure Nginx →](/administration/deploy-nginx/) | |
78 | + </Card> | |
79 | +</CardGrid> | |
80 | + | |
81 | +## Tech Stack | |
82 | + | |
83 | +| Layer | Technology | | |
84 | +|-------|------------| | |
85 | +| **Frontend** | Astro, React, Tailwind CSS, shadcn/ui | | |
86 | +| **Backend** | Node.js, Hono (API), simple-git | | |
87 | +| **Database** | PostgreSQL, Turso (LibSQL), SQLite | | |
88 | +| **Storage** | Local disk, S3, R2, Google Drive | | |
89 | +| **Auth** | JWT, 2FA, OAuth (GitHub, Google) | | |
90 | +| **CI/CD** | GitHub Actions compatible runners | | |
91 | + | |
38 | 92 | ## Quick Links |
39 | 93 | |
40 | 94 | <CardGrid> |
| @@ -55,3 +109,16 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; | ||
55 | 109 | [Start contributing →](/development/contributing/) |
56 | 110 | </Card> |
57 | 111 | </CardGrid> |
112 | + | |
113 | +## Storage Options | |
114 | + | |
115 | +:::tip[Performance Recommendation] | |
116 | +For best performance, use **local SSD storage** in production. Object storage (S3/R2) introduces latency. See [Storage Adapters](/guides/storage-adapters/#️-performance-considerations) for details. | |
117 | +::: | |
118 | + | |
119 | +| Storage Type | Performance | Cost | Best For | | |
120 | +|--------------|-------------|------|----------| | |
121 | +| **Local SSD** | ⚡ Fastest | $$ | Production, self-hosted | | |
122 | +| **S3/R2** | 🐢 Slow | $ | Backup, serverless | | |
123 | +| **Google Drive** | 🐢 Slow | Free | Personal projects | | |
124 | + | |
58 | 125 | |
▼docs-site/src/content/docs/reference/cli-auth-config.md
+79-0
| @@ -0,0 +1,79 @@ | ||
1 | +--- | |
2 | +title: "Auth & Config" | |
3 | +description: "Login flow, token handling, and CLI configuration." | |
4 | +--- | |
5 | + | |
6 | +This page covers how authentication and local configuration work in the OpenCodeHub CLI. | |
7 | + | |
8 | +## Authentication | |
9 | + | |
10 | +### Login | |
11 | + | |
12 | +```bash | |
13 | +och auth login --url http://localhost:3000 | |
14 | +``` | |
15 | + | |
16 | +Options: | |
17 | + | |
18 | +- `--url <url>`: Instance URL (defaults to `http://localhost:3000`) | |
19 | +- `--with-token`: Prompt for a personal access token | |
20 | +- `--token <token>`: Non‑interactive token login | |
21 | + | |
22 | +Tokens are validated via `GET /api/user` and are expected to start with the `och_` prefix. | |
23 | + | |
24 | +### Status | |
25 | + | |
26 | +```bash | |
27 | +och auth status | |
28 | +``` | |
29 | + | |
30 | +Shows the authenticated user, server, and token preview. | |
31 | + | |
32 | +### Logout | |
33 | + | |
34 | +```bash | |
35 | +och auth logout | |
36 | +``` | |
37 | + | |
38 | +Clears the stored token from your local config. | |
39 | + | |
40 | +## Configuration | |
41 | + | |
42 | +The CLI persists settings locally. View the location at any time: | |
43 | + | |
44 | +```bash | |
45 | +och config path | |
46 | +``` | |
47 | + | |
48 | +### Common keys | |
49 | + | |
50 | +- `serverUrl`: OpenCodeHub server URL | |
51 | +- `token`: Personal access token | |
52 | +- `defaultBranch`: Default base branch for PRs (default: `main`) | |
53 | +- `editor`: Editor for interactive input | |
54 | +- `pager`: Pager for long output | |
55 | + | |
56 | +### List & edit | |
57 | + | |
58 | +```bash | |
59 | +och config list | |
60 | +och config get serverUrl | |
61 | +och config set serverUrl https://git.example.com | |
62 | +och config unset editor | |
63 | +``` | |
64 | + | |
65 | +## Identity shortcut | |
66 | + | |
67 | +```bash | |
68 | +och whoami | |
69 | +``` | |
70 | + | |
71 | +A convenience command that validates your token and prints the username. | |
72 | + | |
73 | +## Shell completions | |
74 | + | |
75 | +```bash | |
76 | +och completion bash | |
77 | +``` | |
78 | + | |
79 | +Supported shells: `bash`, `zsh`, `fish`. | |
0 | 80 | |
▼docs-site/src/content/docs/reference/cli-automation-insights.md
+59-0
| @@ -0,0 +1,59 @@ | ||
1 | +--- | |
2 | +title: "Automation & Insights" | |
3 | +description: "Automations, notifications, inbox, and metrics from the CLI." | |
4 | +--- | |
5 | + | |
6 | +## Automation rules | |
7 | + | |
8 | +Automation rules run when triggers fire (like PR opened or CI passed). Use the CLI to manage them per repository. | |
9 | + | |
10 | +```bash | |
11 | +och automate list | |
12 | +och automate create | |
13 | +och automate enable "AI Review on PR" | |
14 | +och automate disable "AI Review on PR" | |
15 | +och automate delete "AI Review on PR" | |
16 | +``` | |
17 | + | |
18 | +> `och automate create` is interactive and guides you through triggers and actions. | |
19 | + | |
20 | +## Notifications | |
21 | + | |
22 | +```bash | |
23 | +och notify list --unread | |
24 | +och notify read --all | |
25 | +och notify settings --show | |
26 | +``` | |
27 | + | |
28 | +The CLI can list and mark notifications, as well as toggle preferences and quiet hours interactively. | |
29 | + | |
30 | +## PR inbox | |
31 | + | |
32 | +```bash | |
33 | +och inbox list | |
34 | +och inbox list --section "Needs Review" | |
35 | +och inbox section list | |
36 | +och inbox section create | |
37 | +och inbox section delete "Needs Review" | |
38 | +``` | |
39 | + | |
40 | +Sections support filters like: | |
41 | + | |
42 | +- Needs my review | |
43 | +- Authored by me | |
44 | +- CI passing | |
45 | +- Ready to merge | |
46 | +- Has conflicts | |
47 | + | |
48 | +## Metrics & insights | |
49 | + | |
50 | +```bash | |
51 | +och metrics show --weeks 4 | |
52 | +och metrics show --repo --weeks 8 | |
53 | + | |
54 | +och insights show --period 4w | |
55 | +och insights team --period 4w --limit 10 | |
56 | +och insights repo | |
57 | +``` | |
58 | + | |
59 | +Use `metrics` for structured summaries and `insights` for leaderboard‑style views. | |
0 | 60 | |
▼docs-site/src/content/docs/reference/cli-commands.md
+22-3
| @@ -2,7 +2,6 @@ | ||
2 | 2 | title: "CLI Command Reference" |
3 | 3 | --- |
4 | 4 | |
5 | - | |
6 | 5 | The `opencodehub-cli` (or `och`) is your primary tool for interacting with OpenCodeHub from the terminal. |
7 | 6 | |
8 | 7 | ## Installation |
| @@ -24,16 +23,20 @@ npm install -g opencodehub-cli | ||
24 | 23 | Manage authentication state. |
25 | 24 | |
26 | 25 | ### `och auth login` |
26 | + | |
27 | 27 | Authenticates with an OpenCodeHub instance. |
28 | + | |
28 | 29 | - **Interactive Mode**: Prompts for URL and credentials. |
29 | 30 | - **Flags**: |
30 | 31 | - `--url <url>`: OCH instance URL. |
31 | 32 | - `--token <token>`: Bypass browser login with existing token. |
32 | 33 | |
33 | 34 | ### `och auth status` |
35 | + | |
34 | 36 | Check current login status and user. |
35 | 37 | |
36 | 38 | ### `och auth logout` |
39 | + | |
37 | 40 | Clear stored credentials. |
38 | 41 | |
39 | 42 | --- |
| @@ -43,19 +46,25 @@ Clear stored credentials. | ||
43 | 46 | Manage repositories. |
44 | 47 | |
45 | 48 | ### `och clone <repo>` |
49 | + | |
46 | 50 | Clone a repository. |
51 | + | |
47 | 52 | - **Example**: `och clone swadhin/my-project` |
48 | 53 | - **Behavior**: Automatically sets up remote `origin`. |
49 | 54 | |
50 | 55 | ### `och create [name]` |
56 | + | |
51 | 57 | Create a new repository. |
58 | + | |
52 | 59 | - **Flags**: |
53 | 60 | - `--public`: Make repository public. |
54 | 61 | - `--private`: Make repository private. |
55 | 62 | - `--desc <text>`: Description. |
56 | 63 | |
57 | 64 | ### `och push` |
65 | + | |
58 | 66 | Push changes to the remote. |
67 | + | |
59 | 68 | - **Features**: Shows a progress bar and PR link on success. |
60 | 69 | |
61 | 70 | --- |
| @@ -65,21 +74,28 @@ Push changes to the remote. | ||
65 | 74 | Manage Stacked Pull Requests. |
66 | 75 | |
67 | 76 | ### `och stack create <branch-name>` |
77 | + | |
68 | 78 | Create a new branch stacked on top of the current one. |
79 | + | |
69 | 80 | - **Example**: `och stack create feature-b` (when on `feature-a`). |
70 | 81 | |
71 | 82 | ### `och stack submit` |
83 | + | |
72 | 84 | Submit all branches in the current stack as Pull Requests. |
85 | + | |
73 | 86 | - **Behavior**: |
74 | 87 | - Pushes all branches. |
75 | 88 | - Creates/Updates PRs. |
76 | 89 | - Sets correct base branches (`feature-a` -> `main`, `feature-b` -> `feature-a`). |
77 | 90 | |
78 | 91 | ### `och stack sync` |
92 | + | |
79 | 93 | Rebase the entire stack. |
94 | + | |
80 | 95 | - **Use Case**: When `main` has updated or a lower PR changed. |
81 | 96 | |
82 | 97 | ### `och stack view` |
98 | + | |
83 | 99 | Visualize the current stack in ASCII tree format. |
84 | 100 | |
85 | 101 | --- |
| @@ -89,10 +105,13 @@ Visualize the current stack in ASCII tree format. | ||
89 | 105 | Manage Pull Requests. |
90 | 106 | |
91 | 107 | ### `och pr list` |
108 | + | |
92 | 109 | List open PRs for current repo. |
93 | 110 | |
94 | 111 | ### `och pr checkout <number>` |
112 | + | |
95 | 113 | Checkout a PR by its number. |
96 | 114 | |
97 | -### `och pr status [number]` | |
98 | -Show CI status and review state of a PR. | |
115 | +### `och pr view <number>` | |
116 | + | |
117 | +Show details for a PR including state and branches. | |
99 | 118 | |
▼docs-site/src/content/docs/reference/cli-core-commands.md
+111-0
| @@ -0,0 +1,111 @@ | ||
1 | +--- | |
2 | +title: "Core Commands" | |
3 | +description: "Repository, PR, issue, CI, and release workflows from the CLI." | |
4 | +--- | |
5 | + | |
6 | +This page summarizes the most common non‑stack workflows. | |
7 | + | |
8 | +## Repository | |
9 | + | |
10 | +```bash | |
11 | +och repo create my-repo --private --description "My repo" | |
12 | +och repo clone owner/my-repo | |
13 | +och repo list | |
14 | +``` | |
15 | + | |
16 | +Shorthand: | |
17 | + | |
18 | +```bash | |
19 | +och init --url http://localhost:3000 | |
20 | +och push --branch main | |
21 | +``` | |
22 | + | |
23 | +## Pull requests | |
24 | + | |
25 | +```bash | |
26 | +och pr create --base main --title "Fix auth" --body "Adds token checks" | |
27 | +och pr list --state open | |
28 | +och pr view 42 | |
29 | +och pr merge 42 --squash | |
30 | +och pr checkout 42 | |
31 | +och pr diff 42 | |
32 | +och pr ready 42 | |
33 | +``` | |
34 | + | |
35 | +## Issues | |
36 | + | |
37 | +```bash | |
38 | +och issue create --title "Crash on login" --body "Steps to reproduce..." | |
39 | +och issue list --state open | |
40 | +och issue view 12 | |
41 | +och issue comment 12 --body "I can reproduce" | |
42 | +och issue close 12 --comment "Fixed in #34" | |
43 | +och issue reopen 12 | |
44 | +``` | |
45 | + | |
46 | +## Branches | |
47 | + | |
48 | +```bash | |
49 | +och branch list | |
50 | +och branch create feature/notes --checkout | |
51 | +och branch rename feature/notes feature/release-notes | |
52 | +och branch delete feature/notes --remote | |
53 | +``` | |
54 | + | |
55 | +## CI/CD | |
56 | + | |
57 | +```bash | |
58 | +och ci list --branch main | |
59 | +och ci view 101 --jobs | |
60 | +och ci trace 809 --follow | |
61 | +``` | |
62 | + | |
63 | +## Releases | |
64 | + | |
65 | +```bash | |
66 | +och release create v1.2.0 --title "1.2.0" --generate-notes | |
67 | +och release list | |
68 | +och release view v1.2.0 | |
69 | +och release delete v1.2.0 | |
70 | +``` | |
71 | + | |
72 | +## Search | |
73 | + | |
74 | +```bash | |
75 | +och search repos "opencode" | |
76 | +och search issues "crash" --state open | |
77 | +och search prs "refactor" --state open | |
78 | +``` | |
79 | + | |
80 | +## Secrets | |
81 | + | |
82 | +```bash | |
83 | +# repo secret | |
84 | +printf "s3cr3t" | och secret set API_TOKEN | |
85 | + | |
86 | +# environment secret | |
87 | +printf "s3cr3t" | och secret set API_TOKEN --env production | |
88 | + | |
89 | +# org secret | |
90 | +printf "s3cr3t" | och secret set API_TOKEN --org my-org | |
91 | + | |
92 | +och secret list | |
93 | +och secret delete API_TOKEN --yes | |
94 | +``` | |
95 | + | |
96 | +## SSH keys | |
97 | + | |
98 | +```bash | |
99 | +och ssh-key add --file ~/.ssh/id_ed25519.pub | |
100 | +och ssh-key list | |
101 | +och ssh-key delete <key-id> | |
102 | +``` | |
103 | + | |
104 | +## Direct API calls | |
105 | + | |
106 | +```bash | |
107 | +och api /user | |
108 | +och api /repos/owner/name -X PATCH -F "description=New description" | |
109 | +``` | |
110 | + | |
111 | +> All commands support `--help` for full options. | |
0 | 112 | |
▼docs-site/src/content/docs/reference/cli-merge-queue.md
+45-0
| @@ -0,0 +1,45 @@ | ||
1 | +--- | |
2 | +title: "Merge Queue" | |
3 | +description: "Manage the merge queue from the CLI." | |
4 | +--- | |
5 | + | |
6 | +The merge queue ensures PRs are merged in a stable, CI‑validated order. The CLI targets the current repository based on your `origin` remote. | |
7 | + | |
8 | +## Add a PR | |
9 | + | |
10 | +```bash | |
11 | +och queue add 128 --priority 5 --method squash | |
12 | +``` | |
13 | + | |
14 | +Options: | |
15 | + | |
16 | +- `--priority <n>`: Higher number moves the PR up the queue. | |
17 | +- `--method <merge|squash|rebase>`: Merge strategy. | |
18 | + | |
19 | +## List the queue | |
20 | + | |
21 | +```bash | |
22 | +och queue list | |
23 | +``` | |
24 | + | |
25 | +Queue item statuses: | |
26 | + | |
27 | +- `pending`: queued | |
28 | +- `running_ci`: CI is running | |
29 | +- `ready`: ready to merge | |
30 | +- `merging`: merge in progress | |
31 | +- `failed`: CI failed or merge blocked | |
32 | + | |
33 | +## Check current PR status | |
34 | + | |
35 | +```bash | |
36 | +och queue status | |
37 | +``` | |
38 | + | |
39 | +Automatically detects the PR for the current branch and reports its queue position. | |
40 | + | |
41 | +## Remove from the queue | |
42 | + | |
43 | +```bash | |
44 | +och queue remove 128 | |
45 | +``` | |
0 | 46 | |
▼docs-site/src/content/docs/reference/cli-overview.md
+56-0
| @@ -0,0 +1,56 @@ | ||
1 | +--- | |
2 | +title: "CLI Overview" | |
3 | +description: "How the OpenCodeHub CLI is structured and where to start." | |
4 | +--- | |
5 | + | |
6 | +The OpenCodeHub CLI (`och`) is a structured, command‑group based tool. Most workflows start with authentication and repository initialization, then move into PR, stack, and CI operations. | |
7 | + | |
8 | +## Installation | |
9 | + | |
10 | +```bash | |
11 | +npm install -g opencodehub-cli | |
12 | +``` | |
13 | + | |
14 | +## Global flags | |
15 | + | |
16 | +- `--help`, `-h`: Show help | |
17 | +- `--version`, `-v`: Print version | |
18 | +- `--debug`: Verbose diagnostics | |
19 | + | |
20 | +## Command matrix | |
21 | + | |
22 | +| Group | Purpose | Subcommands (common) | | |
23 | +| ---------- | ----------------------------- | ----------------------------------------------------------------------- | | |
24 | +| `auth` | Authenticate with an instance | `login`, `logout`, `status` | | |
25 | +| `config` | CLI configuration | `list`, `get`, `set`, `unset`, `path`, `reset` | | |
26 | +| `repo` | Repository actions | `create`, `clone`, `list`, `push` | | |
27 | +| `pr` | Pull requests | `create`, `list`, `view`, `merge`, `checkout`, `close`, `diff`, `ready` | | |
28 | +| `issue` | Issues | `create`, `list`, `view`, `close`, `reopen`, `comment` | | |
29 | +| `stack` | Stacked PR workflow | `create`, `submit`, `view`, `sync`, `status`, `rebase` | | |
30 | +| `queue` | Merge queue | `add`, `list`, `status`, `remove` | | |
31 | +| `review` | Reviews & AI review | `ai`, `status`, `approve`, `request-changes`, `comment` | | |
32 | +| `ci` | CI/CD runs | `list`, `view`, `trace` | | |
33 | +| `metrics` | Metrics summary | `show` | | |
34 | +| `insights` | Metrics + leaderboard | `show`, `team`, `repo` | | |
35 | +| `inbox` | PR inbox | `list`, `section` | | |
36 | +| `notify` | Notifications | `list`, `read`, `settings` | | |
37 | +| `automate` | Automation rules | `list`, `create`, `enable`, `disable`, `delete` | | |
38 | +| `branch` | Git branch management | `list`, `create`, `checkout`, `rename`, `delete`, `track` | | |
39 | +| `release` | Releases | `create`, `list`, `view`, `delete` | | |
40 | +| `search` | Search across entities | `repos`, `issues`, `prs` | | |
41 | +| `secret` | Workflow secrets | `list`, `set`, `delete` | | |
42 | +| `ssh-key` | SSH keys | `list`, `add`, `delete` | | |
43 | +| `api` | Raw API calls | Single command with `--method` | | |
44 | +| Shorthand | Convenience commands | `init`, `sync`, `status`, `whoami`, `push`, `completion` | | |
45 | + | |
46 | +> Some subcommands are interactive by design. Use `--help` on any group for the authoritative list. | |
47 | + | |
48 | +## Where configuration lives | |
49 | + | |
50 | +Run `och config path` to see the exact config file location for your OS. | |
51 | + | |
52 | +## Next steps | |
53 | + | |
54 | +- See [reference/cli-auth-config](../reference/cli-auth-config) for login and config details. | |
55 | +- See [reference/cli-core-commands](../reference/cli-core-commands) for repo, PR, and issue workflows. | |
56 | +- See [reference/cli-stack-workflows](../reference/cli-stack-workflows) for the stacked PR flow. | |
0 | 57 | |
▼docs-site/src/content/docs/reference/cli-stack-workflows.md
+75-0
| @@ -0,0 +1,75 @@ | ||
1 | +--- | |
2 | +title: "Stack Workflows" | |
3 | +description: "Create, submit, and sync stacked PRs from the CLI." | |
4 | +--- | |
5 | + | |
6 | +Stacked PRs use branches prefixed with `stack/` and are designed for incremental review. | |
7 | + | |
8 | +## Create a stack | |
9 | + | |
10 | +```bash | |
11 | +# On your base branch | |
12 | +och stack create auth-schema | |
13 | + | |
14 | +# Add changes and commit | |
15 | +# ... | |
16 | + | |
17 | +# Create the next layer | |
18 | +och stack create auth-service | |
19 | +``` | |
20 | + | |
21 | +`och stack create` creates `stack/<name>` and keeps it tied to the current stack history. | |
22 | + | |
23 | +## Submit a stack | |
24 | + | |
25 | +```bash | |
26 | +och stack submit | |
27 | +``` | |
28 | + | |
29 | +- Pushes all `stack/*` branches. | |
30 | +- Creates or updates PRs through the OpenCodeHub API when available. | |
31 | +- Uses `main` as the default base branch. | |
32 | + | |
33 | +Use `--draft` to create draft PRs, and `--message` to prefix PR titles. | |
34 | + | |
35 | +## View stack | |
36 | + | |
37 | +```bash | |
38 | +och stack view | |
39 | +``` | |
40 | + | |
41 | +Outputs a simple ASCII stack tree with the current branch highlighted. | |
42 | + | |
43 | +## Sync stack | |
44 | + | |
45 | +```bash | |
46 | +och stack sync | |
47 | +``` | |
48 | + | |
49 | +Syncs stack branches with the remote. Use: | |
50 | + | |
51 | +- `--push` to only push | |
52 | +- `--pull` to only pull (rebase) | |
53 | +- `--force` for force-with-lease pushes | |
54 | + | |
55 | +## Status | |
56 | + | |
57 | +```bash | |
58 | +och stack status | |
59 | +``` | |
60 | + | |
61 | +Shows ahead/behind and sync status for each `stack/*` branch. | |
62 | + | |
63 | +## Rebase stack | |
64 | + | |
65 | +```bash | |
66 | +och stack rebase --base main | |
67 | +``` | |
68 | + | |
69 | +Rebases the current stack on the chosen base branch. | |
70 | + | |
71 | +## Tips | |
72 | + | |
73 | +- Keep each stack layer small and reviewable. | |
74 | +- Avoid mixing unrelated changes across layers. | |
75 | +- When a lower PR merges, rebase or sync the upper layers. | |
0 | 76 | |
▼docs-site/src/content/docs/tutorials/ai-powered-reviews.md
+4-3
| @@ -2,7 +2,6 @@ | ||
2 | 2 | title: "Tutorial: AI-Powered Code Reviews" |
3 | 3 | --- |
4 | 4 | |
5 | - | |
6 | 5 | OpenCodeHub integrates with LLMs (GPT-4, Claude) to provide automated, intelligent code reviews on every Pull Request. This tutorial guides you through setting it up. |
7 | 6 | |
8 | 7 | ## Prerequisites |
| @@ -15,13 +14,15 @@ OpenCodeHub integrates with LLMs (GPT-4, Claude) to provide automated, intellige | ||
15 | 14 | As an administrator, you need to configure the AI provider in your environment variables. |
16 | 15 | |
17 | 16 | **For OpenAI:** |
18 | -```env | |
17 | + | |
18 | +```bash | |
19 | 19 | AI_PROVIDER=openai |
20 | 20 | OPENAI_API_KEY=sk-proj-... |
21 | 21 | ``` |
22 | 22 | |
23 | 23 | **For Anthropic:** |
24 | -```env | |
24 | + | |
25 | +```bash | |
25 | 26 | AI_PROVIDER=anthropic |
26 | 27 | ANTHROPIC_API_KEY=sk-ant-... |
27 | 28 | ``` |
28 | 29 | |
▼docs-site/src/content/docs/tutorials/automated-releases.md
+36-0
| @@ -0,0 +1,36 @@ | ||
1 | +--- | |
2 | +title: "Automated Releases" | |
3 | +description: "Tag, generate notes, and publish releases with the CLI." | |
4 | +--- | |
5 | + | |
6 | +This tutorial shows a simple release flow using the CLI. | |
7 | + | |
8 | +## 1) Create a tag and release | |
9 | + | |
10 | +```bash | |
11 | +och release create v1.2.0 --title "1.2.0" --generate-notes | |
12 | +``` | |
13 | + | |
14 | +Use `--draft` for internal review or `--prerelease` for pre‑release builds. | |
15 | + | |
16 | +## 2) Verify release | |
17 | + | |
18 | +```bash | |
19 | +och release view v1.2.0 | |
20 | +``` | |
21 | + | |
22 | +## 3) List releases | |
23 | + | |
24 | +```bash | |
25 | +och release list | |
26 | +``` | |
27 | + | |
28 | +## 4) Remove a release (if needed) | |
29 | + | |
30 | +```bash | |
31 | +och release delete v1.2.0 | |
32 | +``` | |
33 | + | |
34 | +## Optional: tie to CI | |
35 | + | |
36 | +Create a `.github/workflows/release.yml` that tags and calls the CLI once tests pass. | |
0 | 37 | |
▼docs-site/src/content/docs/tutorials/disaster-recovery.md
+42-0
| @@ -0,0 +1,42 @@ | ||
1 | +--- | |
2 | +title: "Disaster Recovery" | |
3 | +description: "Back up and restore OpenCodeHub data safely." | |
4 | +--- | |
5 | + | |
6 | +This guide outlines a conservative recovery strategy that works across storage backends. | |
7 | + | |
8 | +## 1) Back up the database | |
9 | + | |
10 | +Use your database’s native backup tooling to snapshot the OpenCodeHub database. Examples: | |
11 | + | |
12 | +- PostgreSQL: `pg_dump` | |
13 | +- MySQL: `mysqldump` | |
14 | + | |
15 | +## 2) Back up Git data and storage | |
16 | + | |
17 | +Back up the directories used by your instance: | |
18 | + | |
19 | +- `GIT_REPOS_PATH` (default `./data/repositories`) | |
20 | +- `STORAGE_PATH` (default `./data/storage`) | |
21 | + | |
22 | +If you use S3/R2/MinIO, snapshot the bucket or enable versioning. | |
23 | + | |
24 | +## 3) Restore | |
25 | + | |
26 | +1. Restore the database backup. | |
27 | +2. Restore repository and storage data to the configured paths. | |
28 | +3. Run migrations to match the code version: | |
29 | + | |
30 | +```bash | |
31 | +npm run db:migrate | |
32 | +``` | |
33 | + | |
34 | +## 4) Validate | |
35 | + | |
36 | +- Open the UI and check a repository. | |
37 | +- Confirm that PRs, issues, and CI history load. | |
38 | +- Perform a test clone to verify Git data integrity. | |
39 | + | |
40 | +## 5) Automate | |
41 | + | |
42 | +Use a scheduled backup job and store snapshots off‑site. | |
0 | 43 | |
▼docs-site/src/content/docs/tutorials/self-hosted-docker-nginx.md
+36-0
| @@ -0,0 +1,36 @@ | ||
1 | +--- | |
2 | +title: "Self‑Hosted with Docker + Nginx" | |
3 | +description: "Deploy OpenCodeHub behind a reverse proxy with HTTPS." | |
4 | +--- | |
5 | + | |
6 | +This tutorial combines the Docker deployment guide with an Nginx reverse proxy setup. | |
7 | + | |
8 | +## 1) Start services with Docker | |
9 | + | |
10 | +```bash | |
11 | +cp .env.example .env | |
12 | +# Edit .env with production values | |
13 | + | |
14 | +docker-compose up -d | |
15 | +``` | |
16 | + | |
17 | +## 2) Configure Nginx | |
18 | + | |
19 | +Follow the [administration/deploy-nginx](../administration/deploy-nginx) guide to configure TLS termination and proxy rules. | |
20 | + | |
21 | +## 3) Seed admin user | |
22 | + | |
23 | +```bash | |
24 | +docker-compose exec app bun run scripts/seed-admin.ts | |
25 | +``` | |
26 | + | |
27 | +## 4) Validate | |
28 | + | |
29 | +- Open your instance URL | |
30 | +- Create a repository | |
31 | +- Test `git clone` over HTTP or SSH | |
32 | + | |
33 | +## Tips | |
34 | + | |
35 | +- Enable `ENABLE_ACTIONS=true` if you plan to run workflows. | |
36 | +- Set `RUNNER_ENABLED=true` and bind the Docker socket for runners. | |
0 | 37 | |
▼docs-site/src/content/docs/tutorials/team-onboarding.md
+51-0
| @@ -0,0 +1,51 @@ | ||
1 | +--- | |
2 | +title: "Team Onboarding" | |
3 | +description: "Invite teammates, protect branches, and set review standards." | |
4 | +--- | |
5 | + | |
6 | +This tutorial helps you bring a team onto a new OpenCodeHub instance. | |
7 | + | |
8 | +## 1) Create a repository | |
9 | + | |
10 | +Create a new repository from the UI and push your initial code. | |
11 | + | |
12 | +## 2) Invite collaborators | |
13 | + | |
14 | +In repository **Settings → Collaborators**, add team members and set access levels. | |
15 | + | |
16 | +## 3) Set branch protection | |
17 | + | |
18 | +Configure protections to enforce reviews and CI: | |
19 | + | |
20 | +- Required approvals | |
21 | +- Block merges when CI fails | |
22 | +- Protect `main` | |
23 | + | |
24 | +See [guides/branch-protection](../guides/branch-protection). | |
25 | + | |
26 | +## 4) Enable stacked PRs | |
27 | + | |
28 | +Share the stack workflow with your team: | |
29 | + | |
30 | +```bash | |
31 | +npm install -g opencodehub-cli | |
32 | +och auth login --url https://your-instance | |
33 | +``` | |
34 | + | |
35 | +Then point them to [tutorials/your-first-stack](../tutorials/your-first-stack). | |
36 | + | |
37 | +## 5) Automate reviews | |
38 | + | |
39 | +Add an automation to trigger AI review on PR open: | |
40 | + | |
41 | +```bash | |
42 | +och automate create | |
43 | +``` | |
44 | + | |
45 | +## 6) Align notifications | |
46 | + | |
47 | +Encourage reviewers to enable notifications for review requests: | |
48 | + | |
49 | +```bash | |
50 | +och notify settings --show | |
51 | +``` | |
0 | 52 | |
▼docs-site/src/content/docs/tutorials/your-first-stack.md
+52-58
| @@ -2,7 +2,6 @@ | ||
2 | 2 | title: "Tutorial: Your First Stacked PR" |
3 | 3 | --- |
4 | 4 | |
5 | - | |
6 | 5 | > Learn how to split a large feature into reviewable chunks in 15 minutes |
7 | 6 | |
8 | 7 | This hands-on tutorial teaches you how to use Stacked Pull Requests to break down a large feature into small, focused PRs that ship faster. |
| @@ -14,7 +13,7 @@ This hands-on tutorial teaches you how to use Stacked Pull Requests to break dow | ||
14 | 13 | - Navigate the review process |
15 | 14 | - Merge the stack successfully |
16 | 15 | |
17 | -**Time:** 15-20 minutes | |
16 | +**Time:** 15-20 minutes | |
18 | 17 | **Difficulty:** Beginner |
19 | 18 | |
20 | 19 | --- |
| @@ -30,6 +29,7 @@ Before starting, make sure you have: | ||
30 | 29 | - [ ] Basic Git knowledge (branch, commit, push) |
31 | 30 | |
32 | 31 | **First time with OCH CLI?** |
32 | + | |
33 | 33 | ```bash |
34 | 34 | # Install |
35 | 35 | npm install -g opencodehub-cli |
| @@ -127,7 +127,7 @@ och stack submit | ||
127 | 127 | # Output: |
128 | 128 | # ✓ Pushing stack/auth-database |
129 | 129 | # ✓ Creating pull request |
130 | -# | |
130 | +# | |
131 | 131 | # PR #123 created: feat: add users table migration |
132 | 132 | # https://git.yourcompany.com/yourorg/your-repo/pulls/123 |
133 | 133 | ``` |
| @@ -135,6 +135,7 @@ och stack submit | ||
135 | 135 | **2.5. Verify in web UI:** |
136 | 136 | |
137 | 137 | Open the PR link. You should see: |
138 | + | |
138 | 139 | - Title: "feat: add users table migration" |
139 | 140 | - Files changed: `db/migrations/001_create_users_table.sql` |
140 | 141 | - Status: Draft or Ready for Review |
| @@ -165,34 +166,34 @@ import { db } from '../db'; | ||
165 | 166 | export class AuthService { |
166 | 167 | async register(email: string, password: string) { |
167 | 168 | const hash = await bcrypt.hash(password, 10); |
168 | - | |
169 | + | |
169 | 170 | const user = await db.users.create({ |
170 | 171 | email, |
171 | 172 | password_hash: hash |
172 | 173 | }); |
173 | - | |
174 | + | |
174 | 175 | return { id: user.id, email: user.email }; |
175 | 176 | } |
176 | - | |
177 | + | |
177 | 178 | async login(email: string, password: string) { |
178 | 179 | const user = await db.users.findByEmail(email); |
179 | - | |
180 | + | |
180 | 181 | if (!user) { |
181 | 182 | throw new Error('Invalid credentials'); |
182 | 183 | } |
183 | - | |
184 | + | |
184 | 185 | const valid = await bcrypt.compare(password, user.password_hash); |
185 | - | |
186 | + | |
186 | 187 | if (!valid) { |
187 | 188 | throw new Error('Invalid credentials'); |
188 | 189 | } |
189 | - | |
190 | + | |
190 | 191 | const token = jwt.sign( |
191 | 192 | { userId: user.id, email: user.email }, |
192 | 193 | process.env.JWT_SECRET!, |
193 | 194 | { expiresIn: '7d' } |
194 | 195 | ); |
195 | - | |
196 | + | |
196 | 197 | return { token, user: { id: user.id, email: user.email } }; |
197 | 198 | } |
198 | 199 | } |
| @@ -267,14 +268,14 @@ const authService = new AuthService(); | ||
267 | 268 | router.post('/register', async (req, res) => { |
268 | 269 | try { |
269 | 270 | const { email, password } = req.body; |
270 | - | |
271 | + | |
271 | 272 | // Validation |
272 | 273 | if (!email || !password) { |
273 | 274 | return res.status(400).json({ error: 'Email and password required' }); |
274 | 275 | } |
275 | - | |
276 | + | |
276 | 277 | const user = await authService.register(email, password); |
277 | - | |
278 | + | |
278 | 279 | res.status(201).json({ user }); |
279 | 280 | } catch (error) { |
280 | 281 | res.status(500).json({ error: error.message }); |
| @@ -284,9 +285,9 @@ router.post('/register', async (req, res) => { | ||
284 | 285 | router.post('/login', async (req, res) => { |
285 | 286 | try { |
286 | 287 | const { email, password } = req.body; |
287 | - | |
288 | + | |
288 | 289 | const result = await authService.login(email, password); |
289 | - | |
290 | + | |
290 | 291 | res.json(result); |
291 | 292 | } catch (error) { |
292 | 293 | res.status(401).json({ error: error.message }); |
| @@ -314,7 +315,7 @@ och stack submit | ||
314 | 315 | |
315 | 316 | # Output: |
316 | 317 | # ✓ Pushing stack/auth-api |
317 | -# ✓ Creating pull request | |
318 | +# ✓ Creating pull request | |
318 | 319 | # ✓ Linking to base PR #124 |
319 | 320 | # |
320 | 321 | # PR #125 created: feat: add authentication API endpoints |
| @@ -356,6 +357,7 @@ curl -X PATCH https://git.yourcompany.com/api/prs/123 \ | ||
356 | 357 | **5.2. Request reviews:** |
357 | 358 | |
358 | 359 | In the web UI for PR #123: |
360 | + | |
359 | 361 | 1. Click "Reviewers" → Select team members |
360 | 362 | 2. Add comment: "This is the first PR in a stack of 3. Reviewing these in order would be helpful!" |
361 | 363 | |
| @@ -373,14 +375,14 @@ import { db } from '../db'; | ||
373 | 375 | test('creates users table', async () => { |
374 | 376 | // Run migration |
375 | 377 | await db.migrate(); |
376 | - | |
378 | + | |
377 | 379 | // Verify table exists |
378 | 380 | const tables = await db.raw(` |
379 | - SELECT table_name | |
380 | - FROM information_schema.tables | |
381 | + SELECT table_name | |
382 | + FROM information_schema.tables | |
381 | 383 | WHERE table_name = 'users' |
382 | 384 | `); |
383 | - | |
385 | + | |
384 | 386 | expect(tables.length).toBe(1); |
385 | 387 | }); |
386 | 388 | EOF |
| @@ -395,15 +397,15 @@ git push | ||
395 | 397 | **5.4. Monitor review progress:** |
396 | 398 | |
397 | 399 | ```bash |
398 | -# Check PR status | |
399 | -och pr status 123 | |
400 | +# View PR details | |
401 | +och pr view 123 | |
400 | 402 | |
401 | 403 | # Output: |
402 | -# PR #123: feat: add users table migration | |
403 | -# Status: 🔍 In Review | |
404 | -# Approvals: 1/1 required | |
405 | -# CI: ✅ Passing | |
406 | -# Comments: 2 | |
404 | +# #123 feat: add users table migration | |
405 | +# State: OPEN | |
406 | +# Author: @you | |
407 | +# Branch: stack/auth-database → main | |
408 | +# Created: 1/22/2026 | |
407 | 409 | ``` |
408 | 410 | |
409 | 411 | --- |
| @@ -415,37 +417,25 @@ Once PR #123 is approved, the magic happens! | ||
415 | 417 | **6.1. Use the merge queue:** |
416 | 418 | |
417 | 419 | ```bash |
418 | -# Add all PRs to queue | |
419 | -och queue add 123 124 125 | |
420 | - | |
421 | -# Or add individually as they get approved: | |
420 | +# Add individually as they get approved: | |
422 | 421 | och queue add 123 # Merges first |
423 | -# PR #124 auto-rebases onto new main | |
422 | +# PR #124 becomes next in line after #123 merges | |
424 | 423 | och queue add 124 # Merges second |
425 | -# PR #125 auto-rebases onto new main | |
424 | +# PR #125 becomes next in line after #124 merges | |
426 | 425 | och queue add 125 # Merges third |
427 | 426 | ``` |
428 | 427 | |
429 | 428 | **6.2. Watch it merge automatically:** |
430 | 429 | |
431 | 430 | ```bash |
432 | -# Monitor queue | |
433 | -och queue watch | |
434 | - | |
435 | -# Output (live updates): | |
436 | -# Position PR Status ETA | |
437 | -# 1 #123 🏃 Running CI 1 min | |
438 | -# 2 #124 ⏳ Waiting 3 min | |
439 | -# 3 #125 ⏳ Waiting 5 min | |
440 | -# | |
441 | -# [1 min later] | |
442 | -# ✅ PR #123 merged! | |
443 | -# 🔄 Rebasing PR #124... | |
444 | -# 🏃 Running CI for #124... | |
445 | -# | |
446 | -# [2 min later] | |
447 | -# ✅ PR #124 merged! | |
448 | -# 🔄 Rebasing PR #125... | |
431 | +# Check queue status | |
432 | +och queue list | |
433 | + | |
434 | +# Output (refresh as needed): | |
435 | +# Pos PR Title Status | |
436 | +# 1 #123 Add users table migration running_ci | |
437 | +# 2 #124 Auth service pending | |
438 | +# 3 #125 Auth API pending | |
449 | 439 | ``` |
450 | 440 | |
451 | 441 | **6.3. Celebrate! 🎉** |
| @@ -459,7 +449,7 @@ git log --oneline -3 | ||
459 | 449 | |
460 | 450 | # Output: |
461 | 451 | # abc123 feat: add authentication API endpoints |
462 | -# def456 feat: implement authentication service | |
452 | +# def456 feat: implement authentication service | |
463 | 453 | # ghi789 feat: add users table migration |
464 | 454 | ``` |
465 | 455 | |
| @@ -469,22 +459,24 @@ All 3 PRs merged! Your auth system is live! | ||
469 | 459 | |
470 | 460 | ## What You've Learned |
471 | 461 | |
472 | -✅ How to create a stack of dependent PRs | |
473 | -✅ Using `och stack create` for easy stacking | |
474 | -✅ Submitting stacks with `och stack submit` | |
475 | -✅ Viewing stack visualization | |
476 | -✅ Using the merge queue for automatic merging | |
462 | +✅ How to create a stack of dependent PRs | |
463 | +✅ Using `och stack create` for easy stacking | |
464 | +✅ Submitting stacks with `och stack submit` | |
465 | +✅ Viewing stack visualization | |
466 | +✅ Using the merge queue for automatic merging | |
477 | 467 | ✅ How auto-rebasing works |
478 | 468 | |
479 | 469 | ## Key Takeaways |
480 | 470 | |
481 | 471 | **Stacked PRs are better because:** |
472 | + | |
482 | 473 | - Each PR was ~50-100 lines (easy to review) |
483 | 474 | - Reviews happened in parallel (faster) |
484 | 475 | - Each merge was low-risk (incremental) |
485 | 476 | - Clear history (logical progression) |
486 | 477 | |
487 | 478 | **vs. One Large PR:** |
479 | + | |
488 | 480 | - Would be ~200-300 lines |
489 | 481 | - Single review bottleneck |
490 | 482 | - High-risk merge |
| @@ -497,6 +489,7 @@ All 3 PRs merged! Your auth system is live! | ||
497 | 489 | ### Practice More |
498 | 490 | |
499 | 491 | Try stacking your next feature: |
492 | + | |
500 | 493 | - 🎨 Frontend feature → Split UI, logic, styles |
501 | 494 | - 🔧 Backend feature → Split models, services, controllers |
502 | 495 | - 📊 Data pipeline → Split ingestion, processing, output |
| @@ -504,10 +497,11 @@ Try stacking your next feature: | ||
504 | 497 | ### Advanced Techniques |
505 | 498 | |
506 | 499 | Learn more about: |
500 | + | |
507 | 501 | - [Parallel stacks](../features/stacked-prs.md#parallel-stacks) |
508 | 502 | - [Stack reordering](../features/stacked-prs.md#reordering) |
509 | 503 | - [AI code review](../features/ai-review.md) on stacks |
510 | -- [Advanced CLI commands](../../cli/README.md) | |
504 | +- [CLI overview](../reference/cli-overview) | |
511 | 505 | |
512 | 506 | ### Join the Community |
513 | 507 | |
514 | 508 | |
▼docs-site/src/content/docs/tutorials/zero-to-production.md
+57-0
| @@ -0,0 +1,57 @@ | ||
1 | +--- | |
2 | +title: "From Zero to Production" | |
3 | +description: "End‑to‑end setup from local dev to a production‑ready instance." | |
4 | +--- | |
5 | + | |
6 | +This tutorial walks from a clean clone to a production‑ready deployment. | |
7 | + | |
8 | +## 1) Local setup | |
9 | + | |
10 | +```bash | |
11 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
12 | +cd OpenCodeHub | |
13 | +npm install | |
14 | +cp .env.example .env | |
15 | +``` | |
16 | + | |
17 | +Update the required secrets in `.env` (`JWT_SECRET`, `SESSION_SECRET`, `INTERNAL_HOOK_SECRET`, `SITE_URL`). | |
18 | + | |
19 | +## 2) Database migrations | |
20 | + | |
21 | +```bash | |
22 | +npm run db:push | |
23 | +``` | |
24 | + | |
25 | +## 3) Create admin user | |
26 | + | |
27 | +```bash | |
28 | +bun run scripts/seed-admin.ts | |
29 | +``` | |
30 | + | |
31 | +## 4) Start the server | |
32 | + | |
33 | +```bash | |
34 | +npm run dev | |
35 | +``` | |
36 | + | |
37 | +Open the app at `http://localhost:3000` and log in. | |
38 | + | |
39 | +## 5) Production deployment | |
40 | + | |
41 | +For production, use Docker: | |
42 | + | |
43 | +```bash | |
44 | +docker-compose up -d | |
45 | +``` | |
46 | + | |
47 | +Then seed the admin user inside the container: | |
48 | + | |
49 | +```bash | |
50 | +docker-compose exec app bun run scripts/seed-admin.ts | |
51 | +``` | |
52 | + | |
53 | +## Next steps | |
54 | + | |
55 | +- [administration/deployment](../administration/deployment) | |
56 | +- [administration/configuration](../administration/configuration) | |
57 | +- [administration/deploy-docker](../administration/deploy-docker) | |
0 | 58 | |
▼docs/features/ai-review.md
+62-23
| @@ -1,3 +1,8 @@ | ||
1 | +--- | |
2 | +title: "Legacy: AI Code Review" | |
3 | +slug: "legacy/features/ai-review" | |
4 | +--- | |
5 | + | |
1 | 6 | # AI Code Review |
2 | 7 | |
3 | 8 | > Get instant, intelligent code reviews powered by GPT-4 or Claude |
| @@ -24,6 +29,7 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
24 | 29 | ### What AI Review Catches |
25 | 30 | |
26 | 31 | 🔒 **Security Vulnerabilities** |
32 | + | |
27 | 33 | - SQL injection risks |
28 | 34 | - XSS (Cross-Site Scripting) vectors |
29 | 35 | - Authentication bypasses |
| @@ -31,13 +37,15 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
31 | 37 | - Insecure dependencies |
32 | 38 | |
33 | 39 | ⚡ **Performance Issues** |
40 | + | |
34 | 41 | - N+1 database queries |
35 | 42 | - Inefficient algorithms (O(n²) → O(n log n)) |
36 | 43 | - Memory leaks |
37 | 44 | - Blocking operations in async code |
38 | 45 | - Missing indexes |
39 | 46 | |
40 | -📝 **Code Quality** | |
47 | +📝 **Code Quality** | |
48 | + | |
41 | 49 | - Best practice violations |
42 | 50 | - Code smells (long functions, deep nesting) |
43 | 51 | - Incomplete error handling |
| @@ -45,6 +53,7 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
45 | 53 | - Poor naming conventions |
46 | 54 | |
47 | 55 | 🐛 **Potential Bugs** |
56 | + | |
48 | 57 | - Null/undefined pointer exceptions |
49 | 58 | - Race conditions |
50 | 59 | - Off-by-one errors |
| @@ -78,6 +87,7 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
78 | 87 | 1. Get API key from [platform.openai.com](https://platform.openai.com/api-keys) |
79 | 88 | |
80 | 89 | 2. Add to `.env`: |
90 | + | |
81 | 91 | ```env |
82 | 92 | AI_PROVIDER=openai |
83 | 93 | OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx |
| @@ -91,6 +101,7 @@ AI Code Review uses advanced language models to automatically review your pull r | ||
91 | 101 | 1. Get API key from [console.anthropic.com](https://console.anthropic.com/) |
92 | 102 | |
93 | 103 | 2. Add to `.env`: |
104 | + | |
94 | 105 | ```env |
95 | 106 | AI_PROVIDER=anthropic |
96 | 107 | ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx |
| @@ -111,6 +122,7 @@ AZURE_OPENAI_DEPLOYMENT=your-deployment-name | ||
111 | 122 | **Option 4: Self-Hosted (Coming Soon)** |
112 | 123 | |
113 | 124 | OpenCodeHub will support local models via Ollama: |
125 | + | |
114 | 126 | ```env |
115 | 127 | AI_PROVIDER=ollama |
116 | 128 | OLLAMA_ENDPOINT=http://localhost:11434 |
| @@ -170,6 +182,7 @@ git push origin feature-branch | ||
170 | 182 | ``` |
171 | 183 | |
172 | 184 | You'll see: |
185 | + | |
173 | 186 | - ✅ **Status check**: "AI Review: In Progress" → "AI Review: Complete" |
174 | 187 | - 💬 **Inline comments**: On specific lines with issues |
175 | 188 | - 📊 **Summary comment**: Overall assessment |
| @@ -179,12 +192,14 @@ You'll see: | ||
179 | 192 | Request AI review on-demand: |
180 | 193 | |
181 | 194 | **Via Web UI:** |
195 | + | |
182 | 196 | 1. Open PR page |
183 | 197 | 2. Click **"Request AI Review"** button |
184 | 198 | 3. Wait ~30-60 seconds |
185 | 199 | 4. Review appears as comments |
186 | 200 | |
187 | 201 | **Via CLI:** |
202 | + | |
188 | 203 | ```bash |
189 | 204 | # Review specific PR |
190 | 205 | och review ai 125 |
| @@ -197,6 +212,7 @@ och review ai 125 --force | ||
197 | 212 | ``` |
198 | 213 | |
199 | 214 | **Via API:** |
215 | + | |
200 | 216 | ```bash |
201 | 217 | curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \ |
202 | 218 | -H "Authorization: Bearer $TOKEN" |
| @@ -210,30 +226,33 @@ curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \ | ||
210 | 226 | |
211 | 227 | AI assigns severity to each finding: |
212 | 228 | |
213 | -| Severity | Icon | Meaning | Example | | |
214 | -|----------|------|---------|---------| | |
215 | -| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key | | |
216 | -| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling | | |
217 | -| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification | | |
229 | +| Severity | Icon | Meaning | Example | | |
230 | +| --------------- | ---------- | -------------------------------------- | ----------------------------------------- | | |
231 | +| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key | | |
232 | +| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling | | |
233 | +| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification | | |
218 | 234 | |
219 | 235 | ### Example Review Comment |
220 | 236 | |
221 | -```markdown | |
237 | +````markdown | |
222 | 238 | 🔴 CRITICAL: SQL Injection Vulnerability |
223 | 239 | |
224 | -**File:** `src/api/users.ts` | |
240 | +**File:** `src/api/users.ts` | |
225 | 241 | **Line:** 45 |
226 | 242 | |
227 | -**Issue:** | |
243 | +**Issue:** | |
228 | 244 | User input is directly concatenated into SQL query without sanitization. |
229 | 245 | |
230 | 246 | **Current Code:** |
247 | + | |
231 | 248 | ```typescript |
232 | 249 | const query = `SELECT * FROM users WHERE email = '${userEmail}'`; |
233 | 250 | ``` |
251 | +```` | |
234 | 252 | |
235 | 253 | **Risk:** |
236 | 254 | An attacker could inject malicious SQL: |
255 | + | |
237 | 256 | ``` |
238 | 257 | userEmail = "' OR '1'='1" |
239 | 258 | → Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1' |
| @@ -242,6 +261,7 @@ userEmail = "' OR '1'='1" | ||
242 | 261 | |
243 | 262 | **Fix:** |
244 | 263 | Use parameterized queries or ORM: |
264 | + | |
245 | 265 | ```typescript |
246 | 266 | const query = `SELECT * FROM users WHERE email = ?`; |
247 | 267 | const results = await db.query(query, [userEmail]); |
| @@ -251,9 +271,11 @@ const user = await db.users.findOne({ email: userEmail }); | ||
251 | 271 | ``` |
252 | 272 | |
253 | 273 | **References:** |
274 | + | |
254 | 275 | - [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) |
255 | 276 | - [Node.js SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) |
256 | -``` | |
277 | + | |
278 | +```` | |
257 | 279 | |
258 | 280 | ### Summary Comment |
259 | 281 | |
| @@ -285,7 +307,7 @@ AI posts an overall summary: | ||
285 | 307 | - ✅ Comprehensive TypeScript types |
286 | 308 | |
287 | 309 | **Estimated fix time:** 2-3 hours |
288 | -``` | |
310 | +```` | |
289 | 311 | |
290 | 312 | --- |
291 | 313 | |
| @@ -313,7 +335,7 @@ exclude: | ||
313 | 335 | |
314 | 336 | # Focus areas (weighted 1-10) |
315 | 337 | focus: |
316 | - security: 10 # Highest priority | |
338 | + security: 10 # Highest priority | |
317 | 339 | performance: 8 |
318 | 340 | bugs: 8 |
319 | 341 | code_quality: 5 |
| @@ -323,7 +345,7 @@ focus: | ||
323 | 345 | report: |
324 | 346 | critical: always |
325 | 347 | warning: always |
326 | - info: on_request # Only if --verbose flag used | |
348 | + info: on_request # Only if --verbose flag used | |
327 | 349 | |
328 | 350 | # Custom rules |
329 | 351 | custom_rules: |
| @@ -331,7 +353,7 @@ custom_rules: | ||
331 | 353 | pattern: 'console\\.log\\(' |
332 | 354 | severity: warning |
333 | 355 | message: "Remove console.log before merging" |
334 | - | |
356 | + | |
335 | 357 | - name: "Require error handling" |
336 | 358 | pattern: 'await.*\\(.*\\)' |
337 | 359 | check: must_have_try_catch |
| @@ -344,7 +366,7 @@ instructions: > | ||
344 | 366 | 2. Database query efficiency |
345 | 367 | 3. Input validation |
346 | 368 | 4. Proper error handling |
347 | - | |
369 | + | |
348 | 370 | Our coding standards: |
349 | 371 | - TypeScript strict mode |
350 | 372 | - Functional programming preferred |
| @@ -386,36 +408,42 @@ AI automatically adapts to programming language: | ||
386 | 408 | AI review costs depend on provider and model: |
387 | 409 | |
388 | 410 | **OpenAI (GPT-4 Turbo):** |
411 | + | |
389 | 412 | - ~$0.01 per 1K tokens (input) |
390 | 413 | - ~$0.03 per 1K tokens (output) |
391 | 414 | - **Typical PR**: 500-2000 tokens = **$0.02-0.10 per review** |
392 | 415 | |
393 | 416 | **Anthropic (Claude 3 Opus):** |
417 | + | |
394 | 418 | - ~$0.015 per 1K tokens (input) |
395 | 419 | - ~$0.075 per 1K tokens (output) |
396 | 420 | - **Typical PR**: **$0.03-0.15 per review** |
397 | 421 | |
398 | 422 | **Example monthly cost:** |
423 | + | |
399 | 424 | - 50 PRs/month × $0.05/review = **$2.50/month** |
400 | 425 | - 500 PRs/month × $0.05/review = **$25/month** |
401 | 426 | |
402 | 427 | ### Cost Optimization |
403 | 428 | |
404 | 429 | **1. Limit file count:** |
430 | + | |
405 | 431 | ```yaml |
406 | -max_files_per_review: 10 # Review only 10 largest changed files | |
432 | +max_files_per_review: 10 # Review only 10 largest changed files | |
407 | 433 | ``` |
408 | 434 | |
409 | 435 | **2. Use cheaper models for simple changes:** |
436 | + | |
410 | 437 | ```yaml |
411 | 438 | rules: |
412 | 439 | - if: changed_files < 3 |
413 | - model: gpt-3.5-turbo # 10x cheaper | |
440 | + model: gpt-3.5-turbo # 10x cheaper | |
414 | 441 | - if: changed_files >= 3 |
415 | 442 | model: gpt-4-turbo |
416 | 443 | ``` |
417 | 444 | |
418 | 445 | **3. Skip test files:** |
446 | + | |
419 | 447 | ```yaml |
420 | 448 | exclude: |
421 | 449 | - "**/*.test.*" |
| @@ -423,13 +451,15 @@ exclude: | ||
423 | 451 | ``` |
424 | 452 | |
425 | 453 | **4. Review only on-request:** |
454 | + | |
426 | 455 | ```yaml |
427 | -trigger: manual # Don't auto-review every push | |
456 | +trigger: manual # Don't auto-review every push | |
428 | 457 | ``` |
429 | 458 | |
430 | 459 | **5. Use severity threshold:** |
460 | + | |
431 | 461 | ```yaml |
432 | -severity_threshold: warning # Skip "info" level comments | |
462 | +severity_threshold: warning # Skip "info" level comments | |
433 | 463 | ``` |
434 | 464 | |
435 | 465 | --- |
| @@ -439,6 +469,7 @@ severity_threshold: warning # Skip "info" level comments | ||
439 | 469 | ### 💡 When to Use AI Review |
440 | 470 | |
441 | 471 | **✅ Good Use Cases:** |
472 | + | |
442 | 473 | - Security-sensitive code (auth, payments) |
443 | 474 | - Complex algorithms |
444 | 475 | - Database queries |
| @@ -446,6 +477,7 @@ severity_threshold: warning # Skip "info" level comments | ||
446 | 477 | - Third-party integrations |
447 | 478 | |
448 | 479 | **❌ Less Useful:** |
480 | + | |
449 | 481 | - Generated code |
450 | 482 | - Configuration files |
451 | 483 | - Simple refactoring |
| @@ -454,19 +486,22 @@ severity_threshold: warning # Skip "info" level comments | ||
454 | 486 | ### 💡 Responding to AI Feedback |
455 | 487 | |
456 | 488 | **1. Don't auto-fix everything** |
489 | + | |
457 | 490 | - AI can be wrong |
458 | 491 | - Context matters |
459 | 492 | - Use judgment |
460 | 493 | |
461 | 494 | **2. Engage in discussion** |
495 | + | |
462 | 496 | ```markdown |
463 | -@ai-review That's a good catch, but in this case we're using a | |
497 | +@ai-review That's a good catch, but in this case we're using a | |
464 | 498 | trusted source and the data is already sanitized earlier in the pipeline. |
465 | 499 | |
466 | 500 | See line 23 where we validate input. |
467 | 501 | ``` |
468 | 502 | |
469 | 503 | **3. Track false positives** |
504 | + | |
470 | 505 | ```yaml |
471 | 506 | # .opencodehub/ai-review.yml |
472 | 507 | ignore: |
| @@ -486,6 +521,7 @@ AI complements, doesn't replace humans: | ||
486 | 521 | ``` |
487 | 522 | |
488 | 523 | This workflow: |
524 | + | |
489 | 525 | - Reduces human reviewer burden |
490 | 526 | - Catches more bugs |
491 | 527 | - Faster feedback cycles |
| @@ -499,6 +535,7 @@ This workflow: | ||
499 | 535 | **Cause:** Large PR (many files/lines). |
500 | 536 | |
501 | 537 | **Solution:** |
538 | + | |
502 | 539 | ```bash |
503 | 540 | # Check review status |
504 | 541 | och review status 125 |
| @@ -511,11 +548,13 @@ och review ai 125 --max-files 5 | ||
511 | 548 | ### "AI review failed" |
512 | 549 | |
513 | 550 | **Causes:** |
551 | + | |
514 | 552 | 1. API key invalid/expired |
515 | 553 | 2. Rate limit exceeded |
516 | 554 | 3. Timeout (very large PR) |
517 | 555 | |
518 | 556 | **Solutions:** |
557 | + | |
519 | 558 | ```bash |
520 | 559 | # 1. Verify API key |
521 | 560 | echo $OPENAI_API_KEY |
| @@ -535,9 +574,9 @@ och review ai 125 --files src/api/users.ts src/api/auth.ts | ||
535 | 574 | ```yaml |
536 | 575 | # .opencodehub/ai-review.yml |
537 | 576 | sensitivity: |
538 | - security: high # Strict | |
577 | + security: high # Strict | |
539 | 578 | performance: medium |
540 | - code_quality: low # Lenient | |
579 | + code_quality: low # Lenient | |
541 | 580 | ``` |
542 | 581 | |
543 | 582 | ### "Missing obvious bugs" |
| @@ -573,7 +612,7 @@ jobs: | ||
573 | 612 | run: och review ai ${{ github.event.pull_request.number }} |
574 | 613 | env: |
575 | 614 | OCH_TOKEN: ${{ secrets.OCH_TOKEN }} |
576 | - | |
615 | + | |
577 | 616 | - name: Check for critical issues |
578 | 617 | run: | |
579 | 618 | CRITICAL=$(och review status ${{ github.event.pull_request.number }} --json | jq '.critical_count') |
580 | 619 | |
▼docs/features/merge-queue.md
+60-31
| @@ -1,3 +1,8 @@ | ||
1 | +--- | |
2 | +title: "Legacy: Smart Merge Queue" | |
3 | +slug: "legacy/features/merge-queue" | |
4 | +--- | |
5 | + | |
1 | 6 | # Smart Merge Queue |
2 | 7 | |
3 | 8 | > Automate merging with stack-aware CI optimization and conflict resolution |
| @@ -21,6 +26,7 @@ The Smart Merge Queue automatically merges approved pull requests in the correct | ||
21 | 26 | ### The Problem |
22 | 27 | |
23 | 28 | Manual merging is: |
29 | + | |
24 | 30 | - ⏰ **Slow** - Wait for CI, merge, repeat |
25 | 31 | - 🐛 **Error-prone** - Merge conflicts, wrong order |
26 | 32 | - 💸 **Expensive** - Run CI for every PR individually |
| @@ -30,23 +36,25 @@ Manual merging is: | ||
30 | 36 | |
31 | 37 | Smart Merge Queue: |
32 | 38 | |
33 | -✅ **Automatic Merging** - Set it and forget it | |
34 | -✅ **Stack-Aware** - Merges in dependency order | |
35 | -✅ **CI Optimization** - Batches compatible PRs | |
36 | -✅ **Conflict Resolution** - Auto-rebases or alerts | |
39 | +✅ **Automatic Merging** - Set it and forget it | |
40 | +✅ **Stack-Aware** - Merges in dependency order | |
41 | +✅ **CI Optimization** - Batches compatible PRs | |
42 | +✅ **Conflict Resolution** - Auto-rebases or alerts | |
37 | 43 | ✅ **Priority Handling** - Hotfixes jump the queue |
38 | 44 | |
39 | 45 | ### Real-World Impact |
40 | 46 | |
41 | 47 | **Without Queue:** |
48 | + | |
42 | 49 | ``` |
43 | -PR #123 → Wait for CI (5 min) → Merge → | |
50 | +PR #123 → Wait for CI (5 min) → Merge → | |
44 | 51 | PR #124 → Rebase → Wait for CI (5 min) → Merge → |
45 | 52 | PR #125 → Rebase → Wait for CI (5 min) → Merge |
46 | 53 | Total: ~20 minutes, 3 CI runs |
47 | 54 | ``` |
48 | 55 | |
49 | 56 | **With Queue:** |
57 | + | |
50 | 58 | ``` |
51 | 59 | Add #123, #124, #125 to queue → |
52 | 60 | Queue detects dependencies → |
| @@ -105,6 +113,7 @@ All automatic! | ||
105 | 113 | ### CI Optimization |
106 | 114 | |
107 | 115 | **Batching:** |
116 | + | |
108 | 117 | ``` |
109 | 118 | PR #126: Fix typo in docs (no code changes) |
110 | 119 | PR #127: Update README |
| @@ -117,6 +126,7 @@ Queue batches #126 + #127 (docs only) | ||
117 | 126 | ``` |
118 | 127 | |
119 | 128 | **Smart Scheduling:** |
129 | + | |
120 | 130 | - Long CI jobs run in parallel |
121 | 131 | - Short jobs batch together |
122 | 132 | - Stack dependencies respected |
| @@ -170,6 +180,7 @@ curl -X POST https://git.yourcompany.com/api/queue \ | ||
170 | 180 | Navigate to `/queue` or click "Merge Queue" in repository navigation |
171 | 181 | |
172 | 182 | **CLI:** |
183 | + | |
173 | 184 | ```bash |
174 | 185 | # View entire queue |
175 | 186 | och queue list |
| @@ -186,6 +197,7 @@ och queue watch | ||
186 | 197 | ``` |
187 | 198 | |
188 | 199 | **API:** |
200 | + | |
189 | 201 | ```bash |
190 | 202 | curl https://git.yourcompany.com/api/queue \ |
191 | 203 | -H "Authorization: Bearer $TOKEN" |
| @@ -197,14 +209,15 @@ curl https://git.yourcompany.com/api/queue \ | ||
197 | 209 | |
198 | 210 | ### Priority Levels |
199 | 211 | |
200 | -| Priority | Use Case | Behavior | | |
201 | -|----------|----------|----------| | |
202 | -| 🔥 **Critical** | Production outage | Jumps to front, blocks other merges | | |
203 | -| ⚡ **High** | Security fix, hotfix | Merges before normal PRs | | |
204 | -| 📄 **Normal** | Regular features | Default, FIFO order | | |
205 | -| 🐌 **Low** | Refactoring, docs | Merges when queue is empty | | |
212 | +| Priority | Use Case | Behavior | | |
213 | +| --------------- | -------------------- | ----------------------------------- | | |
214 | +| 🔥 **Critical** | Production outage | Jumps to front, blocks other merges | | |
215 | +| ⚡ **High** | Security fix, hotfix | Merges before normal PRs | | |
216 | +| 📄 **Normal** | Regular features | Default, FIFO order | | |
217 | +| 🐌 **Low** | Refactoring, docs | Merges when queue is empty | | |
206 | 218 | |
207 | 219 | **Setting priority:** |
220 | + | |
208 | 221 | ```bash |
209 | 222 | # CLI |
210 | 223 | och queue add 126 --priority critical |
| @@ -245,31 +258,31 @@ och queue resume | ||
245 | 258 | |
246 | 259 | ```yaml |
247 | 260 | # Merge strategy |
248 | -merge_method: squash # or merge, rebase | |
261 | +merge_method: squash # or merge, rebase | |
249 | 262 | |
250 | 263 | # CI requirements |
251 | 264 | require_ci: true |
252 | 265 | required_checks: |
253 | 266 | - "test" |
254 | - - "lint" | |
267 | + - "lint" | |
255 | 268 | - "build" |
256 | 269 | |
257 | -# Approval requirements | |
270 | +# Approval requirements | |
258 | 271 | require_approvals: 1 |
259 | 272 | dismiss_stale_reviews: true |
260 | 273 | |
261 | 274 | # Batching |
262 | 275 | enable_batching: true |
263 | -batch_window: 5m # Wait 5 min to batch PRs | |
264 | -max_batch_size: 3 # Max 3 PRs per batch | |
276 | +batch_window: 5m # Wait 5 min to batch PRs | |
277 | +max_batch_size: 3 # Max 3 PRs per batch | |
265 | 278 | |
266 | 279 | # Rebasing |
267 | -auto_rebase: true # Auto-rebase on conflicts | |
268 | -rebase_strategy: merge # or rebase, squash | |
280 | +auto_rebase: true # Auto-rebase on conflicts | |
281 | +rebase_strategy: merge # or rebase, squash | |
269 | 282 | |
270 | 283 | # Priorities |
271 | -allow_priority_override: true # Let users set priority | |
272 | -max_queue_size: 50 # Max PRs in queue | |
284 | +allow_priority_override: true # Let users set priority | |
285 | +max_queue_size: 50 # Max PRs in queue | |
273 | 286 | ``` |
274 | 287 | |
275 | 288 | ### Branch Protection Rules |
| @@ -285,7 +298,7 @@ branches: | ||
285 | 298 | - lint |
286 | 299 | required_approvals: 1 |
287 | 300 | allow_force_push: false |
288 | - allow_queue_bypass: false # Even admins use queue! | |
301 | + allow_queue_bypass: false # Even admins use queue! | |
289 | 302 | ``` |
290 | 303 | |
291 | 304 | ### Webhook Notifications |
| @@ -303,6 +316,7 @@ curl -X POST https://git.yourcompany.com/api/webhooks \ | ||
303 | 316 | ``` |
304 | 317 | |
305 | 318 | **Payload:** |
319 | + | |
306 | 320 | ```json |
307 | 321 | { |
308 | 322 | "event": "merge_queue.merged", |
| @@ -315,7 +329,7 @@ curl -X POST https://git.yourcompany.com/api/webhooks \ | ||
315 | 329 | "position": 1, |
316 | 330 | "wait_time_seconds": 180 |
317 | 331 | }, |
318 | - "timestamp": "2024-01-01T12:00:00Z" | |
332 | + "timestamp": "2024-01-01T12:00:00Z" | |
319 | 333 | } |
320 | 334 | ``` |
321 | 335 | |
| @@ -326,30 +340,34 @@ curl -X POST https://git.yourcompany.com/api/webhooks \ | ||
326 | 340 | ### 💡 When to Use the Queue |
327 | 341 | |
328 | 342 | **✅ Always use for:** |
343 | + | |
329 | 344 | - Feature branches → main |
330 | 345 | - Release branches |
331 | 346 | - Any PR affecting production |
332 | 347 | |
333 | 348 | **❌ Don't use for:** |
349 | + | |
334 | 350 | - Draft PRs |
335 | -- WIP branches | |
351 | +- WIP branches | |
336 | 352 | - Experimental features |
337 | 353 | |
338 | 354 | ### 💡 Optimizing Queue Performance |
339 | 355 | |
340 | 356 | **1. Fast CI:** |
357 | + | |
341 | 358 | ```yaml |
342 | 359 | # Run quick tests in queue |
343 | 360 | queue_ci: |
344 | - - unit_tests # 2 min | |
345 | - | |
361 | + - unit_tests # 2 min | |
362 | + | |
346 | 363 | # Run slow tests post-merge |
347 | 364 | post_merge_ci: |
348 | - - integration_tests # 10 min | |
349 | - - e2e_tests # 15 min | |
365 | + - integration_tests # 10 min | |
366 | + - e2e_tests # 15 min | |
350 | 367 | ``` |
351 | 368 | |
352 | 369 | **2. Parallel CIs:** |
370 | + | |
353 | 371 | ```yaml |
354 | 372 | # Run multiple jobs in parallel |
355 | 373 | ci: |
| @@ -360,6 +378,7 @@ ci: | ||
360 | 378 | ``` |
361 | 379 | |
362 | 380 | **3. Smart batching:** |
381 | + | |
363 | 382 | ```bash |
364 | 383 | # Group related PRs |
365 | 384 | och queue add 123 124 125 --batch-id feature-auth |
| @@ -370,6 +389,7 @@ och queue add 123 124 125 --batch-id feature-auth | ||
370 | 389 | ### 💡 Handling Conflicts |
371 | 390 | |
372 | 391 | **Automatic resolution:** |
392 | + | |
373 | 393 | ```yaml |
374 | 394 | auto_rebase: true |
375 | 395 | rebase_strategy: merge # Creates merge commit |
| @@ -379,6 +399,7 @@ rebase_strategy: rebase # Cleaner history | ||
379 | 399 | ``` |
380 | 400 | |
381 | 401 | **Manual resolution:** |
402 | + | |
382 | 403 | ```bash |
383 | 404 | # If auto-rebase fails, you'll get notification |
384 | 405 | # Fix locally: |
| @@ -394,18 +415,20 @@ git push --force-with-lease | ||
394 | 415 | ### 💡 Queue Monitoring |
395 | 416 | |
396 | 417 | **Set up alerts:** |
418 | + | |
397 | 419 | ```yaml |
398 | 420 | # Slack notification if queue stalled |
399 | 421 | alerts: |
400 | 422 | - type: queue_stalled |
401 | 423 | threshold: 30m |
402 | 424 | channel: "#eng-deployments" |
403 | - | |
425 | + | |
404 | 426 | - type: merge_failed |
405 | 427 | channel: "#eng-alerts" |
406 | 428 | ``` |
407 | 429 | |
408 | 430 | **Dashboard example:** |
431 | + | |
409 | 432 | ```bash |
410 | 433 | # View queue health |
411 | 434 | och queue stats |
| @@ -425,12 +448,14 @@ och queue stats | ||
425 | 448 | ### "PR stuck in queue" |
426 | 449 | |
427 | 450 | **Causes:** |
451 | + | |
428 | 452 | 1. CI failing |
429 | 453 | 2. Waiting for approval |
430 | 454 | 3. Merge conflict |
431 | 455 | 4. Dependency not merged yet |
432 | 456 | |
433 | 457 | **Solutions:** |
458 | + | |
434 | 459 | ```bash |
435 | 460 | # Check status |
436 | 461 | och queue status 125 |
| @@ -449,6 +474,7 @@ och queue add 125 | ||
449 | 474 | **Cause:** Complex merge conflicts. |
450 | 475 | |
451 | 476 | **Solution:** |
477 | + | |
452 | 478 | ```bash |
453 | 479 | # Remove from queue |
454 | 480 | och queue remove 125 |
| @@ -466,11 +492,13 @@ och queue add 125 | ||
466 | 492 | ### "Queue not processing" |
467 | 493 | |
468 | 494 | **Causes:** |
495 | + | |
469 | 496 | 1. Queue paused |
470 | 497 | 2. CI service down |
471 | 498 | 3. Rate limit reached |
472 | 499 | |
473 | 500 | **Solutions:** |
501 | + | |
474 | 502 | ```bash |
475 | 503 | # Check queue status |
476 | 504 | och queue info |
| @@ -490,6 +518,7 @@ och admin logs queue | ||
490 | 518 | **Cause:** Dependencies not detected. |
491 | 519 | |
492 | 520 | **Solution:** |
521 | + | |
493 | 522 | ```bash |
494 | 523 | # Manually set dependency |
495 | 524 | och pr edit 125 --depends-on 124 |
| @@ -510,7 +539,7 @@ For high-velocity teams: | ||
510 | 539 | merge_train: |
511 | 540 | enabled: true |
512 | 541 | max_concurrent: 5 |
513 | - | |
542 | + | |
514 | 543 | # Creates temporary integration branches |
515 | 544 | # Runs CI in parallel |
516 | 545 | # Merges in rapid succession |
| @@ -538,11 +567,11 @@ module.exports = { | ||
538 | 567 | // Update JIRA ticket |
539 | 568 | // Trigger deployment |
540 | 569 | }, |
541 | - | |
570 | + | |
542 | 571 | afterMerge(pr) { |
543 | 572 | // Create release notes |
544 | 573 | // Update documentation |
545 | - } | |
574 | + }, | |
546 | 575 | }; |
547 | 576 | ``` |
548 | 577 | |
549 | 578 | |
▼docs/guides/storage-adapters.md
+11-0
| @@ -1,3 +1,8 @@ | ||
1 | +--- | |
2 | +title: "Legacy: Storage Adapters" | |
3 | +slug: "legacy/guides/storage-adapters" | |
4 | +--- | |
5 | + | |
1 | 6 | # Storage Adapters |
2 | 7 | |
3 | 8 | OpenCodeHub supports a pluggable storage system, allowing you to store repository data (git objects, LFS files, artifacts) on various backends. |
| @@ -16,6 +21,7 @@ OpenCodeHub supports a pluggable storage system, allowing you to store repositor | ||
16 | 21 | Data is stored in the `data/` directory relative to the application root. |
17 | 22 | |
18 | 23 | **Configuration:** |
24 | + | |
19 | 25 | ```env |
20 | 26 | STORAGE_TYPE=local |
21 | 27 | STORAGE_PATH=./data/storage # Optional, default is ./data |
| @@ -28,6 +34,7 @@ STORAGE_PATH=./data/storage # Optional, default is ./data | ||
28 | 34 | Store data in any S3-compatible bucket. This is recommended for production scalablity. |
29 | 35 | |
30 | 36 | **Configuration:** |
37 | + | |
31 | 38 | ```env |
32 | 39 | STORAGE_TYPE=s3 |
33 | 40 | STORAGE_BUCKET=my-opencodehub-bucket |
| @@ -40,6 +47,7 @@ S3_SECRET_KEY=your-secret-key | ||
40 | 47 | ### Examples |
41 | 48 | |
42 | 49 | **MinIO (Self-hosted):** |
50 | + | |
43 | 51 | ```env |
44 | 52 | STORAGE_ENDPOINT=http://minio:9000 |
45 | 53 | STORAGE_REGION=us-east-1 |
| @@ -47,6 +55,7 @@ S3_FORCE_PATH_STYLE=true | ||
47 | 55 | ``` |
48 | 56 | |
49 | 57 | **Cloudflare R2:** |
58 | + | |
50 | 59 | ```env |
51 | 60 | STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com |
52 | 61 | STORAGE_REGION=auto |
| @@ -75,6 +84,7 @@ GOOGLE_FOLDER_ID=your-folder-id | ||
75 | 84 | ``` |
76 | 85 | |
77 | 86 | **How to get Credentials:** |
87 | + | |
78 | 88 | 1. Go to [Google Cloud Console](https://console.cloud.google.com). |
79 | 89 | 2. Create a project -> Enable **Google Drive API**. |
80 | 90 | 3. Create **OAuth Client ID**. |
| @@ -85,6 +95,7 @@ GOOGLE_FOLDER_ID=your-folder-id | ||
85 | 95 | ## 🔷 Azure Blob Storage |
86 | 96 | |
87 | 97 | **Configuration:** |
98 | + | |
88 | 99 | ```env |
89 | 100 | STORAGE_TYPE=azure |
90 | 101 | AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=... |
91 | 102 | |
▼docs/index.md
+31-28
| @@ -1,7 +1,11 @@ | ||
1 | 1 | # OpenCodeHub Documentation |
2 | 2 | |
3 | 3 | <p align="center"> |
4 | - <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo.svg" alt="OpenCodeHub Logo" width="300"> | |
4 | + <picture> | |
5 | + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-dark.png"> | |
6 | + <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png"> | |
7 | + <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png" alt="OpenCodeHub Logo" width="400"> | |
8 | + </picture> | |
5 | 9 | </p> |
6 | 10 | |
7 | 11 | > Everything you need to know about OpenCodeHub - from getting started to advanced features |
| @@ -17,17 +21,17 @@ Welcome to OpenCodeHub's comprehensive documentation. Whether you're new to the | ||
17 | 21 | <h3>🎯 Getting Started</h3> |
18 | 22 | <p>Install and set up OpenCodeHub in 5 minutes</p> |
19 | 23 | </a> |
20 | - | |
24 | + | |
21 | 25 | <a href="#features"> |
22 | 26 | <h3>✨ Features</h3> |
23 | 27 | <p>Learn about stacked PRs, AI review, and more</p> |
24 | 28 | </a> |
25 | - | |
29 | + | |
26 | 30 | <a href="#tutorials"> |
27 | 31 | <h3>📚 Tutorials</h3> |
28 | 32 | <p>Hands-on guides to master OpenCodeHub</p> |
29 | 33 | </a> |
30 | - | |
34 | + | |
31 | 35 | <a href="#api"> |
32 | 36 | <h3>🔌 API Reference</h3> |
33 | 37 | <p>Integrate with OpenCodeHub programmatically</p> |
| @@ -50,35 +54,35 @@ New to OpenCodeHub? Start here! | ||
50 | 54 | |
51 | 55 | Learn about OpenCodeHub's powerful features: |
52 | 56 | |
53 | -- **[Stacked Pull Requests](features/stacked-prs.md)** ⭐ | |
57 | +- **[Stacked Pull Requests](features/stacked-prs.md)** ⭐ | |
54 | 58 | Break large changes into reviewable stacks. Ship code faster. |
55 | - | |
56 | -- **[AI Code Review](features/ai-review.md)** 🤖 | |
59 | + | |
60 | +- **[AI Code Review](features/ai-review.md)** 🤖 | |
57 | 61 | Get instant feedback from GPT-4 or Claude. Catch bugs automatically. |
58 | - | |
59 | -- **[Smart Merge Queue](features/merge-queue.md)** 🔀 | |
62 | + | |
63 | +- **[Smart Merge Queue](features/merge-queue.md)** 🔀 | |
60 | 64 | Automate merging with stack-aware CI optimization. |
61 | - | |
62 | -- **[Developer Metrics](features/developer-metrics.md)** 📊 | |
65 | + | |
66 | +- **[Developer Metrics](features/developer-metrics.md)** 📊 | |
63 | 67 | Track velocity, review efficiency, and team performance. |
64 | - | |
65 | -- **[Slack Integration](features/slack-integration.md)** 💬 | |
68 | + | |
69 | +- **[Slack Integration](features/slack-integration.md)** 💬 | |
66 | 70 | Get actionable notifications in Slack. Review without leaving chat. |
67 | - | |
68 | -- **[CI/CD Pipelines](features/ci-cd.md)** ⚙️ | |
71 | + | |
72 | +- **[CI/CD Pipelines](features/ci-cd.md)** ⚙️ | |
69 | 73 | GitHub Actions-compatible workflows. Build, test, deploy. |
70 | 74 | |
71 | 75 | ### Hands-On Tutorials |
72 | 76 | |
73 | 77 | Step-by-step guides to master OpenCodeHub: |
74 | 78 | |
75 | -- **[Your First Stacked PR](tutorials/your-first-stack.md)** 🎓 | |
79 | +- **[Your First Stacked PR](tutorials/your-first-stack.md)** 🎓 | |
76 | 80 | 15-minute tutorial: Create your first stack of PRs |
77 | - | |
78 | -- **[AI-Powered Code Reviews](tutorials/ai-powered-reviews.md)** | |
81 | + | |
82 | +- **[AI-Powered Code Reviews](tutorials/ai-powered-reviews.md)** | |
79 | 83 | Set up and use AI review for your projects |
80 | - | |
81 | -- **[Automated Deployment](tutorials/automated-deployment.md)** | |
84 | + | |
85 | +- **[Automated Deployment](tutorials/automated-deployment.md)** | |
82 | 86 | Build a complete CI/CD pipeline from scratch |
83 | 87 | |
84 | 88 | ### Guides |
| @@ -122,7 +126,6 @@ Contributing to OpenCodeHub: | ||
122 | 126 | - **[Glossary](reference/glossary.md)** - Project terminology |
123 | 127 | - **[Troubleshooting](support/troubleshooting.md)** - Fix common issues |
124 | 128 | |
125 | - | |
126 | 129 | --- |
127 | 130 | |
128 | 131 | ## 🎯 Popular Topics |
| @@ -267,13 +270,13 @@ We welcome contributions! | ||
267 | 270 | |
268 | 271 | ## 🌟 Why OpenCodeHub? |
269 | 272 | |
270 | -| Feature | GitHub | GitLab | OpenCodeHub | | |
271 | -|---------|--------|--------|-------------| | |
272 | -| Stacked PRs | ❌ | ❌ | ✅ Native support | | |
273 | -| AI Code Review | ⚠️ Copilot only | ❌ | ✅ GPT-4 & Claude | | |
274 | -| Smart Merge Queue | ⚠️ Basic | ⚠️ Basic | ✅ Stack-aware | | |
275 | -| Self-Hosted | ❌ | ✅ Complex | ✅ Simple | | |
276 | -| Cost | $$$ | $$$ | Free | | |
273 | +| Feature | GitHub | GitLab | OpenCodeHub | | |
274 | +| ----------------- | --------------- | ---------- | ----------------- | | |
275 | +| Stacked PRs | ❌ | ❌ | ✅ Native support | | |
276 | +| AI Code Review | ⚠️ Copilot only | ❌ | ✅ GPT-4 & Claude | | |
277 | +| Smart Merge Queue | ⚠️ Basic | ⚠️ Basic | ✅ Stack-aware | | |
278 | +| Self-Hosted | ❌ | ✅ Complex | ✅ Simple | | |
279 | +| Cost | $$$ | $$$ | Free | | |
277 | 280 | |
278 | 281 | --- |
279 | 282 | |
280 | 283 | |
▼docs/reference/cli-commands.md
+25-0
| @@ -1,3 +1,8 @@ | ||
1 | +--- | |
2 | +title: "Legacy: CLI Command Reference" | |
3 | +slug: "legacy/reference/cli-commands" | |
4 | +--- | |
5 | + | |
1 | 6 | # CLI Command Reference |
2 | 7 | |
3 | 8 | The `opencodehub-cli` (or `och`) is your primary tool for interacting with OpenCodeHub from the terminal. |
| @@ -21,16 +26,20 @@ npm install -g opencodehub-cli | ||
21 | 26 | Manage authentication state. |
22 | 27 | |
23 | 28 | ### `och auth login` |
29 | + | |
24 | 30 | Authenticates with an OpenCodeHub instance. |
31 | + | |
25 | 32 | - **Interactive Mode**: Prompts for URL and credentials. |
26 | 33 | - **Flags**: |
27 | 34 | - `--url <url>`: OCH instance URL. |
28 | 35 | - `--token <token>`: Bypass browser login with existing token. |
29 | 36 | |
30 | 37 | ### `och auth status` |
38 | + | |
31 | 39 | Check current login status and user. |
32 | 40 | |
33 | 41 | ### `och auth logout` |
42 | + | |
34 | 43 | Clear stored credentials. |
35 | 44 | |
36 | 45 | --- |
| @@ -40,19 +49,25 @@ Clear stored credentials. | ||
40 | 49 | Manage repositories. |
41 | 50 | |
42 | 51 | ### `och clone <repo>` |
52 | + | |
43 | 53 | Clone a repository. |
54 | + | |
44 | 55 | - **Example**: `och clone swadhin/my-project` |
45 | 56 | - **Behavior**: Automatically sets up remote `origin`. |
46 | 57 | |
47 | 58 | ### `och create [name]` |
59 | + | |
48 | 60 | Create a new repository. |
61 | + | |
49 | 62 | - **Flags**: |
50 | 63 | - `--public`: Make repository public. |
51 | 64 | - `--private`: Make repository private. |
52 | 65 | - `--desc <text>`: Description. |
53 | 66 | |
54 | 67 | ### `och push` |
68 | + | |
55 | 69 | Push changes to the remote. |
70 | + | |
56 | 71 | - **Features**: Shows a progress bar and PR link on success. |
57 | 72 | |
58 | 73 | --- |
| @@ -62,21 +77,28 @@ Push changes to the remote. | ||
62 | 77 | Manage Stacked Pull Requests. |
63 | 78 | |
64 | 79 | ### `och stack create <branch-name>` |
80 | + | |
65 | 81 | Create a new branch stacked on top of the current one. |
82 | + | |
66 | 83 | - **Example**: `och stack create feature-b` (when on `feature-a`). |
67 | 84 | |
68 | 85 | ### `och stack submit` |
86 | + | |
69 | 87 | Submit all branches in the current stack as Pull Requests. |
88 | + | |
70 | 89 | - **Behavior**: |
71 | 90 | - Pushes all branches. |
72 | 91 | - Creates/Updates PRs. |
73 | 92 | - Sets correct base branches (`feature-a` -> `main`, `feature-b` -> `feature-a`). |
74 | 93 | |
75 | 94 | ### `och stack sync` |
95 | + | |
76 | 96 | Rebase the entire stack. |
97 | + | |
77 | 98 | - **Use Case**: When `main` has updated or a lower PR changed. |
78 | 99 | |
79 | 100 | ### `och stack view` |
101 | + | |
80 | 102 | Visualize the current stack in ASCII tree format. |
81 | 103 | |
82 | 104 | --- |
| @@ -86,10 +108,13 @@ Visualize the current stack in ASCII tree format. | ||
86 | 108 | Manage Pull Requests. |
87 | 109 | |
88 | 110 | ### `och pr list` |
111 | + | |
89 | 112 | List open PRs for current repo. |
90 | 113 | |
91 | 114 | ### `och pr checkout <number>` |
115 | + | |
92 | 116 | Checkout a PR by its number. |
93 | 117 | |
94 | 118 | ### `och pr status [number]` |
119 | + | |
95 | 120 | Show CI status and review state of a PR. |
96 | 121 | |
▼docs/tutorials/ai-powered-reviews.md
+7-0
| @@ -1,3 +1,8 @@ | ||
1 | +--- | |
2 | +title: "Legacy: AI-Powered Code Reviews" | |
3 | +slug: "legacy/tutorials/ai-powered-reviews" | |
4 | +--- | |
5 | + | |
1 | 6 | # Tutorial: AI-Powered Code Reviews |
2 | 7 | |
3 | 8 | OpenCodeHub integrates with LLMs (GPT-4, Claude) to provide automated, intelligent code reviews on every Pull Request. This tutorial guides you through setting it up. |
| @@ -12,12 +17,14 @@ OpenCodeHub integrates with LLMs (GPT-4, Claude) to provide automated, intellige | ||
12 | 17 | As an administrator, you need to configure the AI provider in your environment variables. |
13 | 18 | |
14 | 19 | **For OpenAI:** |
20 | + | |
15 | 21 | ```env |
16 | 22 | AI_PROVIDER=openai |
17 | 23 | OPENAI_API_KEY=sk-proj-... |
18 | 24 | ``` |
19 | 25 | |
20 | 26 | **For Anthropic:** |
27 | + | |
21 | 28 | ```env |
22 | 29 | AI_PROVIDER=anthropic |
23 | 30 | ANTHROPIC_API_KEY=sk-ant-... |
24 | 31 | |
▼docs/tutorials/your-first-stack.md
+39-27
| @@ -1,3 +1,8 @@ | ||
1 | +--- | |
2 | +title: "Legacy: Your First Stacked PR" | |
3 | +slug: "legacy/tutorials/your-first-stack" | |
4 | +--- | |
5 | + | |
1 | 6 | # Tutorial: Your First Stacked PR |
2 | 7 | |
3 | 8 | > Learn how to split a large feature into reviewable chunks in 15 minutes |
| @@ -11,7 +16,7 @@ This hands-on tutorial teaches you how to use Stacked Pull Requests to break dow | ||
11 | 16 | - Navigate the review process |
12 | 17 | - Merge the stack successfully |
13 | 18 | |
14 | -**Time:** 15-20 minutes | |
19 | +**Time:** 15-20 minutes | |
15 | 20 | **Difficulty:** Beginner |
16 | 21 | |
17 | 22 | --- |
| @@ -27,6 +32,7 @@ Before starting, make sure you have: | ||
27 | 32 | - [ ] Basic Git knowledge (branch, commit, push) |
28 | 33 | |
29 | 34 | **First time with OCH CLI?** |
35 | + | |
30 | 36 | ```bash |
31 | 37 | # Install |
32 | 38 | npm install -g opencodehub-cli |
| @@ -124,7 +130,7 @@ och stack submit | ||
124 | 130 | # Output: |
125 | 131 | # ✓ Pushing stack/auth-database |
126 | 132 | # ✓ Creating pull request |
127 | -# | |
133 | +# | |
128 | 134 | # PR #123 created: feat: add users table migration |
129 | 135 | # https://git.yourcompany.com/yourorg/your-repo/pulls/123 |
130 | 136 | ``` |
| @@ -132,6 +138,7 @@ och stack submit | ||
132 | 138 | **2.5. Verify in web UI:** |
133 | 139 | |
134 | 140 | Open the PR link. You should see: |
141 | + | |
135 | 142 | - Title: "feat: add users table migration" |
136 | 143 | - Files changed: `db/migrations/001_create_users_table.sql` |
137 | 144 | - Status: Draft or Ready for Review |
| @@ -162,34 +169,34 @@ import { db } from '../db'; | ||
162 | 169 | export class AuthService { |
163 | 170 | async register(email: string, password: string) { |
164 | 171 | const hash = await bcrypt.hash(password, 10); |
165 | - | |
172 | + | |
166 | 173 | const user = await db.users.create({ |
167 | 174 | email, |
168 | 175 | password_hash: hash |
169 | 176 | }); |
170 | - | |
177 | + | |
171 | 178 | return { id: user.id, email: user.email }; |
172 | 179 | } |
173 | - | |
180 | + | |
174 | 181 | async login(email: string, password: string) { |
175 | 182 | const user = await db.users.findByEmail(email); |
176 | - | |
183 | + | |
177 | 184 | if (!user) { |
178 | 185 | throw new Error('Invalid credentials'); |
179 | 186 | } |
180 | - | |
187 | + | |
181 | 188 | const valid = await bcrypt.compare(password, user.password_hash); |
182 | - | |
189 | + | |
183 | 190 | if (!valid) { |
184 | 191 | throw new Error('Invalid credentials'); |
185 | 192 | } |
186 | - | |
193 | + | |
187 | 194 | const token = jwt.sign( |
188 | 195 | { userId: user.id, email: user.email }, |
189 | 196 | process.env.JWT_SECRET!, |
190 | 197 | { expiresIn: '7d' } |
191 | 198 | ); |
192 | - | |
199 | + | |
193 | 200 | return { token, user: { id: user.id, email: user.email } }; |
194 | 201 | } |
195 | 202 | } |
| @@ -264,14 +271,14 @@ const authService = new AuthService(); | ||
264 | 271 | router.post('/register', async (req, res) => { |
265 | 272 | try { |
266 | 273 | const { email, password } = req.body; |
267 | - | |
274 | + | |
268 | 275 | // Validation |
269 | 276 | if (!email || !password) { |
270 | 277 | return res.status(400).json({ error: 'Email and password required' }); |
271 | 278 | } |
272 | - | |
279 | + | |
273 | 280 | const user = await authService.register(email, password); |
274 | - | |
281 | + | |
275 | 282 | res.status(201).json({ user }); |
276 | 283 | } catch (error) { |
277 | 284 | res.status(500).json({ error: error.message }); |
| @@ -281,9 +288,9 @@ router.post('/register', async (req, res) => { | ||
281 | 288 | router.post('/login', async (req, res) => { |
282 | 289 | try { |
283 | 290 | const { email, password } = req.body; |
284 | - | |
291 | + | |
285 | 292 | const result = await authService.login(email, password); |
286 | - | |
293 | + | |
287 | 294 | res.json(result); |
288 | 295 | } catch (error) { |
289 | 296 | res.status(401).json({ error: error.message }); |
| @@ -311,7 +318,7 @@ och stack submit | ||
311 | 318 | |
312 | 319 | # Output: |
313 | 320 | # ✓ Pushing stack/auth-api |
314 | -# ✓ Creating pull request | |
321 | +# ✓ Creating pull request | |
315 | 322 | # ✓ Linking to base PR #124 |
316 | 323 | # |
317 | 324 | # PR #125 created: feat: add authentication API endpoints |
| @@ -353,6 +360,7 @@ curl -X PATCH https://git.yourcompany.com/api/prs/123 \ | ||
353 | 360 | **5.2. Request reviews:** |
354 | 361 | |
355 | 362 | In the web UI for PR #123: |
363 | + | |
356 | 364 | 1. Click "Reviewers" → Select team members |
357 | 365 | 2. Add comment: "This is the first PR in a stack of 3. Reviewing these in order would be helpful!" |
358 | 366 | |
| @@ -370,14 +378,14 @@ import { db } from '../db'; | ||
370 | 378 | test('creates users table', async () => { |
371 | 379 | // Run migration |
372 | 380 | await db.migrate(); |
373 | - | |
381 | + | |
374 | 382 | // Verify table exists |
375 | 383 | const tables = await db.raw(` |
376 | - SELECT table_name | |
377 | - FROM information_schema.tables | |
384 | + SELECT table_name | |
385 | + FROM information_schema.tables | |
378 | 386 | WHERE table_name = 'users' |
379 | 387 | `); |
380 | - | |
388 | + | |
381 | 389 | expect(tables.length).toBe(1); |
382 | 390 | }); |
383 | 391 | EOF |
| @@ -440,7 +448,7 @@ och queue watch | ||
440 | 448 | # 🔄 Rebasing PR #124... |
441 | 449 | # 🏃 Running CI for #124... |
442 | 450 | # |
443 | -# [2 min later] | |
451 | +# [2 min later] | |
444 | 452 | # ✅ PR #124 merged! |
445 | 453 | # 🔄 Rebasing PR #125... |
446 | 454 | ``` |
| @@ -456,7 +464,7 @@ git log --oneline -3 | ||
456 | 464 | |
457 | 465 | # Output: |
458 | 466 | # abc123 feat: add authentication API endpoints |
459 | -# def456 feat: implement authentication service | |
467 | +# def456 feat: implement authentication service | |
460 | 468 | # ghi789 feat: add users table migration |
461 | 469 | ``` |
462 | 470 | |
| @@ -466,22 +474,24 @@ All 3 PRs merged! Your auth system is live! | ||
466 | 474 | |
467 | 475 | ## What You've Learned |
468 | 476 | |
469 | -✅ How to create a stack of dependent PRs | |
470 | -✅ Using `och stack create` for easy stacking | |
471 | -✅ Submitting stacks with `och stack submit` | |
472 | -✅ Viewing stack visualization | |
473 | -✅ Using the merge queue for automatic merging | |
477 | +✅ How to create a stack of dependent PRs | |
478 | +✅ Using `och stack create` for easy stacking | |
479 | +✅ Submitting stacks with `och stack submit` | |
480 | +✅ Viewing stack visualization | |
481 | +✅ Using the merge queue for automatic merging | |
474 | 482 | ✅ How auto-rebasing works |
475 | 483 | |
476 | 484 | ## Key Takeaways |
477 | 485 | |
478 | 486 | **Stacked PRs are better because:** |
487 | + | |
479 | 488 | - Each PR was ~50-100 lines (easy to review) |
480 | 489 | - Reviews happened in parallel (faster) |
481 | 490 | - Each merge was low-risk (incremental) |
482 | 491 | - Clear history (logical progression) |
483 | 492 | |
484 | 493 | **vs. One Large PR:** |
494 | + | |
485 | 495 | - Would be ~200-300 lines |
486 | 496 | - Single review bottleneck |
487 | 497 | - High-risk merge |
| @@ -494,6 +504,7 @@ All 3 PRs merged! Your auth system is live! | ||
494 | 504 | ### Practice More |
495 | 505 | |
496 | 506 | Try stacking your next feature: |
507 | + | |
497 | 508 | - 🎨 Frontend feature → Split UI, logic, styles |
498 | 509 | - 🔧 Backend feature → Split models, services, controllers |
499 | 510 | - 📊 Data pipeline → Split ingestion, processing, output |
| @@ -501,6 +512,7 @@ Try stacking your next feature: | ||
501 | 512 | ### Advanced Techniques |
502 | 513 | |
503 | 514 | Learn more about: |
515 | + | |
504 | 516 | - [Parallel stacks](../features/stacked-prs.md#parallel-stacks) |
505 | 517 | - [Stack reordering](../features/stacked-prs.md#reordering) |
506 | 518 | - [AI code review](../features/ai-review.md) on stacks |
507 | 519 | |
▼public/docs/index.html
+447-305
| @@ -1,335 +1,477 @@ | ||
1 | -<!DOCTYPE html> | |
1 | +<!doctype html> | |
2 | 2 | <html lang="en"> |
3 | - | |
4 | -<head> | |
5 | - <meta charset="UTF-8"> | |
6 | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
3 | + <head> | |
4 | + <meta charset="UTF-8" /> | |
5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
7 | 6 | <title>OpenCodeHub Documentation</title> |
8 | - <meta name="description" | |
9 | - content="Complete documentation for OpenCodeHub - Self-hosted Git platform with stacked PRs, AI review, and CI/CD"> | |
10 | - <link rel="icon" type="image/svg+xml" href="../public/logo.svg"> | |
11 | - <link rel="preconnect" href="https://fonts.googleapis.com"> | |
12 | - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
7 | + <meta | |
8 | + name="description" | |
9 | + content="Complete documentation for OpenCodeHub - Self-hosted Git platform with stacked PRs, AI review, and CI/CD" | |
10 | + /> | |
11 | + <link rel="icon" type="image/svg+xml" href="../public/logo.svg" /> | |
12 | + <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
13 | + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
13 | 14 | <link |
14 | - href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" | |
15 | - rel="stylesheet"> | |
16 | - <link rel="stylesheet" href="styles.css"> | |
17 | -</head> | |
15 | + href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" | |
16 | + rel="stylesheet" | |
17 | + /> | |
18 | + <link rel="stylesheet" href="styles.css" /> | |
19 | + </head> | |
18 | 20 | |
19 | -<body> | |
21 | + <body> | |
20 | 22 | <!-- Header --> |
21 | 23 | <header class="header"> |
22 | - <a href="index.html" class="header-logo"> | |
23 | - <img src="../public/logo.svg" alt="OpenCodeHub" height="40"> | |
24 | + <a href="index.html" class="header-logo"> | |
25 | + <img src="../public/logo.svg" alt="OpenCodeHub" height="40" /> | |
26 | + </a> | |
27 | + <nav class="header-nav"> | |
28 | + <a href="getting-started.html">Getting Started</a> | |
29 | + <a href="deployment.html">Deployment</a> | |
30 | + <a href="cli.html">CLI</a> | |
31 | + <a href="api.html">API</a> | |
32 | + <a | |
33 | + href="https://github.com/swadhinbiswas/OpencodeHub" | |
34 | + class="btn-github" | |
35 | + target="_blank" | |
36 | + > | |
37 | + <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"> | |
38 | + <path | |
39 | + d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" | |
40 | + /> | |
41 | + </svg> | |
42 | + GitHub | |
24 | 43 | </a> |
25 | - <nav class="header-nav"> | |
26 | - <a href="getting-started.html">Getting Started</a> | |
27 | - <a href="deployment.html">Deployment</a> | |
28 | - <a href="cli.html">CLI</a> | |
29 | - <a href="api.html">API</a> | |
30 | - <a href="https://github.com/swadhinbiswas/OpencodeHub" class="btn-github" target="_blank"> | |
31 | - <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"> | |
32 | - <path | |
33 | - d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" /> | |
34 | - </svg> | |
35 | - GitHub | |
36 | - </a> | |
37 | - </nav> | |
44 | + </nav> | |
38 | 45 | </header> |
39 | 46 | |
40 | 47 | <div class="layout"> |
41 | - <!-- Sidebar --> | |
42 | - <aside class="sidebar"> | |
43 | - <div class="sidebar-section"> | |
44 | - <div class="sidebar-title">Getting Started</div> | |
45 | - <nav class="sidebar-nav"> | |
46 | - <a href="getting-started.html" class="sidebar-link active"> | |
47 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
48 | - <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /> | |
49 | - </svg> | |
50 | - Quick Start | |
51 | - </a> | |
52 | - <a href="getting-started.html#installation" class="sidebar-link"> | |
53 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
54 | - <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" /> | |
55 | - </svg> | |
56 | - Installation | |
57 | - </a> | |
58 | - <a href="getting-started.html#configuration" class="sidebar-link"> | |
59 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
60 | - <circle cx="12" cy="12" r="3" /> | |
61 | - <path | |
62 | - d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" /> | |
63 | - </svg> | |
64 | - Configuration | |
65 | - </a> | |
66 | - </nav> | |
67 | - </div> | |
68 | - | |
69 | - <div class="sidebar-section"> | |
70 | - <div class="sidebar-title">Deployment</div> | |
71 | - <nav class="sidebar-nav"> | |
72 | - <a href="deployment.html" class="sidebar-link"> | |
73 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
74 | - <rect x="2" y="3" width="20" height="14" rx="2" ry="2" /> | |
75 | - <path d="M8 21h8M12 17v4" /> | |
76 | - </svg> | |
77 | - Overview | |
78 | - </a> | |
79 | - <a href="deployment.html#docker" class="sidebar-link"> | |
80 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
81 | - <path | |
82 | - d="M22 12.5c0 .5-.5 1-1 1h-2v-2h2c.5 0 1 .5 1 1zM2 12.5c0-.5.5-1 1-1h2v2H3c-.5 0-1-.5-1-1z" /> | |
83 | - <rect x="6" y="9" width="12" height="6" rx="1" /> | |
84 | - <rect x="8" y="11" width="2" height="2" /> | |
85 | - <rect x="11" y="11" width="2" height="2" /> | |
86 | - <rect x="14" y="11" width="2" height="2" /> | |
87 | - </svg> | |
88 | - Docker | |
89 | - </a> | |
90 | - <a href="deployment.html#vercel" class="sidebar-link"> | |
91 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
92 | - <polygon points="12 2 2 22 22 22" /> | |
93 | - </svg> | |
94 | - Vercel | |
95 | - </a> | |
96 | - <a href="deployment.html#vps" class="sidebar-link"> | |
97 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
98 | - <rect x="2" y="2" width="20" height="8" rx="2" ry="2" /> | |
99 | - <rect x="2" y="14" width="20" height="8" rx="2" ry="2" /> | |
100 | - <line x1="6" y1="6" x2="6.01" y2="6" /> | |
101 | - <line x1="6" y1="18" x2="6.01" y2="18" /> | |
102 | - </svg> | |
103 | - VPS / Linux | |
104 | - </a> | |
105 | - </nav> | |
106 | - </div> | |
48 | + <!-- Sidebar --> | |
49 | + <aside class="sidebar"> | |
50 | + <div class="sidebar-section"> | |
51 | + <div class="sidebar-title">Getting Started</div> | |
52 | + <nav class="sidebar-nav"> | |
53 | + <a href="getting-started.html" class="sidebar-link active"> | |
54 | + <svg | |
55 | + viewBox="0 0 24 24" | |
56 | + fill="none" | |
57 | + stroke="currentColor" | |
58 | + stroke-width="2" | |
59 | + > | |
60 | + <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /> | |
61 | + </svg> | |
62 | + Quick Start | |
63 | + </a> | |
64 | + <a href="getting-started.html#installation" class="sidebar-link"> | |
65 | + <svg | |
66 | + viewBox="0 0 24 24" | |
67 | + fill="none" | |
68 | + stroke="currentColor" | |
69 | + stroke-width="2" | |
70 | + > | |
71 | + <path | |
72 | + d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" | |
73 | + /> | |
74 | + </svg> | |
75 | + Installation | |
76 | + </a> | |
77 | + <a href="getting-started.html#configuration" class="sidebar-link"> | |
78 | + <svg | |
79 | + viewBox="0 0 24 24" | |
80 | + fill="none" | |
81 | + stroke="currentColor" | |
82 | + stroke-width="2" | |
83 | + > | |
84 | + <circle cx="12" cy="12" r="3" /> | |
85 | + <path | |
86 | + d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" | |
87 | + /> | |
88 | + </svg> | |
89 | + Configuration | |
90 | + </a> | |
91 | + </nav> | |
92 | + </div> | |
107 | 93 | |
108 | - <div class="sidebar-section"> | |
109 | - <div class="sidebar-title">CLI</div> | |
110 | - <nav class="sidebar-nav"> | |
111 | - <a href="cli.html" class="sidebar-link"> | |
112 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
113 | - <polyline points="4 17 10 11 4 5" /> | |
114 | - <line x1="12" y1="19" x2="20" y2="19" /> | |
115 | - </svg> | |
116 | - CLI Reference | |
117 | - </a> | |
118 | - <a href="cli.html#stacked-prs" class="sidebar-link"> | |
119 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
120 | - <path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2" /> | |
121 | - <rect x="8" y="2" width="8" height="4" rx="1" ry="1" /> | |
122 | - </svg> | |
123 | - Stacked PRs | |
124 | - </a> | |
125 | - </nav> | |
126 | - </div> | |
94 | + <div class="sidebar-section"> | |
95 | + <div class="sidebar-title">Deployment</div> | |
96 | + <nav class="sidebar-nav"> | |
97 | + <a href="deployment.html" class="sidebar-link"> | |
98 | + <svg | |
99 | + viewBox="0 0 24 24" | |
100 | + fill="none" | |
101 | + stroke="currentColor" | |
102 | + stroke-width="2" | |
103 | + > | |
104 | + <rect x="2" y="3" width="20" height="14" rx="2" ry="2" /> | |
105 | + <path d="M8 21h8M12 17v4" /> | |
106 | + </svg> | |
107 | + Overview | |
108 | + </a> | |
109 | + <a href="deployment.html#docker" class="sidebar-link"> | |
110 | + <svg | |
111 | + viewBox="0 0 24 24" | |
112 | + fill="none" | |
113 | + stroke="currentColor" | |
114 | + stroke-width="2" | |
115 | + > | |
116 | + <path | |
117 | + d="M22 12.5c0 .5-.5 1-1 1h-2v-2h2c.5 0 1 .5 1 1zM2 12.5c0-.5.5-1 1-1h2v2H3c-.5 0-1-.5-1-1z" | |
118 | + /> | |
119 | + <rect x="6" y="9" width="12" height="6" rx="1" /> | |
120 | + <rect x="8" y="11" width="2" height="2" /> | |
121 | + <rect x="11" y="11" width="2" height="2" /> | |
122 | + <rect x="14" y="11" width="2" height="2" /> | |
123 | + </svg> | |
124 | + Docker | |
125 | + </a> | |
126 | + <a href="deployment.html#vercel" class="sidebar-link"> | |
127 | + <svg | |
128 | + viewBox="0 0 24 24" | |
129 | + fill="none" | |
130 | + stroke="currentColor" | |
131 | + stroke-width="2" | |
132 | + > | |
133 | + <polygon points="12 2 2 22 22 22" /> | |
134 | + </svg> | |
135 | + Vercel | |
136 | + </a> | |
137 | + <a href="deployment.html#vps" class="sidebar-link"> | |
138 | + <svg | |
139 | + viewBox="0 0 24 24" | |
140 | + fill="none" | |
141 | + stroke="currentColor" | |
142 | + stroke-width="2" | |
143 | + > | |
144 | + <rect x="2" y="2" width="20" height="8" rx="2" ry="2" /> | |
145 | + <rect x="2" y="14" width="20" height="8" rx="2" ry="2" /> | |
146 | + <line x1="6" y1="6" x2="6.01" y2="6" /> | |
147 | + <line x1="6" y1="18" x2="6.01" y2="18" /> | |
148 | + </svg> | |
149 | + VPS / Linux | |
150 | + </a> | |
151 | + </nav> | |
152 | + </div> | |
127 | 153 | |
128 | - <div class="sidebar-section"> | |
129 | - <div class="sidebar-title">API</div> | |
130 | - <nav class="sidebar-nav"> | |
131 | - <a href="api.html" class="sidebar-link"> | |
132 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
133 | - <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" /> | |
134 | - <polyline points="14 2 14 8 20 8" /> | |
135 | - <line x1="16" y1="13" x2="8" y2="13" /> | |
136 | - <line x1="16" y1="17" x2="8" y2="17" /> | |
137 | - <polyline points="10 9 9 9 8 9" /> | |
138 | - </svg> | |
139 | - REST API | |
140 | - </a> | |
141 | - <a href="api.html#webhooks" class="sidebar-link"> | |
142 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
143 | - <path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" /> | |
144 | - <path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" /> | |
145 | - </svg> | |
146 | - Webhooks | |
147 | - </a> | |
148 | - </nav> | |
149 | - </div> | |
150 | - </aside> | |
154 | + <div class="sidebar-section"> | |
155 | + <div class="sidebar-title">CLI</div> | |
156 | + <nav class="sidebar-nav"> | |
157 | + <a href="cli.html" class="sidebar-link"> | |
158 | + <svg | |
159 | + viewBox="0 0 24 24" | |
160 | + fill="none" | |
161 | + stroke="currentColor" | |
162 | + stroke-width="2" | |
163 | + > | |
164 | + <polyline points="4 17 10 11 4 5" /> | |
165 | + <line x1="12" y1="19" x2="20" y2="19" /> | |
166 | + </svg> | |
167 | + CLI Reference | |
168 | + </a> | |
169 | + <a href="cli.html#stacked-prs" class="sidebar-link"> | |
170 | + <svg | |
171 | + viewBox="0 0 24 24" | |
172 | + fill="none" | |
173 | + stroke="currentColor" | |
174 | + stroke-width="2" | |
175 | + > | |
176 | + <path | |
177 | + d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2" | |
178 | + /> | |
179 | + <rect x="8" y="2" width="8" height="4" rx="1" ry="1" /> | |
180 | + </svg> | |
181 | + Stacked PRs | |
182 | + </a> | |
183 | + </nav> | |
184 | + </div> | |
151 | 185 | |
152 | - <!-- Main Content --> | |
153 | - <main class="main"> | |
154 | - <div class="content"> | |
155 | - <!-- Hero Section --> | |
156 | - <section class="hero animate-fade-in"> | |
157 | - <h1>OpenCodeHub Documentation</h1> | |
158 | - <p class="hero-subtitle"> | |
159 | - Everything you need to deploy, configure, and master OpenCodeHub - | |
160 | - the self-hosted Git platform with stacked PRs, AI review, and CI/CD. | |
161 | - </p> | |
162 | - <div class="hero-buttons"> | |
163 | - <a href="getting-started.html" class="btn btn-primary"> | |
164 | - <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" | |
165 | - stroke-width="2"> | |
166 | - <polygon points="5 3 19 12 5 21 5 3" /> | |
167 | - </svg> | |
168 | - Get Started | |
169 | - </a> | |
170 | - <a href="deployment.html" class="btn btn-secondary"> | |
171 | - <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" | |
172 | - stroke-width="2"> | |
173 | - <path | |
174 | - d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" /> | |
175 | - </svg> | |
176 | - Deploy Now | |
177 | - </a> | |
178 | - </div> | |
179 | - </section> | |
186 | + <div class="sidebar-section"> | |
187 | + <div class="sidebar-title">API</div> | |
188 | + <nav class="sidebar-nav"> | |
189 | + <a href="api.html" class="sidebar-link"> | |
190 | + <svg | |
191 | + viewBox="0 0 24 24" | |
192 | + fill="none" | |
193 | + stroke="currentColor" | |
194 | + stroke-width="2" | |
195 | + > | |
196 | + <path | |
197 | + d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" | |
198 | + /> | |
199 | + <polyline points="14 2 14 8 20 8" /> | |
200 | + <line x1="16" y1="13" x2="8" y2="13" /> | |
201 | + <line x1="16" y1="17" x2="8" y2="17" /> | |
202 | + <polyline points="10 9 9 9 8 9" /> | |
203 | + </svg> | |
204 | + REST API | |
205 | + </a> | |
206 | + <a href="api.html#webhooks" class="sidebar-link"> | |
207 | + <svg | |
208 | + viewBox="0 0 24 24" | |
209 | + fill="none" | |
210 | + stroke="currentColor" | |
211 | + stroke-width="2" | |
212 | + > | |
213 | + <path | |
214 | + d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
215 | + /> | |
216 | + <path | |
217 | + d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
218 | + /> | |
219 | + </svg> | |
220 | + Webhooks | |
221 | + </a> | |
222 | + </nav> | |
223 | + </div> | |
224 | + </aside> | |
180 | 225 | |
181 | - <!-- Quick Install --> | |
182 | - <section> | |
183 | - <h2>⚡ Quick Install</h2> | |
184 | - <p>Get OpenCodeHub running in minutes with Docker:</p> | |
226 | + <!-- Main Content --> | |
227 | + <main class="main"> | |
228 | + <div class="content"> | |
229 | + <!-- Hero Section --> | |
230 | + <section class="hero animate-fade-in"> | |
231 | + <h1>OpenCodeHub Documentation</h1> | |
232 | + <p class="hero-subtitle"> | |
233 | + Everything you need to deploy, configure, and master OpenCodeHub - | |
234 | + the self-hosted Git platform with stacked PRs, AI review, and | |
235 | + CI/CD. | |
236 | + </p> | |
237 | + <div class="hero-buttons"> | |
238 | + <a href="getting-started.html" class="btn btn-primary"> | |
239 | + <svg | |
240 | + width="18" | |
241 | + height="18" | |
242 | + viewBox="0 0 24 24" | |
243 | + fill="none" | |
244 | + stroke="currentColor" | |
245 | + stroke-width="2" | |
246 | + > | |
247 | + <polygon points="5 3 19 12 5 21 5 3" /> | |
248 | + </svg> | |
249 | + Get Started | |
250 | + </a> | |
251 | + <a href="deployment.html" class="btn btn-secondary"> | |
252 | + <svg | |
253 | + width="18" | |
254 | + height="18" | |
255 | + viewBox="0 0 24 24" | |
256 | + fill="none" | |
257 | + stroke="currentColor" | |
258 | + stroke-width="2" | |
259 | + > | |
260 | + <path | |
261 | + d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" | |
262 | + /> | |
263 | + </svg> | |
264 | + Deploy Now | |
265 | + </a> | |
266 | + </div> | |
267 | + </section> | |
185 | 268 | |
186 | - <div class="command-line"> | |
187 | - <span class="command-prompt">$</span> | |
188 | - <span class="command-text">git clone https://github.com/swadhinbiswas/OpencodeHub && cd | |
189 | - OpenCodeHub</span> | |
190 | - <button class="command-copy" | |
191 | - onclick="copyCommand(this, 'git clone https://github.com/swadhinbiswas/OpencodeHub && cd OpenCodeHub')"> | |
192 | - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" | |
193 | - stroke-width="2"> | |
194 | - <rect x="9" y="9" width="13" height="13" rx="2" ry="2" /> | |
195 | - <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" /> | |
196 | - </svg> | |
197 | - </button> | |
198 | - </div> | |
269 | + <!-- Quick Install --> | |
270 | + <section> | |
271 | + <h2>⚡ Quick Install</h2> | |
272 | + <p>Get OpenCodeHub running in minutes with Docker:</p> | |
199 | 273 | |
200 | - <div class="command-line"> | |
201 | - <span class="command-prompt">$</span> | |
202 | - <span class="command-text">docker-compose up -d</span> | |
203 | - <button class="command-copy" onclick="copyCommand(this, 'docker-compose up -d')"> | |
204 | - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" | |
205 | - stroke-width="2"> | |
206 | - <rect x="9" y="9" width="13" height="13" rx="2" ry="2" /> | |
207 | - <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" /> | |
208 | - </svg> | |
209 | - </button> | |
210 | - </div> | |
274 | + <div class="command-line"> | |
275 | + <span class="command-prompt">$</span> | |
276 | + <span class="command-text" | |
277 | + >git clone https://github.com/swadhinbiswas/OpencodeHub && cd | |
278 | + OpenCodeHub</span | |
279 | + > | |
280 | + <button | |
281 | + class="command-copy" | |
282 | + onclick=" | |
283 | + copyCommand( | |
284 | + this, | |
285 | + 'git clone https://github.com/swadhinbiswas/OpencodeHub && cd OpenCodeHub', | |
286 | + ) | |
287 | + " | |
288 | + > | |
289 | + <svg | |
290 | + width="16" | |
291 | + height="16" | |
292 | + viewBox="0 0 24 24" | |
293 | + fill="none" | |
294 | + stroke="currentColor" | |
295 | + stroke-width="2" | |
296 | + > | |
297 | + <rect x="9" y="9" width="13" height="13" rx="2" ry="2" /> | |
298 | + <path | |
299 | + d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" | |
300 | + /> | |
301 | + </svg> | |
302 | + </button> | |
303 | + </div> | |
211 | 304 | |
212 | - <p>Then visit <code>http://localhost:3000</code> in your browser.</p> | |
213 | - </section> | |
305 | + <div class="command-line"> | |
306 | + <span class="command-prompt">$</span> | |
307 | + <span class="command-text">docker-compose up -d</span> | |
308 | + <button | |
309 | + class="command-copy" | |
310 | + onclick="copyCommand(this, 'docker-compose up -d')" | |
311 | + > | |
312 | + <svg | |
313 | + width="16" | |
314 | + height="16" | |
315 | + viewBox="0 0 24 24" | |
316 | + fill="none" | |
317 | + stroke="currentColor" | |
318 | + stroke-width="2" | |
319 | + > | |
320 | + <rect x="9" y="9" width="13" height="13" rx="2" ry="2" /> | |
321 | + <path | |
322 | + d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" | |
323 | + /> | |
324 | + </svg> | |
325 | + </button> | |
326 | + </div> | |
214 | 327 | |
215 | - <!-- Features --> | |
216 | - <section> | |
217 | - <h2>✨ Key Features</h2> | |
218 | - <div class="feature-grid"> | |
219 | - <div class="card"> | |
220 | - <h3 class="card-title"> | |
221 | - <span>📚</span> Stacked Pull Requests | |
222 | - </h3> | |
223 | - <p class="card-description"> | |
224 | - Break large changes into reviewable chunks. Ship faster with dependent PRs that | |
225 | - automatically update. | |
226 | - </p> | |
227 | - </div> | |
228 | - <div class="card"> | |
229 | - <h3 class="card-title"> | |
230 | - <span>🤖</span> AI Code Review | |
231 | - </h3> | |
232 | - <p class="card-description"> | |
233 | - Instant feedback from GPT-4 or Claude. Catch security issues, bugs, and code smells | |
234 | - automatically. | |
235 | - </p> | |
236 | - </div> | |
237 | - <div class="card"> | |
238 | - <h3 class="card-title"> | |
239 | - <span>🔀</span> Smart Merge Queue | |
240 | - </h3> | |
241 | - <p class="card-description"> | |
242 | - Stack-aware CI optimization. Batch PRs, auto-rebase, and merge in the correct order. | |
243 | - </p> | |
244 | - </div> | |
245 | - <div class="card"> | |
246 | - <h3 class="card-title"> | |
247 | - <span>⚡</span> CI/CD Pipelines | |
248 | - </h3> | |
249 | - <p class="card-description"> | |
250 | - GitHub Actions-compatible workflows. Build, test, and deploy with self-hosted runners. | |
251 | - </p> | |
252 | - </div> | |
253 | - <div class="card"> | |
254 | - <h3 class="card-title"> | |
255 | - <span>🔒</span> Enterprise Security | |
256 | - </h3> | |
257 | - <p class="card-description"> | |
258 | - Rate limiting, CSRF protection, audit logging, 2FA, and encrypted secrets management. | |
259 | - </p> | |
260 | - </div> | |
261 | - <div class="card"> | |
262 | - <h3 class="card-title"> | |
263 | - <span>💻</span> Powerful CLI | |
264 | - </h3> | |
265 | - <p class="card-description"> | |
266 | - Stack-first workflows from your terminal. Manage PRs, sync stacks, and more. | |
267 | - </p> | |
268 | - </div> | |
269 | - </div> | |
270 | - </section> | |
328 | + <p> | |
329 | + Then visit <code>http://localhost:3000</code> in your browser. | |
330 | + </p> | |
331 | + </section> | |
271 | 332 | |
272 | - <!-- Documentation Sections --> | |
273 | - <section> | |
274 | - <h2>📖 Documentation</h2> | |
333 | + <!-- Features --> | |
334 | + <section> | |
335 | + <h2>✨ Key Features</h2> | |
336 | + <div class="feature-grid"> | |
337 | + <div class="card"> | |
338 | + <h3 class="card-title"> | |
339 | + <span>📚</span> Stacked Pull Requests | |
340 | + </h3> | |
341 | + <p class="card-description"> | |
342 | + Break large changes into reviewable chunks. Ship faster with | |
343 | + dependent PRs that automatically update. | |
344 | + </p> | |
345 | + </div> | |
346 | + <div class="card"> | |
347 | + <h3 class="card-title"><span>🤖</span> AI Code Review</h3> | |
348 | + <p class="card-description"> | |
349 | + Instant feedback from GPT-4 or Claude. Catch security issues, | |
350 | + bugs, and code smells automatically. | |
351 | + </p> | |
352 | + </div> | |
353 | + <div class="card"> | |
354 | + <h3 class="card-title"><span>🔀</span> Smart Merge Queue</h3> | |
355 | + <p class="card-description"> | |
356 | + Stack-aware CI optimization. Batch PRs, auto-rebase, and merge | |
357 | + in the correct order. | |
358 | + </p> | |
359 | + </div> | |
360 | + <div class="card"> | |
361 | + <h3 class="card-title"><span>⚡</span> CI/CD Pipelines</h3> | |
362 | + <p class="card-description"> | |
363 | + GitHub Actions-compatible workflows. Build, test, and deploy | |
364 | + with self-hosted runners. | |
365 | + </p> | |
366 | + </div> | |
367 | + <div class="card"> | |
368 | + <h3 class="card-title"><span>🔒</span> Enterprise Security</h3> | |
369 | + <p class="card-description"> | |
370 | + Rate limiting, CSRF protection, audit logging, 2FA, and | |
371 | + encrypted secrets management. | |
372 | + </p> | |
373 | + </div> | |
374 | + <div class="card"> | |
375 | + <h3 class="card-title"><span>💻</span> Powerful CLI</h3> | |
376 | + <p class="card-description"> | |
377 | + Stack-first workflows from your terminal. Manage PRs, sync | |
378 | + stacks, and more. | |
379 | + </p> | |
380 | + </div> | |
381 | + </div> | |
382 | + </section> | |
275 | 383 | |
276 | - <div class="card" style="cursor: pointer" onclick="location.href='getting-started.html'"> | |
277 | - <h3 class="card-title">🚀 Getting Started</h3> | |
278 | - <p class="card-description"> | |
279 | - Install OpenCodeHub, create your first repository, and configure your environment. | |
280 | - </p> | |
281 | - </div> | |
384 | + <!-- Documentation Sections --> | |
385 | + <section> | |
386 | + <h2>📖 Documentation</h2> | |
282 | 387 | |
283 | - <div class="card" style="cursor: pointer" onclick="location.href='deployment.html'"> | |
284 | - <h3 class="card-title">🌐 Deployment Guide</h3> | |
285 | - <p class="card-description"> | |
286 | - Deploy to Docker, Vercel, VPS, or any cloud provider with production-ready configurations. | |
287 | - </p> | |
288 | - </div> | |
388 | + <div | |
389 | + class="card" | |
390 | + style="cursor: pointer" | |
391 | + onclick="location.href = 'getting-started.html'" | |
392 | + > | |
393 | + <h3 class="card-title">🚀 Getting Started</h3> | |
394 | + <p class="card-description"> | |
395 | + Install OpenCodeHub, create your first repository, and configure | |
396 | + your environment. | |
397 | + </p> | |
398 | + </div> | |
289 | 399 | |
290 | - <div class="card" style="cursor: pointer" onclick="location.href='cli.html'"> | |
291 | - <h3 class="card-title">💻 CLI Reference</h3> | |
292 | - <p class="card-description"> | |
293 | - Complete command reference for the OpenCodeHub CLI. Stack management, authentication, and | |
294 | - more. | |
295 | - </p> | |
296 | - </div> | |
400 | + <div | |
401 | + class="card" | |
402 | + style="cursor: pointer" | |
403 | + onclick="location.href = 'deployment.html'" | |
404 | + > | |
405 | + <h3 class="card-title">🌐 Deployment Guide</h3> | |
406 | + <p class="card-description"> | |
407 | + Deploy to Docker, Vercel, VPS, or any cloud provider with | |
408 | + production-ready configurations. | |
409 | + </p> | |
410 | + </div> | |
297 | 411 | |
298 | - <div class="card" style="cursor: pointer" onclick="location.href='api.html'"> | |
299 | - <h3 class="card-title">🔌 API Documentation</h3> | |
300 | - <p class="card-description"> | |
301 | - REST API reference, webhook events, and authentication for programmatic access. | |
302 | - </p> | |
303 | - </div> | |
304 | - </section> | |
412 | + <div | |
413 | + class="card" | |
414 | + style="cursor: pointer" | |
415 | + onclick="location.href = 'cli.html'" | |
416 | + > | |
417 | + <h3 class="card-title">💻 CLI Reference</h3> | |
418 | + <p class="card-description"> | |
419 | + Complete command reference for the OpenCodeHub CLI. Stack | |
420 | + management, authentication, and more. | |
421 | + </p> | |
422 | + </div> | |
305 | 423 | |
306 | - <!-- Footer --> | |
307 | - <footer | |
308 | - style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--border-primary); text-align: center; color: var(--text-muted);"> | |
309 | - <p> | |
310 | - Made with ❤️ by <a href="https://github.com/swadhinbiswas">Swadhin Biswas</a> • | |
311 | - <a href="https://github.com/swadhinbiswas/OpencodeHub">GitHub</a> • | |
312 | - <a href="https://github.com/swadhinbiswas/OpencodeHub/blob/main/LICENSE">MIT License</a> | |
313 | - </p> | |
314 | - </footer> | |
424 | + <div | |
425 | + class="card" | |
426 | + style="cursor: pointer" | |
427 | + onclick="location.href = 'api.html'" | |
428 | + > | |
429 | + <h3 class="card-title">🔌 API Documentation</h3> | |
430 | + <p class="card-description"> | |
431 | + REST API reference, webhook events, and authentication for | |
432 | + programmatic access. | |
433 | + </p> | |
315 | 434 | </div> |
316 | - </main> | |
435 | + </section> | |
436 | + | |
437 | + <!-- Footer --> | |
438 | + <footer | |
439 | + style=" | |
440 | + margin-top: 4rem; | |
441 | + padding-top: 2rem; | |
442 | + border-top: 1px solid var(--border-primary); | |
443 | + text-align: center; | |
444 | + color: var(--text-muted); | |
445 | + " | |
446 | + > | |
447 | + <p> | |
448 | + Made with ❤️ by | |
449 | + <a href="https://github.com/swadhinbiswas">Swadhin Biswas</a> • | |
450 | + <a href="https://github.com/swadhinbiswas/OpencodeHub">GitHub</a> | |
451 | + • | |
452 | + <a | |
453 | + href="https://github.com/swadhinbiswas/OpencodeHub/blob/main/LICENSE" | |
454 | + >MIT License</a | |
455 | + > | |
456 | + </p> | |
457 | + </footer> | |
458 | + </div> | |
459 | + </main> | |
317 | 460 | </div> |
318 | 461 | |
319 | 462 | <script> |
320 | - function copyCommand(button, text) { | |
321 | - navigator.clipboard.writeText(text); | |
322 | - button.classList.add('copied'); | |
323 | - setTimeout(() => button.classList.remove('copied'), 2000); | |
324 | - } | |
463 | + function copyCommand(button, text) { | |
464 | + navigator.clipboard.writeText(text); | |
465 | + button.classList.add("copied"); | |
466 | + setTimeout(() => button.classList.remove("copied"), 2000); | |
467 | + } | |
325 | 468 | |
326 | - // Update active sidebar link based on current page | |
327 | - document.querySelectorAll('.sidebar-link').forEach(link => { | |
328 | - if (link.href === window.location.href) { | |
329 | - link.classList.add('active'); | |
330 | - } | |
331 | - }); | |
469 | + // Update active sidebar link based on current page | |
470 | + document.querySelectorAll(".sidebar-link").forEach((link) => { | |
471 | + if (link.href === window.location.href) { | |
472 | + link.classList.add("active"); | |
473 | + } | |
474 | + }); | |
332 | 475 | </script> |
333 | -</body> | |
334 | - | |
335 | -</html> | |
476 | + </body> | |
477 | +</html> | |
336 | 478 | |
▼public/favicon.png
+0-0
Binary file not shown.
▼public/logo-dark.png
+0-0
Binary file not shown.
▼public/logo-light.png
+0-0
Binary file not shown.
▼public/logo.png
+0-0
Binary file not shown.
▼public/stack-workflow.svg
+113-42
| @@ -1,70 +1,141 @@ | ||
1 | -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300"> | |
1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 400"> | |
2 | 2 | <defs> |
3 | 3 | <style> |
4 | - @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap'); | |
4 | + @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); | |
5 | 5 | </style> |
6 | - <!-- Gradients --> | |
7 | - <linearGradient id="grad-main" x1="0%" y1="0%" x2="100%" y2="0%"> | |
8 | - <stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" /> | |
9 | - <stop offset="100%" style="stop-color:#16a34a;stop-opacity:1" /> | |
6 | + | |
7 | + <!-- Modern Gradients matching logo colors --> | |
8 | + <linearGradient id="grad-main" x1="0%" y1="0%" x2="100%" y2="100%"> | |
9 | + <stop offset="0%" style="stop-color:#50fa7b;stop-opacity:1" /> | |
10 | + <stop offset="100%" style="stop-color:#2dd4bf;stop-opacity:1" /> | |
11 | + </linearGradient> | |
12 | + | |
13 | + <linearGradient id="grad-feat1" x1="0%" y1="0%" x2="100%" y2="100%"> | |
14 | + <stop offset="0%" style="stop-color:#8be9fd;stop-opacity:1" /> | |
15 | + <stop offset="100%" style="stop-color:#60a5fa;stop-opacity:1" /> | |
10 | 16 | </linearGradient> |
11 | - <linearGradient id="grad-feat1" x1="0%" y1="0%" x2="100%" y2="0%"> | |
12 | - <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" /> | |
13 | - <stop offset="100%" style="stop-color:#2563eb;stop-opacity:1" /> | |
17 | + | |
18 | + <linearGradient id="grad-feat2" x1="0%" y1="0%" x2="100%" y2="100%"> | |
19 | + <stop offset="0%" style="stop-color:#bd93f9;stop-opacity:1" /> | |
20 | + <stop offset="100%" style="stop-color:#a78bfa;stop-opacity:1" /> | |
14 | 21 | </linearGradient> |
15 | - <linearGradient id="grad-feat2" x1="0%" y1="0%" x2="100%" y2="0%"> | |
16 | - <stop offset="0%" style="stop-color:#a855f7;stop-opacity:1" /> | |
17 | - <stop offset="100%" style="stop-color:#9333ea;stop-opacity:1" /> | |
22 | + | |
23 | + <linearGradient id="grad-feat3" x1="0%" y1="0%" x2="100%" y2="100%"> | |
24 | + <stop offset="0%" style="stop-color:#ff79c6;stop-opacity:1" /> | |
25 | + <stop offset="100%" style="stop-color:#f472b6;stop-opacity:1" /> | |
18 | 26 | </linearGradient> |
19 | - | |
27 | + | |
28 | + <!-- Enhanced shadow --> | |
20 | 29 | <filter id="shadow"> |
21 | - <feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000" flood-opacity="0.2"/> | |
30 | + <feDropShadow dx="0" dy="6" stdDeviation="8" flood-color="#000" flood-opacity="0.25"/> | |
22 | 31 | </filter> |
23 | - | |
24 | - <marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth"> | |
25 | - <path d="M0,0 L0,6 L9,3 z" fill="#64748b" /> | |
32 | + | |
33 | + <filter id="glow"> | |
34 | + <feGaussianBlur stdDeviation="3" result="coloredBlur"/> | |
35 | + <feMerge> | |
36 | + <feMergeNode in="coloredBlur"/> | |
37 | + <feMergeNode in="SourceGraphic"/> | |
38 | + </feMerge> | |
39 | + </filter> | |
40 | + | |
41 | + <!-- Arrow markers --> | |
42 | + <marker id="arrow" markerWidth="12" markerHeight="12" refX="10" refY="4" orient="auto"> | |
43 | + <path d="M0,0 L0,8 L10,4 z" fill="#8be9fd" /> | |
44 | + </marker> | |
45 | + | |
46 | + <marker id="arrow-down" markerWidth="12" markerHeight="12" refX="4" refY="10" orient="auto"> | |
47 | + <path d="M0,0 L8,0 L4,10 z" fill="#ff79c6" opacity="0.6" /> | |
26 | 48 | </marker> |
27 | 49 | </defs> |
28 | 50 | |
29 | - <!-- Background (optional) --> | |
30 | - <!-- <rect width="800" height="300" fill="#f8fafc" /> --> | |
51 | + <!-- Background with subtle gradient --> | |
52 | + <rect width="900" height="400" fill="#1a1b26" opacity="0.02" /> | |
31 | 53 | |
32 | - <!-- Title --> | |
33 | - <text x="400" y="40" font-family="Inter, sans-serif" font-size="20" font-weight="600" text-anchor="middle" fill="#334155">Stacked PR Workflow</text> | |
54 | + <!-- Title with modern styling --> | |
55 | + <text x="450" y="45" font-family="Inter, sans-serif" font-size="26" font-weight="700" text-anchor="middle" fill="#2dd4bf"> | |
56 | + Stacked PR Workflow | |
57 | + </text> | |
58 | + <text x="450" y="70" font-family="Inter, sans-serif" font-size="14" text-anchor="middle" fill="#64748b"> | |
59 | + Build dependent features in parallel with confidence | |
60 | + </text> | |
34 | 61 | |
35 | 62 | <!-- Workflow Steps --> |
36 | - <g transform="translate(50, 80)"> | |
37 | - | |
38 | - <!-- Step 1: Main (Base) --> | |
39 | - <g transform="translate(0, 50)"> | |
40 | - <rect x="0" y="0" width="160" height="80" rx="12" fill="url(#grad-main)" filter="url(#shadow)"/> | |
41 | - <text x="80" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">main</text> | |
42 | - <text x="80" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.9">Production Ready</text> | |
63 | + <g transform="translate(50, 120)"> | |
64 | + | |
65 | + <!-- Step 1: Main Branch --> | |
66 | + <g transform="translate(0, 40)"> | |
67 | + <rect x="0" y="0" width="180" height="90" rx="16" fill="url(#grad-main)" filter="url(#shadow)" opacity="0.95"/> | |
68 | + <rect x="2" y="2" width="176" height="86" rx="15" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.2"/> | |
69 | + <text x="90" y="40" font-family="Inter, sans-serif" font-size="18" font-weight="700" text-anchor="middle" fill="white">main</text> | |
70 | + <text x="90" y="60" font-family="Inter, sans-serif" font-size="13" text-anchor="middle" fill="white" opacity="0.9">Base Branch</text> | |
71 | + <circle cx="15" cy="15" r="5" fill="#50fa7b" filter="url(#glow)"/> | |
43 | 72 | </g> |
44 | 73 | |
45 | 74 | <!-- Arrow 1 --> |
46 | - <path d="M170 90 L230 90" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)" fill="none"/> | |
75 | + <path d="M190 85 L250 85" stroke="#8be9fd" stroke-width="3" marker-end="url(#arrow)" fill="none" opacity="0.8"/> | |
76 | + <text x="220" y="78" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="#64748b">depends on</text> | |
47 | 77 | |
48 | 78 | <!-- Step 2: Feature 1 --> |
49 | - <g transform="translate(240, 50)"> | |
50 | - <rect x="0" y="0" width="160" height="80" rx="12" fill="url(#grad-feat1)" filter="url(#shadow)"/> | |
51 | - <text x="80" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/api-core</text> | |
52 | - <text x="80" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.9">PR #1 (Base: main)</text> | |
79 | + <g transform="translate(260, 40)"> | |
80 | + <rect x="0" y="0" width="180" height="90" rx="16" fill="url(#grad-feat1)" filter="url(#shadow)" opacity="0.95"/> | |
81 | + <rect x="2" y="2" width="176" height="86" rx="15" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.2"/> | |
82 | + <text x="90" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/api-core</text> | |
83 | + <text x="90" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.85">PR #123</text> | |
84 | + <text x="90" y="72" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="white" opacity="0.7">Base: main</text> | |
85 | + <circle cx="15" cy="15" r="5" fill="#8be9fd" filter="url(#glow)"/> | |
53 | 86 | </g> |
54 | 87 | |
55 | 88 | <!-- Arrow 2 --> |
56 | - <path d="M410 90 L470 90" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)" fill="none"/> | |
89 | + <path d="M450 85 L510 85" stroke="#8be9fd" stroke-width="3" marker-end="url(#arrow)" fill="none" opacity="0.8"/> | |
90 | + <text x="480" y="78" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="#64748b">depends on</text> | |
91 | + | |
92 | + <!-- Step 3: Feature 2 (Stacked on Feature 1) --> | |
93 | + <g transform="translate(520, 40)"> | |
94 | + <rect x="0" y="0" width="180" height="90" rx="16" fill="url(#grad-feat2)" filter="url(#shadow)" opacity="0.95"/> | |
95 | + <rect x="2" y="2" width="176" height="86" rx="15" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.2"/> | |
96 | + <text x="90" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/ui-login</text> | |
97 | + <text x="90" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.85">PR #124</text> | |
98 | + <text x="90" y="72" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="white" opacity="0.7">Base: PR #123</text> | |
99 | + <circle cx="15" cy="15" r="5" fill="#bd93f9" filter="url(#glow)"/> | |
100 | + </g> | |
101 | + | |
102 | + <!-- Stack Dependency Visualization --> | |
103 | + <g transform="translate(0, 165)"> | |
104 | + <!-- Dependency lines --> | |
105 | + <path d="M90 0 L90 30" stroke="#ff79c6" stroke-width="2" stroke-dasharray="6,4" marker-end="url(#arrow-down)" opacity="0.5"/> | |
106 | + <path d="M350 0 L350 30" stroke="#ff79c6" stroke-width="2" stroke-dasharray="6,4" marker-end="url(#arrow-down)" opacity="0.5"/> | |
107 | + <path d="M610 0 L610 30" stroke="#ff79c6" stroke-width="2" stroke-dasharray="6,4" marker-end="url(#arrow-down)" opacity="0.5"/> | |
108 | + | |
109 | + <!-- Stack info box --> | |
110 | + <rect x="180" y="35" width="340" height="55" rx="12" fill="#1a1b26" fill-opacity="0.05" stroke="#8be9fd" stroke-width="2" stroke-dasharray="8,4" opacity="0.6"/> | |
111 | + <text x="350" y="58" font-family="Inter, sans-serif" font-size="13" font-weight="600" text-anchor="middle" fill="#8be9fd"> | |
112 | + ⚡ Automatic Change Propagation | |
113 | + </text> | |
114 | + <text x="350" y="77" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="#64748b"> | |
115 | + Updates to PR #123 automatically sync to PR #124 | |
116 | + </text> | |
117 | + </g> | |
118 | + | |
119 | + </g> | |
57 | 120 | |
58 | - <!-- Step 3: Feature 2 (Stacked) --> | |
59 | - <g transform="translate(480, 50)"> | |
60 | - <rect x="0" y="0" width="160" height="80" rx="12" fill="url(#grad-feat2)" filter="url(#shadow)"/> | |
61 | - <text x="80" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/ui-login</text> | |
62 | - <text x="80" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.9">PR #2 (Base: PR #1)</text> | |
121 | + <!-- Benefits Section --> | |
122 | + <g transform="translate(50, 320)"> | |
123 | + <!-- Benefit 1 --> | |
124 | + <g> | |
125 | + <circle cx="10" cy="10" r="6" fill="#50fa7b" opacity="0.8"/> | |
126 | + <text x="25" y="15" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#334155">Parallel Development</text> | |
63 | 127 | </g> |
64 | 128 | |
65 | - <!-- Connection Lines for "Stack" concept --> | |
66 | - <path d="M320 140 L320 160 L560 160 L560 140" stroke="#cbd5e1" stroke-width="2" stroke-dasharray="5,5" fill="none"/> | |
67 | - <text x="440" y="180" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="#64748b">Changes propagate automatically down the stack</text> | |
129 | + <!-- Benefit 2 --> | |
130 | + <g transform="translate(250, 0)"> | |
131 | + <circle cx="10" cy="10" r="6" fill="#8be9fd" opacity="0.8"/> | |
132 | + <text x="25" y="15" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#334155">Faster Reviews</text> | |
133 | + </g> | |
68 | 134 | |
135 | + <!-- Benefit 3 --> | |
136 | + <g transform="translate(500, 0)"> | |
137 | + <circle cx="10" cy="10" r="6" fill="#bd93f9" opacity="0.8"/> | |
138 | + <text x="25" y="15" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#334155">Auto-sync Changes</text> | |
139 | + </g> | |
69 | 140 | </g> |
70 | 141 | </svg> |
71 | 142 | |
▼src/components/layout/Header.astro
+5-4
| @@ -9,7 +9,8 @@ const user = Astro.locals.user; | ||
9 | 9 | <div class="container h-16 flex items-center justify-between gap-4"> |
10 | 10 | <!-- Logo --> |
11 | 11 | <a href="/" class="flex items-center gap-2 transition-opacity hover:opacity-80"> |
12 | - <img src="/logo.svg" alt="OpenCodeHub" class="h-9 w-auto" /> | |
12 | + <img src="/logo-dark.png" alt="OpenCodeHub" class="h-9 w-auto dark:hidden" /> | |
13 | + <img src="/logo-light.png" alt="OpenCodeHub" class="h-9 w-auto hidden dark:block" /> | |
13 | 14 | </a> |
14 | 15 | |
15 | 16 | <!-- Navigation --> |
| @@ -160,7 +161,7 @@ const user = Astro.locals.user; | ||
160 | 161 | <a href="/actions" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">Actions</a> |
161 | 162 | <a href="/cli" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">CLI</a> |
162 | 163 | <a href="/admin" class="block px-3 py-2 text-sm font-medium text-purple-400 hover:bg-purple-500/10 rounded-lg transition-colors">Mission Control</a> |
163 | - | |
164 | + | |
164 | 165 | <div class="pt-2"> |
165 | 166 | <button |
166 | 167 | class="w-full flex items-center h-10 rounded-lg border border-input bg-background/50 px-3 text-sm text-muted-foreground text-left" |
| @@ -172,7 +173,7 @@ const user = Astro.locals.user; | ||
172 | 173 | </div> |
173 | 174 | </nav> |
174 | 175 | </div> |
175 | - | |
176 | + | |
176 | 177 | <GlobalSearch client:idle /> |
177 | 178 | </header> |
178 | 179 | |
| @@ -226,7 +227,7 @@ const user = Astro.locals.user; | ||
226 | 227 | }); |
227 | 228 | // Also handle keydown k if focused (though usually we intercept global k) |
228 | 229 | } |
229 | - | |
230 | + | |
230 | 231 | if (mobileSearchTrigger) { |
231 | 232 | mobileSearchTrigger.addEventListener('click', openSearch); |
232 | 233 | } |
233 | 234 | |
▼src/components/repo/FileExplorer.astro
+110-81
| @@ -1,5 +1,6 @@ | ||
1 | 1 | --- |
2 | 2 | import { FileText, Folder } from "lucide-react"; |
3 | +import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; | |
3 | 4 | |
4 | 5 | interface Props { |
5 | 6 | owner: string; |
| @@ -26,91 +27,119 @@ const getUrl = (file: any) => { | ||
26 | 27 | }; |
27 | 28 | --- |
28 | 29 | |
29 | -<div class="rounded-lg border"> | |
30 | - <div class="divide-y"> | |
31 | - { | |
32 | - path && ( | |
33 | - <div class="flex items-center gap-4 px-4 py-2 hover:bg-muted/30"> | |
34 | - <div class="w-4 flex justify-center"> | |
35 | - <span class="text-muted-foreground">..</span> | |
36 | - </div> | |
37 | - <span class="flex-1 text-sm"> | |
38 | - <a | |
39 | - href={`/${owner}/${repo}/tree/${branch}/${path.split("/").slice(0, -1).join("/")}`} | |
40 | - class="hover:underline hover:text-primary font-bold text-blue-500" | |
41 | - > | |
42 | - .. | |
43 | - </a> | |
44 | - </span> | |
45 | - </div> | |
46 | - ) | |
47 | - } | |
48 | - { | |
49 | - files.length > 0 ? ( | |
50 | - files.map((file) => ( | |
51 | - <div class="flex items-center gap-4 px-4 py-2 hover:bg-muted/30 group"> | |
52 | - <div class="w-4 flex justify-center"> | |
53 | - <div class="w-4 flex justify-center"> | |
54 | - {file.type === "directory" ? ( | |
55 | - <svg | |
56 | - class="h-4 w-4 text-blue-500 fill-blue-500" | |
57 | - viewBox="0 0 16 16" | |
58 | - fill="currentColor" | |
59 | - > | |
60 | - <path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z" /> | |
61 | - </svg> | |
62 | - ) : ( | |
63 | - (() => { | |
64 | - const ext = file.name.split('.').pop()?.toLowerCase(); | |
65 | - let iconClass = "text-muted-foreground"; | |
66 | - if (['js', 'jsx', 'ts', 'tsx', 'cjs', 'mjs'].includes(ext || '')) iconClass = "text-yellow-500"; | |
67 | - else if (['html', 'htm'].includes(ext || '')) iconClass = "text-orange-600"; | |
68 | - else if (['css', 'scss', 'sass', 'less'].includes(ext || '')) iconClass = "text-blue-600"; | |
69 | - else if (['json', 'yml', 'yaml', 'toml'].includes(ext || '')) iconClass = "text-yellow-600"; | |
70 | - else if (['md', 'txt'].includes(ext || '')) iconClass = "text-gray-400"; | |
71 | - else if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext || '')) iconClass = "text-purple-500"; | |
72 | - else if (['py'].includes(ext || '')) iconClass = "text-blue-500"; | |
73 | - else if (['go'].includes(ext || '')) iconClass = "text-cyan-500"; | |
74 | - else if (['rs'].includes(ext || '')) iconClass = "text-orange-500"; | |
75 | - else if (['java'].includes(ext || '')) iconClass = "text-red-500"; | |
76 | - else if (['c', 'cpp', 'h', 'hpp'].includes(ext || '')) iconClass = "text-blue-700"; | |
77 | - else if (['sh', 'bash', 'zsh'].includes(ext || '')) iconClass = "text-green-500"; | |
78 | - | |
79 | - return <FileText className={`h-4 w-4 ${iconClass}`} />; | |
80 | - })() | |
81 | - )} | |
82 | - </div> | |
83 | - </div> | |
84 | - <span class="flex-1 text-sm"> | |
30 | +<div class="w-full"> | |
31 | + <table class="w-full text-sm"> | |
32 | + <tbody class="divide-y divide-border"> | |
33 | + { | |
34 | + path && ( | |
35 | + <tr class="hover:bg-muted/50 transition-colors"> | |
36 | + <td class="p-4 py-2 w-10"> | |
37 | + <span class="text-muted-foreground">..</span> | |
38 | + </td> | |
39 | + <td class="p-4 py-2 font-medium"> | |
85 | 40 | <a |
86 | - href={getUrl(file)} | |
87 | - class="hover:underline hover:text-primary text-foreground" | |
41 | + href={`/${owner}/${repo}/tree/${branch}/${path.split("/").slice(0, -1).join("/")}`} | |
42 | + class="hover:underline hover:text-primary text-blue-500 block" | |
88 | 43 | > |
89 | - {file.name} | |
44 | + .. | |
90 | 45 | </a> |
91 | - </span> | |
92 | - <span class="hidden sm:block text-sm text-muted-foreground truncate max-w-[300px]"> | |
93 | - {file.lastCommit ? ( | |
46 | + </td> | |
47 | + <td class="p-4 py-2" /> | |
48 | + <td class="p-4 py-2" /> | |
49 | + </tr> | |
50 | + ) | |
51 | + } | |
52 | + { | |
53 | + files.length > 0 ? ( | |
54 | + files.map((file) => ( | |
55 | + <tr class="hover:bg-muted/50 transition-colors group"> | |
56 | + <td class="p-4 py-2.5 w-10"> | |
57 | + {file.type === "directory" ? ( | |
58 | + <svg | |
59 | + class="h-4 w-4 text-blue-500 fill-blue-500" | |
60 | + viewBox="0 0 16 16" | |
61 | + fill="currentColor" | |
62 | + > | |
63 | + <path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z" /> | |
64 | + </svg> | |
65 | + ) : ( | |
66 | + (() => { | |
67 | + const ext = file.name.split(".").pop()?.toLowerCase(); | |
68 | + let iconClass = "text-muted-foreground"; | |
69 | + if ( | |
70 | + ["js", "jsx", "ts", "tsx", "cjs", "mjs"].includes( | |
71 | + ext || "", | |
72 | + ) | |
73 | + ) | |
74 | + iconClass = "text-yellow-500"; | |
75 | + else if (["html", "htm"].includes(ext || "")) | |
76 | + iconClass = "text-orange-600"; | |
77 | + else if ( | |
78 | + ["css", "scss", "sass", "less"].includes(ext || "") | |
79 | + ) | |
80 | + iconClass = "text-blue-600"; | |
81 | + else if ( | |
82 | + ["json", "yml", "yaml", "toml"].includes(ext || "") | |
83 | + ) | |
84 | + iconClass = "text-yellow-600"; | |
85 | + else if (["md", "txt"].includes(ext || "")) | |
86 | + iconClass = "text-gray-400"; | |
87 | + else if ( | |
88 | + ["png", "jpg", "jpeg", "gif", "svg", "webp"].includes( | |
89 | + ext || "", | |
90 | + ) | |
91 | + ) | |
92 | + iconClass = "text-purple-500"; | |
93 | + else if (["py"].includes(ext || "")) | |
94 | + iconClass = "text-blue-500"; | |
95 | + else if (["go"].includes(ext || "")) | |
96 | + iconClass = "text-cyan-500"; | |
97 | + else if (["rs"].includes(ext || "")) | |
98 | + iconClass = "text-orange-500"; | |
99 | + else if (["java"].includes(ext || "")) | |
100 | + iconClass = "text-red-500"; | |
101 | + else if (["c", "cpp", "h", "hpp"].includes(ext || "")) | |
102 | + iconClass = "text-blue-700"; | |
103 | + else if (["sh", "bash", "zsh"].includes(ext || "")) | |
104 | + iconClass = "text-green-500"; | |
105 | + | |
106 | + return <FileText className={`h-4 w-4 ${iconClass}`} />; | |
107 | + })() | |
108 | + )} | |
109 | + </td> | |
110 | + <td class="p-4 py-2.5"> | |
94 | 111 | <a |
95 | - href={`/${owner}/${repo}/commit/${file.lastCommit.hash}`} | |
96 | - class="hover:text-primary hover:underline" | |
112 | + href={getUrl(file)} | |
113 | + class="hover:underline hover:text-primary text-foreground font-medium block" | |
97 | 114 | > |
98 | - {file.lastCommit.message} | |
115 | + {file.name} | |
99 | 116 | </a> |
100 | - ) : ( | |
101 | - <span class="animate-pulse bg-muted h-4 w-24 rounded inline-block" /> | |
102 | - )} | |
103 | - </span> | |
104 | - <span class="text-sm text-muted-foreground whitespace-nowrap w-24 text-right"> | |
105 | - {file.lastCommit?.date || ""} | |
106 | - </span> | |
107 | - </div> | |
108 | - )) | |
109 | - ) : ( | |
110 | - <div class="p-8 text-center text-muted-foreground"> | |
111 | - <p>This directory is empty.</p> | |
112 | - </div> | |
113 | - ) | |
114 | - } | |
115 | - </div> | |
117 | + </td> | |
118 | + <td class="p-4 py-2.5 text-muted-foreground max-w-md truncate"> | |
119 | + {file.lastCommit ? ( | |
120 | + <a | |
121 | + href={`/${owner}/${repo}/commit/${file.lastCommit.hash}`} | |
122 | + class="hover:text-primary hover:underline" | |
123 | + > | |
124 | + {file.lastCommit.message} | |
125 | + </a> | |
126 | + ) : ( | |
127 | + <span class="animate-pulse bg-muted h-4 w-24 rounded inline-block" /> | |
128 | + )} | |
129 | + </td> | |
130 | + <td class="p-4 py-2.5 text-right text-muted-foreground whitespace-nowrap text-xs"> | |
131 | + {file.lastCommit?.date || ""} | |
132 | + </td> | |
133 | + </tr> | |
134 | + )) | |
135 | + ) : ( | |
136 | + <tr> | |
137 | + <td colspan={4} class="p-8 text-center text-muted-foreground"> | |
138 | + This directory is empty. | |
139 | + </td> | |
140 | + </tr> | |
141 | + ) | |
142 | + } | |
143 | + </tbody> | |
144 | + </table> | |
116 | 145 | </div> |
117 | 146 | |
▼src/components/repo/RepoActions.tsx
+129-0
| @@ -0,0 +1,129 @@ | ||
1 | + | |
2 | +import * as React from "react"; | |
3 | +import { | |
4 | + Eye, | |
5 | + GitFork, | |
6 | + Star, | |
7 | + ChevronDown, | |
8 | + Loader2 | |
9 | +} from "lucide-react"; | |
10 | +import { Button } from "@/components/ui/button"; | |
11 | + | |
12 | +interface RepoActionsProps { | |
13 | + owner: string; | |
14 | + repo: string; | |
15 | + watchers: number; | |
16 | + forks: number; | |
17 | + stars: number; | |
18 | + isLoggedIn: boolean; | |
19 | +} | |
20 | + | |
21 | +export function RepoActions({ | |
22 | + owner, | |
23 | + repo, | |
24 | + watchers, | |
25 | + forks, | |
26 | + stars, | |
27 | + isLoggedIn | |
28 | +}: RepoActionsProps) { | |
29 | + const [isForking, setIsForking] = React.useState(false); | |
30 | + | |
31 | + const handleFork = async () => { | |
32 | + if (!isLoggedIn) { | |
33 | + window.location.href = "/login"; | |
34 | + return; | |
35 | + } | |
36 | + | |
37 | + setIsForking(true); | |
38 | + try { | |
39 | + const response = await fetch(`/api/repos/${owner}/${repo}/fork`, { | |
40 | + method: "POST", | |
41 | + }); | |
42 | + | |
43 | + const data = await response.json(); | |
44 | + | |
45 | + if (response.ok) { | |
46 | + window.location.href = data.fork.url; | |
47 | + } else if (response.status === 409) { | |
48 | + // Already forked - redirect to existing fork | |
49 | + window.location.href = `/${data.fork.owner}/${data.fork.name}`; | |
50 | + } else { | |
51 | + alert(data.message || "Failed to fork repository"); | |
52 | + setIsForking(false); | |
53 | + } | |
54 | + } catch (error) { | |
55 | + console.error("Fork error:", error); | |
56 | + alert("Failed to fork repository"); | |
57 | + setIsForking(false); | |
58 | + } | |
59 | + }; | |
60 | + | |
61 | + return ( | |
62 | + <div className="flex items-center gap-2 flex-wrap"> | |
63 | + {/* Watch */} | |
64 | + <div className="flex items-center rounded-lg border bg-background overflow-hidden shadow-sm"> | |
65 | + <Button | |
66 | + variant="ghost" | |
67 | + size="sm" | |
68 | + className="rounded-none border-r px-3 hover:bg-muted" | |
69 | + > | |
70 | + <Eye className="mr-2 h-4 w-4 text-muted-foreground" /> | |
71 | + Watch | |
72 | + <span className="ml-2 rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium"> | |
73 | + {watchers} | |
74 | + </span> | |
75 | + </Button> | |
76 | + <Button | |
77 | + variant="ghost" | |
78 | + size="sm" | |
79 | + className="px-2 rounded-none hover:bg-muted" | |
80 | + > | |
81 | + <ChevronDown className="h-4 w-4 text-muted-foreground" /> | |
82 | + </Button> | |
83 | + </div> | |
84 | + | |
85 | + {/* Fork */} | |
86 | + <div className="flex items-center rounded-lg border bg-background overflow-hidden shadow-sm"> | |
87 | + <Button | |
88 | + variant="ghost" | |
89 | + size="sm" | |
90 | + onClick={handleFork} | |
91 | + disabled={isForking} | |
92 | + className="rounded-none px-3 hover:bg-muted" | |
93 | + > | |
94 | + {isForking ? ( | |
95 | + <Loader2 className="mr-2 h-4 w-4 animate-spin" /> | |
96 | + ) : ( | |
97 | + <GitFork className="mr-2 h-4 w-4 text-muted-foreground" /> | |
98 | + )} | |
99 | + Fork | |
100 | + <span className="ml-2 rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium"> | |
101 | + {forks} | |
102 | + </span> | |
103 | + </Button> | |
104 | + </div> | |
105 | + | |
106 | + {/* Star */} | |
107 | + <div className="flex items-center rounded-lg border bg-background overflow-hidden shadow-sm"> | |
108 | + <Button | |
109 | + variant="ghost" | |
110 | + size="sm" | |
111 | + className="rounded-none border-r px-3 hover:bg-muted" | |
112 | + > | |
113 | + <Star className="mr-2 h-4 w-4 text-muted-foreground" /> | |
114 | + Star | |
115 | + <span className="ml-2 rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium"> | |
116 | + {stars} | |
117 | + </span> | |
118 | + </Button> | |
119 | + <Button | |
120 | + variant="ghost" | |
121 | + size="sm" | |
122 | + className="px-2 rounded-none hover:bg-muted" | |
123 | + > | |
124 | + <ChevronDown className="h-4 w-4 text-muted-foreground" /> | |
125 | + </Button> | |
126 | + </div> | |
127 | + </div> | |
128 | + ); | |
129 | +} | |
0 | 130 | |
▼src/components/repo/RepoControls.tsx
+181-0
| @@ -0,0 +1,181 @@ | ||
1 | + | |
2 | +import * as React from "react"; | |
3 | +import { | |
4 | + GitBranch, | |
5 | + ChevronDown, | |
6 | + Copy, | |
7 | + Check, | |
8 | + Download, | |
9 | + Code | |
10 | +} from "lucide-react"; | |
11 | + | |
12 | +import { cn } from "@/lib/utils"; | |
13 | +import { Button } from "@/components/ui/button"; | |
14 | +import { | |
15 | + Command, | |
16 | + CommandEmpty, | |
17 | + CommandGroup, | |
18 | + CommandInput, | |
19 | + CommandItem, | |
20 | + CommandList, | |
21 | +} from "@/components/ui/command"; | |
22 | +import { | |
23 | + Popover, | |
24 | + PopoverContent, | |
25 | + PopoverTrigger, | |
26 | +} from "@/components/ui/popover"; | |
27 | +import { | |
28 | + Tabs, | |
29 | + TabsContent, | |
30 | + TabsList, | |
31 | + TabsTrigger, | |
32 | +} from "@/components/ui/tabs"; | |
33 | +import { Input } from "@/components/ui/input"; | |
34 | + | |
35 | +interface RepoControlsProps { | |
36 | + owner: string; | |
37 | + repo: string; | |
38 | + currentBranch: string; | |
39 | + branches: string[]; | |
40 | + httpCloneUrl: string; | |
41 | + sshCloneUrl: string; | |
42 | +} | |
43 | + | |
44 | +export function RepoControls({ | |
45 | + owner, | |
46 | + repo, | |
47 | + currentBranch, | |
48 | + branches, | |
49 | + httpCloneUrl, | |
50 | + sshCloneUrl, | |
51 | +}: RepoControlsProps) { | |
52 | + const [openBranch, setOpenBranch] = React.useState(false); | |
53 | + const [openClone, setOpenClone] = React.useState(false); | |
54 | + const [copied, setCopied] = React.useState(false); | |
55 | + | |
56 | + const copyToClipboard = async (text: string) => { | |
57 | + await navigator.clipboard.writeText(text); | |
58 | + setCopied(true); | |
59 | + setTimeout(() => setCopied(false), 2000); | |
60 | + }; | |
61 | + | |
62 | + return ( | |
63 | + <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> | |
64 | + <div className="flex items-center gap-2"> | |
65 | + {/* Branch Selector */} | |
66 | + <Popover open={openBranch} onOpenChange={setOpenBranch}> | |
67 | + <PopoverTrigger asChild> | |
68 | + <Button | |
69 | + variant="outline" | |
70 | + role="combobox" | |
71 | + aria-expanded={openBranch} | |
72 | + className="justify-between min-w-[150px]" | |
73 | + > | |
74 | + <div className="flex items-center gap-2"> | |
75 | + <GitBranch className="h-4 w-4 opacity-50" /> | |
76 | + {currentBranch} | |
77 | + </div> | |
78 | + <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> | |
79 | + </Button> | |
80 | + </PopoverTrigger> | |
81 | + <PopoverContent className="w-[200px] p-0" align="start"> | |
82 | + <Command> | |
83 | + <CommandInput placeholder="Search branch..." /> | |
84 | + <CommandList> | |
85 | + <CommandEmpty>No branch found.</CommandEmpty> | |
86 | + <CommandGroup heading="Branches"> | |
87 | + {branches.map((branch) => ( | |
88 | + <CommandItem | |
89 | + key={branch} | |
90 | + value={branch} | |
91 | + onSelect={() => { | |
92 | + window.location.href = `/${owner}/${repo}/tree/${branch}`; | |
93 | + setOpenBranch(false); | |
94 | + }} | |
95 | + > | |
96 | + <Check | |
97 | + className={cn( | |
98 | + "mr-2 h-4 w-4", | |
99 | + currentBranch === branch ? "opacity-100" : "opacity-0" | |
100 | + )} | |
101 | + /> | |
102 | + {branch} | |
103 | + </CommandItem> | |
104 | + ))} | |
105 | + </CommandGroup> | |
106 | + </CommandList> | |
107 | + </Command> | |
108 | + </PopoverContent> | |
109 | + </Popover> | |
110 | + </div> | |
111 | + | |
112 | + <div className="flex items-center gap-2"> | |
113 | + {/* Clone Button */} | |
114 | + <Popover open={openClone} onOpenChange={setOpenClone}> | |
115 | + <PopoverTrigger asChild> | |
116 | + <Button className="gap-2 bg-green-600 hover:bg-green-700 text-white"> | |
117 | + <Code className="h-4 w-4" /> | |
118 | + Code | |
119 | + <ChevronDown className="h-4 w-4" /> | |
120 | + </Button> | |
121 | + </PopoverTrigger> | |
122 | + <PopoverContent className="w-[350px] p-4" align="end"> | |
123 | + <Tabs defaultValue="https" className="w-full"> | |
124 | + <TabsList className="w-full grid grid-cols-2"> | |
125 | + <TabsTrigger value="https">HTTPS</TabsTrigger> | |
126 | + <TabsTrigger value="ssh">SSH</TabsTrigger> | |
127 | + </TabsList> | |
128 | + | |
129 | + <div className="mt-4"> | |
130 | + <TabsContent value="https" className="mt-0"> | |
131 | + <div className="flex items-center gap-2"> | |
132 | + <Input readOnly value={httpCloneUrl} className="h-9 font-mono text-xs" /> | |
133 | + <Button | |
134 | + size="icon" | |
135 | + variant="outline" | |
136 | + className="h-9 w-9 shrink-0" | |
137 | + onClick={() => copyToClipboard(httpCloneUrl)} | |
138 | + > | |
139 | + {copied ? ( | |
140 | + <Check className="h-4 w-4 text-green-500" /> | |
141 | + ) : ( | |
142 | + <Copy className="h-4 w-4" /> | |
143 | + )} | |
144 | + </Button> | |
145 | + </div> | |
146 | + </TabsContent> | |
147 | + <TabsContent value="ssh" className="mt-0"> | |
148 | + <div className="flex items-center gap-2"> | |
149 | + <Input readOnly value={sshCloneUrl} className="h-9 font-mono text-xs" /> | |
150 | + <Button | |
151 | + size="icon" | |
152 | + variant="outline" | |
153 | + className="h-9 w-9 shrink-0" | |
154 | + onClick={() => copyToClipboard(sshCloneUrl)} | |
155 | + > | |
156 | + {copied ? ( | |
157 | + <Check className="h-4 w-4 text-green-500" /> | |
158 | + ) : ( | |
159 | + <Copy className="h-4 w-4" /> | |
160 | + )} | |
161 | + </Button> | |
162 | + </div> | |
163 | + </TabsContent> | |
164 | + </div> | |
165 | + | |
166 | + <div className="mt-4 border-t pt-4"> | |
167 | + <a | |
168 | + href={`/${owner}/${repo}/archive/refs/heads/${currentBranch}.zip`} | |
169 | + className="flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors" | |
170 | + > | |
171 | + <Download className="h-4 w-4" /> | |
172 | + Download ZIP | |
173 | + </a> | |
174 | + </div> | |
175 | + </Tabs> | |
176 | + </PopoverContent> | |
177 | + </Popover> | |
178 | + </div> | |
179 | + </div> | |
180 | + ); | |
181 | +} | |
0 | 182 | |
▼src/components/repo/RepoHeader.astro
+128-107
| @@ -1,22 +1,17 @@ | ||
1 | 1 | --- |
2 | 2 | import { |
3 | - GitBranch, | |
4 | - Star, | |
5 | - GitFork, | |
6 | - Eye, | |
7 | 3 | Code, |
8 | 4 | FileText, |
9 | 5 | Settings, |
10 | - Lock, | |
11 | - Globe, | |
12 | - PlayCircle, | |
13 | - Book, | |
14 | 6 | Shield, |
15 | 7 | BarChart2, |
16 | 8 | GitPullRequest, |
17 | 9 | Layout, |
18 | - ChevronDown, | |
10 | + PlayCircle, | |
11 | + Book, | |
19 | 12 | } from "lucide-react"; |
13 | +import { Badge } from "@/components/ui/badge"; | |
14 | +import { RepoActions } from "@/components/repo/RepoActions"; | |
20 | 15 | |
21 | 16 | interface Props { |
22 | 17 | repo: { |
| @@ -30,6 +25,9 @@ interface Props { | ||
30 | 25 | stars?: number; |
31 | 26 | openIssueCount?: number; |
32 | 27 | openPrCount?: number; |
28 | + website?: string | null; | |
29 | + license?: string | null; | |
30 | + updatedAt?: Date | string | number | null; | |
33 | 31 | }; |
34 | 32 | activeTab: |
35 | 33 | | "code" |
| @@ -44,6 +42,26 @@ interface Props { | ||
44 | 42 | } |
45 | 43 | |
46 | 44 | const { repo, activeTab } = Astro.props; |
45 | +const isLoggedIn = !!Astro.locals.user; | |
46 | + | |
47 | +// Helper for time ago | |
48 | +function timeAgo(date: Date | string | number) { | |
49 | + if (!date) return ""; | |
50 | + const seconds = Math.floor( | |
51 | + (new Date().getTime() - new Date(date).getTime()) / 1000, | |
52 | + ); | |
53 | + let interval = seconds / 31536000; | |
54 | + if (interval > 1) return Math.floor(interval) + "y ago"; | |
55 | + interval = seconds / 2592000; | |
56 | + if (interval > 1) return Math.floor(interval) + "mo ago"; | |
57 | + interval = seconds / 86400; | |
58 | + if (interval > 1) return Math.floor(interval) + "d ago"; | |
59 | + interval = seconds / 3600; | |
60 | + if (interval > 1) return Math.floor(interval) + "h ago"; | |
61 | + interval = seconds / 60; | |
62 | + if (interval > 1) return Math.floor(interval) + "m ago"; | |
63 | + return Math.floor(seconds) + "s ago"; | |
64 | +} | |
47 | 65 | |
48 | 66 | const tabs = [ |
49 | 67 | { |
| @@ -105,148 +123,151 @@ const tabs = [ | ||
105 | 123 | ]; |
106 | 124 | --- |
107 | 125 | |
108 | -<!-- Enhanced Repo Header --> | |
109 | 126 | <div class="mb-6"> |
110 | 127 | <!-- Top Section: Repo Name & Action Buttons --> |
111 | - <div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4 py-4 border-b border-white/10"> | |
128 | + <div | |
129 | + class="flex flex-col lg:flex-row lg:items-start justify-between gap-4 py-8 border-b" | |
130 | + > | |
112 | 131 | <!-- Repo Info --> |
113 | - <div class="space-y-2"> | |
114 | - <!-- Repo Name - BIGGER and more prominent --> | |
132 | + <div class="space-y-4 max-w-3xl"> | |
133 | + <!-- Repo Name --> | |
115 | 134 | <div class="flex items-center gap-3 flex-wrap"> |
116 | - <a | |
117 | - href={`/${repo.owner}`} | |
118 | - class="text-xl font-semibold text-cyan-400 hover:underline transition-colors" | |
135 | + <a | |
136 | + href={`/${repo.owner}`} | |
137 | + class="text-xl text-muted-foreground hover:text-foreground hover:underline transition-colors" | |
119 | 138 | > |
120 | 139 | {repo.owner} |
121 | 140 | </a> |
122 | - <span class="text-xl text-gray-500">/</span> | |
141 | + <span class="text-xl text-muted-foreground">/</span> | |
123 | 142 | <a |
124 | 143 | href={`/${repo.owner}/${repo.name}`} |
125 | - class="text-xl font-bold text-white hover:text-cyan-400 transition-colors" | |
144 | + class="text-xl font-bold hover:text-primary hover:underline transition-colors" | |
126 | 145 | > |
127 | 146 | {repo.name} |
128 | 147 | </a> |
129 | - <!-- Visibility Badge --> | |
130 | - <span | |
131 | - class="inline-flex items-center gap-1 rounded-full border border-white/20 bg-white/5 px-2.5 py-1 text-xs font-medium text-gray-400" | |
148 | + <Badge | |
149 | + variant="secondary" | |
150 | + className="rounded-full px-2.5 font-normal text-muted-foreground border-muted-foreground/30" | |
132 | 151 | > |
133 | - { | |
134 | - repo.visibility === "public" ? ( | |
135 | - <> | |
136 | - <Globe className="h-3 w-3 text-green-400" /> | |
137 | - <span>Public</span> | |
138 | - </> | |
139 | - ) : ( | |
140 | - <> | |
141 | - <Lock className="h-3 w-3 text-orange-400" /> | |
142 | - <span>Private</span> | |
143 | - </> | |
144 | - ) | |
145 | - } | |
146 | - </span> | |
152 | + {repo.visibility} | |
153 | + </Badge> | |
147 | 154 | </div> |
148 | - | |
155 | + | |
149 | 156 | <!-- Description --> |
150 | 157 | { |
151 | 158 | repo.description && ( |
152 | - <p class="text-gray-400 text-sm max-w-2xl">{repo.description}</p> | |
159 | + <p class="text-base text-foreground/80 leading-relaxed"> | |
160 | + {repo.description} | |
161 | + </p> | |
153 | 162 | ) |
154 | 163 | } |
155 | - | |
164 | + | |
165 | + <!-- Metadata Row --> | |
166 | + <div | |
167 | + class="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-muted-foreground" | |
168 | + > | |
169 | + { | |
170 | + repo.website && ( | |
171 | + <a | |
172 | + href={repo.website} | |
173 | + target="_blank" | |
174 | + rel="noopener noreferrer" | |
175 | + class="flex items-center gap-1.5 hover:text-primary transition-colors" | |
176 | + > | |
177 | + <svg | |
178 | + class="h-4 w-4" | |
179 | + fill="none" | |
180 | + viewBox="0 0 24 24" | |
181 | + stroke="currentColor" | |
182 | + > | |
183 | + <path | |
184 | + stroke-linecap="round" | |
185 | + stroke-linejoin="round" | |
186 | + stroke-width="2" | |
187 | + d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" | |
188 | + /> | |
189 | + </svg> | |
190 | + {repo.website} | |
191 | + </a> | |
192 | + ) | |
193 | + } | |
194 | + { | |
195 | + repo.license && ( | |
196 | + <div class="flex items-center gap-1.5"> | |
197 | + <svg | |
198 | + class="h-4 w-4" | |
199 | + fill="none" | |
200 | + viewBox="0 0 24 24" | |
201 | + stroke="currentColor" | |
202 | + > | |
203 | + <path | |
204 | + stroke-linecap="round" | |
205 | + stroke-linejoin="round" | |
206 | + stroke-width="2" | |
207 | + d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3" | |
208 | + /> | |
209 | + </svg> | |
210 | + {repo.license} license | |
211 | + </div> | |
212 | + ) | |
213 | + } | |
214 | + {repo.updatedAt && <div>Updated {timeAgo(repo.updatedAt)}</div>} | |
215 | + </div> | |
216 | + | |
156 | 217 | <!-- Topics --> |
157 | 218 | { |
158 | 219 | repo.topics && repo.topics.length > 0 && ( |
159 | - <div class="flex flex-wrap gap-2 mt-2"> | |
220 | + <div class="flex flex-wrap gap-2"> | |
160 | 221 | {repo.topics.map((topic: string) => ( |
161 | - <a | |
162 | - href={`/topics/${topic}`} | |
163 | - class="inline-flex items-center rounded-full bg-cyan-500/10 border border-cyan-500/20 px-2.5 py-0.5 text-xs font-medium text-cyan-400 hover:bg-cyan-500/20 transition-colors" | |
222 | + <Badge | |
223 | + variant="secondary" | |
224 | + className="rounded-full hover:bg-primary/20 hover:text-primary cursor-pointer transition-colors" | |
164 | 225 | > |
165 | - {topic} | |
166 | - </a> | |
226 | + <a href={`/topics/${topic}`}>{topic}</a> | |
227 | + </Badge> | |
167 | 228 | ))} |
168 | 229 | </div> |
169 | 230 | ) |
170 | 231 | } |
171 | 232 | </div> |
172 | - | |
233 | + | |
173 | 234 | <!-- Action Buttons --> |
174 | - <div class="flex items-center gap-2 flex-wrap"> | |
175 | - <!-- Watch --> | |
176 | - <div class="flex items-center rounded-lg border border-white/10 bg-white/5 overflow-hidden"> | |
177 | - <button | |
178 | - class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/10 transition-colors" | |
179 | - > | |
180 | - <Eye className="h-4 w-4" /> | |
181 | - Watch | |
182 | - <span class="ml-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-cyan-400"> | |
183 | - {repo.watchers || 0} | |
184 | - </span> | |
185 | - </button> | |
186 | - <div class="w-px h-6 bg-white/10"></div> | |
187 | - <button class="px-2 py-2 hover:bg-white/10 transition-colors text-gray-400"> | |
188 | - <ChevronDown className="h-4 w-4" /> | |
189 | - </button> | |
190 | - </div> | |
191 | - | |
192 | - <!-- Fork --> | |
193 | - <div class="flex items-center rounded-lg border border-white/10 bg-white/5 overflow-hidden"> | |
194 | - <button | |
195 | - id="fork-btn" | |
196 | - data-owner={repo.owner} | |
197 | - data-repo={repo.name} | |
198 | - class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/10 transition-colors" | |
199 | - > | |
200 | - <GitFork className="h-4 w-4" /> | |
201 | - Fork | |
202 | - <span class="ml-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-purple-400"> | |
203 | - {repo.forks || 0} | |
204 | - </span> | |
205 | - </button> | |
206 | - </div> | |
207 | - | |
208 | - <!-- Star --> | |
209 | - <div class="flex items-center rounded-lg border border-white/10 bg-white/5 overflow-hidden"> | |
210 | - <button | |
211 | - class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/10 transition-colors" | |
212 | - > | |
213 | - <Star className="h-4 w-4" /> | |
214 | - Star | |
215 | - <span class="ml-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-yellow-400"> | |
216 | - {repo.stars || 0} | |
217 | - </span> | |
218 | - </button> | |
219 | - <div class="w-px h-6 bg-white/10"></div> | |
220 | - <button class="px-2 py-2 hover:bg-white/10 transition-colors text-gray-400"> | |
221 | - <ChevronDown className="h-4 w-4" /> | |
222 | - </button> | |
223 | - </div> | |
224 | - </div> | |
235 | + <RepoActions | |
236 | + client:load | |
237 | + owner={repo.owner} | |
238 | + repo={repo.name} | |
239 | + watchers={repo.watchers || 0} | |
240 | + forks={repo.forks || 0} | |
241 | + stars={repo.stars || 0} | |
242 | + isLoggedIn={isLoggedIn} | |
243 | + /> | |
225 | 244 | </div> |
226 | 245 | |
227 | 246 | <!-- Navigation Tabs --> |
228 | - <div class="overflow-x-auto -mx-4 px-4"> | |
229 | - <nav class="flex gap-1 pt-3 min-w-max"> | |
247 | + <div class="overflow-x-auto -mx-4 px-4 pt-4"> | |
248 | + <nav class="flex gap-1 min-w-max border-b border-transparent"> | |
230 | 249 | { |
231 | 250 | tabs.map((tab) => ( |
232 | 251 | <a |
233 | 252 | href={tab.href} |
234 | 253 | class:list={[ |
235 | - "inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all", | |
254 | + "inline-flex items-center gap-2 px-4 py-2.5 rounded-t-lg text-sm font-medium border-b-2 transition-all", | |
236 | 255 | activeTab === tab.id |
237 | - ? "bg-white/10 text-white border border-white/10" | |
238 | - : "text-gray-400 hover:text-white hover:bg-white/5", | |
256 | + ? "border-primary text-foreground bg-muted/40" | |
257 | + : "border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground/30", | |
239 | 258 | ]} |
240 | 259 | > |
241 | 260 | <tab.icon className="h-4 w-4" /> |
242 | 261 | {tab.label} |
243 | 262 | {tab.count !== undefined && tab.count > 0 && ( |
244 | - <span class:list={[ | |
245 | - "rounded-full px-2 py-0.5 text-xs font-medium", | |
246 | - activeTab === tab.id | |
247 | - ? "bg-cyan-500/20 text-cyan-400" | |
248 | - : "bg-white/10 text-gray-400" | |
249 | - ]}> | |
263 | + <span | |
264 | + class:list={[ | |
265 | + "rounded-full px-2 py-0.5 text-xs font-medium", | |
266 | + activeTab === tab.id | |
267 | + ? "bg-primary/10 text-primary" | |
268 | + : "bg-muted text-muted-foreground", | |
269 | + ]} | |
270 | + > | |
250 | 271 | {tab.count} |
251 | 272 | </span> |
252 | 273 | )} |
253 | 274 | |
▼src/components/ui/popover.tsx
+29-0
| @@ -0,0 +1,29 @@ | ||
1 | +import * as React from "react" | |
2 | +import * as PopoverPrimitive from "@radix-ui/react-popover" | |
3 | + | |
4 | +import { cn } from "@/lib/utils" | |
5 | + | |
6 | +const Popover = PopoverPrimitive.Root | |
7 | + | |
8 | +const PopoverTrigger = PopoverPrimitive.Trigger | |
9 | + | |
10 | +const PopoverContent = React.forwardRef< | |
11 | + React.ElementRef<typeof PopoverPrimitive.Content>, | |
12 | + React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> | |
13 | +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( | |
14 | + <PopoverPrimitive.Portal> | |
15 | + <PopoverPrimitive.Content | |
16 | + ref={ref} | |
17 | + align={align} | |
18 | + sideOffset={sideOffset} | |
19 | + className={cn( | |
20 | + "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", | |
21 | + className | |
22 | + )} | |
23 | + {...props} | |
24 | + /> | |
25 | + </PopoverPrimitive.Portal> | |
26 | +)) | |
27 | +PopoverContent.displayName = PopoverPrimitive.Content.displayName | |
28 | + | |
29 | +export { Popover, PopoverTrigger, PopoverContent } | |
0 | 30 | |
▼src/components/ui/table.tsx
+116-0
| @@ -0,0 +1,116 @@ | ||
1 | +import * as React from "react" | |
2 | +import { cn } from "@/lib/utils" | |
3 | + | |
4 | +const Table = React.forwardRef< | |
5 | + HTMLTableElement, | |
6 | + React.HTMLAttributes<HTMLTableElement> | |
7 | +>(({ className, ...props }, ref) => ( | |
8 | + <div className="relative w-full overflow-auto"> | |
9 | + <table | |
10 | + ref={ref} | |
11 | + className={cn("w-full caption-bottom text-sm", className)} | |
12 | + {...props} | |
13 | + /> | |
14 | + </div> | |
15 | +)) | |
16 | +Table.displayName = "Table" | |
17 | + | |
18 | +const TableHeader = React.forwardRef< | |
19 | + HTMLTableSectionElement, | |
20 | + React.HTMLAttributes<HTMLTableSectionElement> | |
21 | +>(({ className, ...props }, ref) => ( | |
22 | + <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} /> | |
23 | +)) | |
24 | +TableHeader.displayName = "TableHeader" | |
25 | + | |
26 | +const TableBody = React.forwardRef< | |
27 | + HTMLTableSectionElement, | |
28 | + React.HTMLAttributes<HTMLTableSectionElement> | |
29 | +>(({ className, ...props }, ref) => ( | |
30 | + <tbody | |
31 | + ref={ref} | |
32 | + className={cn("[&_tr:last-child]:border-0", className)} | |
33 | + {...props} | |
34 | + /> | |
35 | +)) | |
36 | +TableBody.displayName = "TableBody" | |
37 | + | |
38 | +const TableFooter = React.forwardRef< | |
39 | + HTMLTableSectionElement, | |
40 | + React.HTMLAttributes<HTMLTableSectionElement> | |
41 | +>(({ className, ...props }, ref) => ( | |
42 | + <tfoot | |
43 | + ref={ref} | |
44 | + className={cn( | |
45 | + "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", | |
46 | + className | |
47 | + )} | |
48 | + {...props} | |
49 | + /> | |
50 | +)) | |
51 | +TableFooter.displayName = "TableFooter" | |
52 | + | |
53 | +const TableRow = React.forwardRef< | |
54 | + HTMLTableRowElement, | |
55 | + React.HTMLAttributes<HTMLTableRowElement> | |
56 | +>(({ className, ...props }, ref) => ( | |
57 | + <tr | |
58 | + ref={ref} | |
59 | + className={cn( | |
60 | + "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", | |
61 | + className | |
62 | + )} | |
63 | + {...props} | |
64 | + /> | |
65 | +)) | |
66 | +TableRow.displayName = "TableRow" | |
67 | + | |
68 | +const TableHead = React.forwardRef< | |
69 | + HTMLTableCellElement, | |
70 | + React.ThHTMLAttributes<HTMLTableCellElement> | |
71 | +>(({ className, ...props }, ref) => ( | |
72 | + <th | |
73 | + ref={ref} | |
74 | + className={cn( | |
75 | + "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", | |
76 | + className | |
77 | + )} | |
78 | + {...props} | |
79 | + /> | |
80 | +)) | |
81 | +TableHead.displayName = "TableHead" | |
82 | + | |
83 | +const TableCell = React.forwardRef< | |
84 | + HTMLTableCellElement, | |
85 | + React.TdHTMLAttributes<HTMLTableCellElement> | |
86 | +>(({ className, ...props }, ref) => ( | |
87 | + <td | |
88 | + ref={ref} | |
89 | + className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} | |
90 | + {...props} | |
91 | + /> | |
92 | +)) | |
93 | +TableCell.displayName = "TableCell" | |
94 | + | |
95 | +const TableCaption = React.forwardRef< | |
96 | + HTMLTableCaptionElement, | |
97 | + React.HTMLAttributes<HTMLTableCaptionElement> | |
98 | +>(({ className, ...props }, ref) => ( | |
99 | + <caption | |
100 | + ref={ref} | |
101 | + className={cn("mt-4 text-sm text-muted-foreground", className)} | |
102 | + {...props} | |
103 | + /> | |
104 | +)) | |
105 | +TableCaption.displayName = "TableCaption" | |
106 | + | |
107 | +export { | |
108 | + Table, | |
109 | + TableHeader, | |
110 | + TableBody, | |
111 | + TableFooter, | |
112 | + TableHead, | |
113 | + TableRow, | |
114 | + TableCell, | |
115 | + TableCaption, | |
116 | +} | |
0 | 117 | |
▼src/lib/git.ts
+22-0
| @@ -606,6 +606,28 @@ export async function getCommits( | ||
606 | 606 | } |
607 | 607 | } |
608 | 608 | |
609 | +/** | |
610 | + * Get total commit count | |
611 | + */ | |
612 | +export async function getCommitCount( | |
613 | + repoPath: string, | |
614 | + ref: string = "HEAD" | |
615 | +): Promise<number> { | |
616 | + const git = getGit(repoPath); | |
617 | + | |
618 | + try { | |
619 | + // Check if repository is empty first | |
620 | + if (await isRepoEmpty(repoPath)) { | |
621 | + return 0; | |
622 | + } | |
623 | + | |
624 | + const count = await git.raw(["rev-list", "--count", ref]); | |
625 | + return parseInt(count.trim(), 10); | |
626 | + } catch (error) { | |
627 | + return 0; | |
628 | + } | |
629 | +} | |
630 | + | |
609 | 631 | /** |
610 | 632 | * Get a single commit |
611 | 633 | */ |
612 | 634 | |
▼src/pages/[owner]/[repo]/index.astro
+75-254
| @@ -19,12 +19,15 @@ import { | ||
19 | 19 | getCommits, |
20 | 20 | getFileContent, |
21 | 21 | getBranches, |
22 | + getCommitCount, | |
22 | 23 | } from "@/lib/git"; |
23 | 24 | import { resolveRepoPath } from "@/lib/git-storage"; |
24 | 25 | import { canReadRepo } from "@/lib/permissions"; |
25 | 26 | import { renderMarkdown } from "@/lib/markdown"; |
26 | 27 | import RepoHeader from "@/components/repo/RepoHeader.astro"; |
27 | 28 | import FileExplorer from "@/components/repo/FileExplorer.astro"; |
29 | +import { RepoControls } from "@/components/repo/RepoControls"; | |
30 | +import { Card } from "@/components/ui/card"; | |
28 | 31 | |
29 | 32 | const { owner: ownerName, repo: repoName } = Astro.params; |
30 | 33 | const db = getDatabase(); |
| @@ -77,6 +80,7 @@ const currentBranch = repoData.defaultBranch; | ||
77 | 80 | const currentPath = ""; |
78 | 81 | let files: any[] = []; |
79 | 82 | let latestCommit: any = null; |
83 | +let totalCommits = 0; | |
80 | 84 | let readme = ""; |
81 | 85 | |
82 | 86 | // Format dates helper |
| @@ -148,6 +152,9 @@ try { | ||
148 | 152 | latestCommit = commits[0]; |
149 | 153 | } |
150 | 154 | |
155 | + // Get total commit count | |
156 | + totalCommits = await getCommitCount(resolvedLocalPath, currentBranch); | |
157 | + | |
151 | 158 | // Get README |
152 | 159 | const readmeFile = files.find((f) => f.name.toLowerCase() === "readme.md"); |
153 | 160 | if (readmeFile) { |
| @@ -203,183 +210,102 @@ const repo = { | ||
203 | 210 | <!-- Main Content --> |
204 | 211 | <div class="lg:col-span-3 space-y-4"> |
205 | 212 | <!-- Branch/Clone Bar --> |
206 | - <div | |
207 | - class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between" | |
208 | - > | |
209 | - <div class="flex items-center gap-2"> | |
210 | - <!-- Branch Selector --> | |
211 | - <div class="relative"> | |
212 | - <button | |
213 | - class="inline-flex items-center gap-2 rounded-md border bg-background px-3 py-1.5 text-sm font-medium shadow-sm hover:bg-accent" | |
214 | - id="branch-btn" | |
215 | - > | |
216 | - <GitBranch className="h-4 w-4" /> | |
217 | - {currentBranch} | |
218 | - <ChevronDown className="h-4 w-4" /> | |
219 | - </button> | |
220 | - <div | |
221 | - id="branch-dropdown" | |
222 | - class="hidden absolute left-0 top-full mt-1 w-64 rounded-md border bg-popover p-2 shadow-lg z-10" | |
223 | - > | |
224 | - <input | |
225 | - type="search" | |
226 | - placeholder="Find a branch..." | |
227 | - class="w-full mb-2 h-8 rounded-md border bg-background px-3 text-sm" | |
228 | - /> | |
229 | - <div class="max-h-64 overflow-y-auto"> | |
230 | - <div | |
231 | - class="text-xs font-semibold text-muted-foreground px-2 py-1" | |
232 | - > | |
233 | - Branches | |
213 | + <RepoControls | |
214 | + client:load | |
215 | + owner={repo.owner} | |
216 | + repo={repo.name} | |
217 | + currentBranch={currentBranch} | |
218 | + branches={branches} | |
219 | + httpCloneUrl={repo.httpCloneUrl || | |
220 | + `http://localhost:3000/${repo.owner}/${repo.name}.git`} | |
221 | + sshCloneUrl={repo.sshCloneUrl || | |
222 | + `git@localhost:${repo.owner}/${repo.name}.git`} | |
223 | + /> | |
224 | + | |
225 | + <!-- File Browser --> | |
226 | + <div class="space-y-4"> | |
227 | + <!-- Latest Commit --> | |
228 | + { | |
229 | + latestCommit && latestCommit.authorName ? ( | |
230 | + <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 rounded-lg border bg-card p-4 text-sm shadow-sm"> | |
231 | + <div class="flex items-center gap-3 overflow-hidden"> | |
232 | + <div class="h-8 w-8 shrink-0 rounded-full bg-muted flex items-center justify-center border text-xs font-medium"> | |
233 | + {latestCommit.authorName[0]?.toUpperCase()} | |
234 | 234 | </div> |
235 | - { | |
236 | - branches.map((b) => ( | |
235 | + <div class="flex flex-col min-w-0"> | |
236 | + <div class="flex items-center gap-2 truncate"> | |
237 | 237 | <a |
238 | - href={`/${repo.owner}/${repo.name}/tree/${b}`} | |
239 | - class={`flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent ${b === currentBranch ? "bg-accent" : ""}`} | |
238 | + href="#" | |
239 | + class="font-semibold hover:text-primary hover:underline truncate" | |
240 | 240 | > |
241 | - {b === currentBranch && ( | |
242 | - <span class="text-primary">✓</span> | |
243 | - )} | |
244 | - {b} | |
241 | + {latestCommit.authorName} | |
245 | 242 | </a> |
246 | - )) | |
247 | - } | |
248 | - </div> | |
249 | - </div> | |
250 | - </div> | |
251 | - </div> | |
252 | - <div class="flex items-center gap-2"> | |
253 | - <!-- Clone Button --> | |
254 | - <div class="relative"> | |
255 | - <button | |
256 | - class="inline-flex items-center gap-2 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90" | |
257 | - id="clone-btn" | |
258 | - > | |
259 | - <Code className="h-4 w-4" /> | |
260 | - Code | |
261 | - <ChevronDown className="h-4 w-4" /> | |
262 | - </button> | |
263 | - <div | |
264 | - id="clone-dropdown" | |
265 | - class="hidden absolute right-0 top-full mt-1 w-80 rounded-md border bg-popover p-4 shadow-lg z-10" | |
266 | - > | |
267 | - <div class="space-y-4"> | |
268 | - <div> | |
269 | - <div class="flex items-center gap-2 mb-2"> | |
270 | - <button | |
271 | - id="https-tab" | |
272 | - class="text-sm font-medium border-b-2 border-primary pb-1" | |
273 | - >HTTPS</button | |
274 | - > | |
275 | - <button | |
276 | - id="ssh-tab" | |
277 | - class="text-sm font-medium text-muted-foreground pb-1 border-b-2 border-transparent" | |
278 | - >SSH</button | |
279 | - > | |
243 | + <span class="text-muted-foreground truncate hidden sm:inline"> | |
244 | + {latestCommit.message} | |
245 | + </span> | |
280 | 246 | </div> |
281 | - <div class="flex items-center gap-2"> | |
282 | - <input | |
283 | - type="text" | |
284 | - readonly | |
285 | - id="clone-url" | |
286 | - value={repo.httpCloneUrl || | |
287 | - `http://localhost:3000/${repo.owner}/${repo.name}.git`} | |
288 | - data-https={repo.httpCloneUrl || | |
289 | - `http://localhost:3000/${repo.owner}/${repo.name}.git`} | |
290 | - data-ssh={repo.sshCloneUrl || | |
291 | - `git@localhost:${repo.owner}/${repo.name}.git`} | |
292 | - class="flex-1 h-8 rounded-md border bg-muted px-2 text-xs font-mono" | |
293 | - /> | |
294 | - <button | |
295 | - id="copy-url-btn" | |
296 | - class="inline-flex items-center justify-center h-8 w-8 rounded-md border hover:bg-accent" | |
297 | - title="Copy to clipboard" | |
298 | - > | |
299 | - <Copy className="h-4 w-4" /> | |
300 | - </button> | |
247 | + <div class="flex sm:hidden text-muted-foreground truncate text-xs"> | |
248 | + {latestCommit.message} | |
249 | + </div> | |
250 | + <div class="flex items-center gap-2 text-xs text-muted-foreground"> | |
251 | + <span>committed {timeAgo(latestCommit.authorDate)}</span> | |
301 | 252 | </div> |
302 | - </div> | |
303 | - <div class="border-t pt-4 space-y-2"> | |
304 | - <a | |
305 | - href={`/${repo.owner}/${repo.name}/archive/refs/heads/${currentBranch}.zip`} | |
306 | - class="flex items-center gap-2 text-sm hover:text-primary" | |
307 | - > | |
308 | - <Download className="h-4 w-4" /> | |
309 | - Download ZIP | |
310 | - </a> | |
311 | 253 | </div> |
312 | 254 | </div> |
313 | - </div> | |
314 | - </div> | |
315 | - </div> | |
316 | - </div> | |
317 | 255 | |
318 | - <!-- File Browser --> | |
319 | - <div class="space-y-4"> | |
320 | - <!-- Latest Commit --> | |
321 | - { | |
322 | - latestCommit && latestCommit.authorName ? ( | |
323 | - <div class="flex items-center gap-4 rounded-lg border bg-muted/30 p-4"> | |
324 | - <div class="h-8 w-8 rounded-full bg-muted flex items-center justify-center"> | |
325 | - <span class="text-sm font-medium"> | |
326 | - {latestCommit.authorName[0].toUpperCase()} | |
327 | - </span> | |
328 | - </div> | |
329 | - <div class="flex-1 min-w-0"> | |
330 | - <div class="flex items-center gap-2 text-sm"> | |
331 | - <a href="#" class="font-medium hover:text-primary"> | |
332 | - {latestCommit.authorName} | |
333 | - </a> | |
334 | - <a href="#" class="truncate hover:text-primary"> | |
335 | - {latestCommit.message} | |
336 | - </a> | |
337 | - </div> | |
338 | - <div class="flex items-center gap-2 text-xs text-muted-foreground"> | |
339 | - <a href="#" class="font-mono hover:text-primary"> | |
256 | + <div class="flex items-center gap-3 shrink-0 ml-11 sm:ml-0"> | |
257 | + <div class="flex items-center border rounded-md overflow-hidden bg-muted/30"> | |
258 | + <a | |
259 | + href={`/${repo.owner}/${repo.name}/commit/${latestCommit.sha}`} | |
260 | + class="px-3 py-1.5 hover:bg-muted hover:text-primary font-mono text-xs transition-colors border-r" | |
261 | + > | |
340 | 262 | {latestCommit.sha.substring(0, 7)} |
341 | 263 | </a> |
342 | - <span>·</span> | |
343 | - <span>committed {timeAgo(latestCommit.authorDate)}</span> | |
264 | + <a | |
265 | + href={`/${repo.owner}/${repo.name}/commits/${currentBranch}`} | |
266 | + class="px-3 py-1.5 hover:bg-muted hover:text-primary text-xs flex items-center gap-1.5 transition-colors" | |
267 | + > | |
268 | + <Clock className="h-3.5 w-3.5" /> | |
269 | + <span class="font-medium">{totalCommits}</span> | |
270 | + <span class="sr-only">commits</span> | |
271 | + </a> | |
344 | 272 | </div> |
345 | 273 | </div> |
346 | - <a | |
347 | - href={`/${repo.owner}/${repo.name}/commits/${currentBranch}`} | |
348 | - class="text-sm text-muted-foreground hover:text-primary whitespace-nowrap" | |
349 | - > | |
350 | - <GitCommit className="h-4 w-4 inline mr-1" /> | |
351 | - History | |
352 | - </a> | |
353 | 274 | </div> |
354 | 275 | ) : ( |
355 | - <div class="p-4 text-sm text-muted-foreground border rounded-lg"> | |
356 | - No commits yet. | |
357 | - </div> | |
276 | + <Card className="p-8 text-center text-muted-foreground border-dashed"> | |
277 | + <div class="flex flex-col items-center gap-2"> | |
278 | + <GitBranch className="h-8 w-8 opacity-50 mb-2" /> | |
279 | + <p>No commits yet.</p> | |
280 | + </div> | |
281 | + </Card> | |
358 | 282 | ) |
359 | 283 | } |
360 | 284 | |
361 | 285 | <!-- File List --> |
362 | - <FileExplorer | |
363 | - owner={repo.owner} | |
364 | - repo={repo.name} | |
365 | - branch={currentBranch} | |
366 | - path={currentPath} | |
367 | - files={files} | |
368 | - /> | |
286 | + <Card className="overflow-hidden"> | |
287 | + <FileExplorer | |
288 | + owner={repo.owner} | |
289 | + repo={repo.name} | |
290 | + branch={currentBranch} | |
291 | + path={currentPath} | |
292 | + files={files} | |
293 | + /> | |
294 | + </Card> | |
369 | 295 | |
370 | 296 | <!-- README --> |
371 | 297 | { |
372 | 298 | readme && ( |
373 | - <div class="rounded-lg border mt-8"> | |
299 | + <Card className="mt-8 overflow-hidden"> | |
374 | 300 | <div class="flex items-center gap-2 border-b px-4 py-3 bg-muted/30"> |
375 | - <FileText className="h-4 w-4" /> | |
376 | - <span class="font-medium">README.md</span> | |
301 | + <FileText className="h-4 w-4 text-muted-foreground" /> | |
302 | + <span class="font-medium text-sm">README.md</span> | |
377 | 303 | </div> |
378 | 304 | <div |
379 | - class="p-6 prose dark:prose-invert max-w-none" | |
305 | + class="p-6 prose prose-sm dark:prose-invert max-w-none prose-headings:border-b prose-headings:pb-2 prose-img:rounded-lg" | |
380 | 306 | set:html={readme} |
381 | 307 | /> |
382 | - </div> | |
308 | + </Card> | |
383 | 309 | ) |
384 | 310 | } |
385 | 311 | </div> |
| @@ -524,108 +450,3 @@ const repo = { | ||
524 | 450 | </div> |
525 | 451 | </div> |
526 | 452 | </BaseLayout> |
527 | - | |
528 | -<script> | |
529 | - // Branch dropdown | |
530 | - const branchBtn = document.getElementById("branch-btn"); | |
531 | - const branchDropdown = document.getElementById("branch-dropdown"); | |
532 | - | |
533 | - branchBtn?.addEventListener("click", (e) => { | |
534 | - e.stopPropagation(); | |
535 | - branchDropdown?.classList.toggle("hidden"); | |
536 | - document.getElementById("clone-dropdown")?.classList.add("hidden"); | |
537 | - }); | |
538 | - | |
539 | - // Clone dropdown | |
540 | - const cloneBtn = document.getElementById("clone-btn"); | |
541 | - const cloneDropdown = document.getElementById("clone-dropdown"); | |
542 | - | |
543 | - cloneBtn?.addEventListener("click", (e) => { | |
544 | - e.stopPropagation(); | |
545 | - cloneDropdown?.classList.toggle("hidden"); | |
546 | - branchDropdown?.classList.add("hidden"); | |
547 | - }); | |
548 | - | |
549 | - // Close dropdowns when clicking outside | |
550 | - document.addEventListener("click", () => { | |
551 | - branchDropdown?.classList.add("hidden"); | |
552 | - cloneDropdown?.classList.add("hidden"); | |
553 | - }); | |
554 | - | |
555 | - // HTTPS/SSH tab switching | |
556 | - const httpsTab = document.getElementById("https-tab"); | |
557 | - const sshTab = document.getElementById("ssh-tab"); | |
558 | - const cloneUrl = document.getElementById("clone-url") as HTMLInputElement; | |
559 | - | |
560 | - httpsTab?.addEventListener("click", (e) => { | |
561 | - e.stopPropagation(); | |
562 | - if (cloneUrl) { | |
563 | - cloneUrl.value = cloneUrl.dataset.https || ""; | |
564 | - } | |
565 | - httpsTab.classList.add("border-primary"); | |
566 | - httpsTab.classList.remove("text-muted-foreground", "border-transparent"); | |
567 | - sshTab?.classList.remove("border-primary"); | |
568 | - sshTab?.classList.add("text-muted-foreground", "border-transparent"); | |
569 | - }); | |
570 | - | |
571 | - sshTab?.addEventListener("click", (e) => { | |
572 | - e.stopPropagation(); | |
573 | - if (cloneUrl) { | |
574 | - cloneUrl.value = cloneUrl.dataset.ssh || ""; | |
575 | - } | |
576 | - sshTab.classList.add("border-primary"); | |
577 | - sshTab.classList.remove("text-muted-foreground", "border-transparent"); | |
578 | - httpsTab?.classList.remove("border-primary"); | |
579 | - httpsTab?.classList.add("text-muted-foreground", "border-transparent"); | |
580 | - }); | |
581 | - | |
582 | - // Copy URL to clipboard | |
583 | - const copyBtn = document.getElementById("copy-url-btn"); | |
584 | - copyBtn?.addEventListener("click", async (e) => { | |
585 | - e.stopPropagation(); | |
586 | - if (cloneUrl) { | |
587 | - await navigator.clipboard.writeText(cloneUrl.value); | |
588 | - copyBtn.innerHTML = | |
589 | - '<svg class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>'; | |
590 | - setTimeout(() => { | |
591 | - copyBtn.innerHTML = | |
592 | - '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><rect x="9" y="9" width="13" height="13" rx="2" ry="2" stroke-width="2"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>'; | |
593 | - }, 2000); | |
594 | - } | |
595 | - }); | |
596 | - | |
597 | - // Fork button | |
598 | - const forkBtn = document.getElementById("fork-btn") as HTMLButtonElement; | |
599 | - forkBtn?.addEventListener("click", async () => { | |
600 | - const owner = forkBtn.dataset.owner; | |
601 | - const repo = forkBtn.dataset.repo; | |
602 | - | |
603 | - forkBtn.disabled = true; | |
604 | - forkBtn.innerHTML = | |
605 | - '<svg class="animate-spin h-4 w-4 mr-2" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Forking...'; | |
606 | - | |
607 | - try { | |
608 | - const response = await fetch(`/api/repos/${owner}/${repo}/fork`, { | |
609 | - method: "POST", | |
610 | - }); | |
611 | - | |
612 | - const data = await response.json(); | |
613 | - | |
614 | - if (response.ok) { | |
615 | - window.location.href = data.fork.url; | |
616 | - } else if (response.status === 409) { | |
617 | - // Already forked - redirect to existing fork | |
618 | - window.location.href = `/${data.fork.owner}/${data.fork.name}`; | |
619 | - } else { | |
620 | - alert(data.message || "Failed to fork repository"); | |
621 | - forkBtn.disabled = false; | |
622 | - forkBtn.innerHTML = | |
623 | - '<svg class="h-4 w-4 mr-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 5C7 3.89543 7.89543 3 9 3C10.1046 3 11 3.89543 11 5C11 5.74028 10.5978 6.38663 10 6.73244V14.0396H14.5M17 5C17 3.89543 16.1046 3 15 3C13.8954 3 13 3.89543 13 5C13 5.74028 13.4022 6.38663 14 6.73244V8.5M14.5 14.0396C14.5 15.1441 15.3954 16.0396 16.5 16.0396C17.6046 16.0396 18.5 15.1441 18.5 14.0396C18.5 12.935 17.6046 12.0396 16.5 12.0396C15.3954 12.0396 14.5 12.935 14.5 14.0396Z"></path></svg>Fork'; | |
624 | - } | |
625 | - } catch (error) { | |
626 | - console.error("Fork error:", error); | |
627 | - alert("Failed to fork repository"); | |
628 | - forkBtn.disabled = false; | |
629 | - } | |
630 | - }); | |
631 | -</script> | |
632 | 453 | |