8630af3
commit 8630af3
31 files changed
+7667 -237
Showing 31 changed files+7667-237
.gitignoreDEPLOYMENT.mdREADME.mdREADME.md.backupREADME_NEW.mdVercel Deploy.mdbun.lockcli/README.mddocs/features/ai-review.mddocs/features/merge-queue.mddocs/features/stacked-prs.mddocs/index.mddocs/tutorials/your-first-stack.mddrizzle.config.tspackage.jsonsrc/components/home/FeatureDemosSection.tsxsrc/db/index.tssrc/db/schema/activity.tssrc/db/schema/developer-metrics.tssrc/db/schema/index.tssrc/db/schema/issues.tssrc/db/schema/organizations.tssrc/db/schema/pull-requests.tssrc/db/schema/slack-integration.tssrc/pages/[owner]/[repo]/index.astrosrc/pages/admin/_metrics-disabled.txtsrc/pages/admin/_storage-disabled.txtsrc/pages/api.astrosrc/pages/docs/features/stacked-prs.astrosrc/pages/docs/index.astrosrc/pages/index.astro
βΌ.gitignore
+1-0
| @@ -48,3 +48,4 @@ drizzle/meta/ | ||
48 | 48 | |
49 | 49 | # Monaco Editor cache |
50 | 50 | .monaco/ |
51 | +.vercel | |
51 | 52 | |
βΌDEPLOYMENT.md
+1-1
| @@ -116,7 +116,7 @@ REDIS_URL=redis://localhost:6379 | ||
116 | 116 | |
117 | 117 | ```bash |
118 | 118 | # 1. Clone and configure |
119 | -git clone https://github.com/yourusername/OpenCodeHub.git | |
119 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
120 | 120 | cd OpenCodeHub |
121 | 121 | |
122 | 122 | # 2. Install dependencies |
123 | 123 | |
βΌREADME.md
+536-6
| @@ -81,7 +81,7 @@ | ||
81 | 81 | |
82 | 82 | ```bash |
83 | 83 | # 1. Clone the repository |
84 | -git clone https://github.com/yourusername/OpenCodeHub.git | |
84 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
85 | 85 | cd OpenCodeHub |
86 | 86 | |
87 | 87 | # 2. Install dependencies |
| @@ -113,7 +113,7 @@ npm run dev | ||
113 | 113 | |
114 | 114 | ```bash |
115 | 115 | # 1. Clone and configure |
116 | -git clone https://github.com/yourusername/OpenCodeHub.git | |
116 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
117 | 117 | cd OpenCodeHub |
118 | 118 | cp .env.example .env |
119 | 119 | |
| @@ -129,6 +129,536 @@ docker-compose exec app bun run scripts/seed-admin.ts | ||
129 | 129 | ``` |
130 | 130 | |
131 | 131 | --- |
132 | +# Advanced Features Documentation Insert | |
133 | + | |
134 | +**Insert this content into README.md after line 131 (after Docker installation section, before Configuration section)** | |
135 | + | |
136 | +--- | |
137 | + | |
138 | +## π― OCH CLI - Command Line Interface | |
139 | + | |
140 | +Powerful CLI for stack-first PR workflows. | |
141 | + | |
142 | +### Installation | |
143 | + | |
144 | +```bash | |
145 | +# Install globally | |
146 | +npm install -g opencodehub-cli | |
147 | + | |
148 | +# Or use with npx | |
149 | +npx opencodehub-cli | |
150 | + | |
151 | +# Verify installation | |
152 | +och --version | |
153 | +``` | |
154 | + | |
155 | +### Quick Setup | |
156 | + | |
157 | +```bash | |
158 | +# 1. Login to your OpenCode Hub instance | |
159 | +och auth login --url https://git.yourcompany.com | |
160 | + | |
161 | +# 2. Navigate to your repository | |
162 | +cd your-repo | |
163 | + | |
164 | +# 3. Initialize | |
165 | +och init --url https://git.yourcompany.com | |
166 | +``` | |
167 | + | |
168 | +### CLI Commands Reference | |
169 | + | |
170 | +**Authentication:** | |
171 | +```bash | |
172 | +och auth login # Interactive login | |
173 | +och auth login --token TOKEN # Login with token | |
174 | +och auth logout # Logout | |
175 | +och auth whoami # Show current user | |
176 | +``` | |
177 | + | |
178 | +**Stack Management:** | |
179 | +```bash | |
180 | +och stack create \u003cname\u003e # Create new stacked branch | |
181 | +och stack view # View current stack (alias: ls) | |
182 | +och stack submit # Push and create PRs | |
183 | +och stack submit --draft # Create as draft PRs | |
184 | +och stack sync # Rebase stack on latest | |
185 | +och stack reorder # Reorder branches in stack | |
186 | +``` | |
187 | + | |
188 | +**Repository:** | |
189 | +```bash | |
190 | +och init # Initialize repo for OpenCodeHub | |
191 | +och sync # Bidirectional sync with remote | |
192 | +och status # Show stack status (alias: st) | |
193 | +``` | |
194 | + | |
195 | +--- | |
196 | + | |
197 | +## π Advanced Features | |
198 | + | |
199 | +### π Stacked Pull Requests | |
200 | + | |
201 | +**Ship faster by breaking large features into reviewable chunks.** | |
202 | + | |
203 | +#### Why Stacked PRs? | |
204 | + | |
205 | +- **Faster Reviews**: Smaller, focused PRs get reviewed quicker | |
206 | +- **Parallel Development**: Build on unmerged PRs while waiting for review | |
207 | +- **Clean History**: Each PR represents one logical change | |
208 | +- **Risk Reduction**: Deploy incrementally, rollback easily | |
209 | + | |
210 | +#### How It Works | |
211 | + | |
212 | +``` | |
213 | +main | |
214 | + βββ PR #123: Add database schema | |
215 | + βββ PR #124: Add authentication service | |
216 | + βββ PR #125: Add login UI | |
217 | +``` | |
218 | + | |
219 | +Each PR builds on the previous one. When #123 merges, #124 automatically rebases. | |
220 | + | |
221 | +#### Creating a Stack | |
222 | + | |
223 | +**Using CLI:** | |
224 | +```bash | |
225 | +# 1. Create base branch | |
226 | +git checkout -b feature/auth | |
227 | +git commit -m "Add user table schema" | |
228 | +och stack create auth-schema | |
229 | + | |
230 | +# 2. Create stacked branch | |
231 | +och stack create auth-service | |
232 | +git commit -m "Add authentication service" | |
233 | + | |
234 | +# 3. Create final layer | |
235 | +och stack create auth-ui | |
236 | +git commit -m "Add login UI" | |
237 | + | |
238 | +# 4. Submit entire stack | |
239 | +och stack submit | |
240 | +``` | |
241 | + | |
242 | +**Using Web UI:** | |
243 | +1. Create first PR normally | |
244 | +2. When creating second PR, select "Stack on PR #123" | |
245 | +3. System tracks dependencies automatically | |
246 | + | |
247 | +#### Stack Features | |
248 | + | |
249 | +β
**Auto-Rebasing**: When base PR merges, dependent PRs rebase automatically | |
250 | +β
**Dependency Visualization**: See entire stack in PR view | |
251 | +β
**Smart Merge Queue**: Merges in correct order | |
252 | +β
**Conflict Detection**: Alerts on rebase conflicts | |
253 | + | |
254 | +--- | |
255 | + | |
256 | +### π€ AI Code Review | |
257 | + | |
258 | +**Get instant feedback from AI reviewers powered by GPT-4 or Claude.** | |
259 | + | |
260 | +#### Setup | |
261 | + | |
262 | +**1. Configure AI Provider:** | |
263 | +```env | |
264 | +# In .env | |
265 | +AI_PROVIDER=openai # or anthropic | |
266 | +OPENAI_API_KEY=sk-... | |
267 | +# or | |
268 | +ANTHROPIC_API_KEY=sk-ant-... | |
269 | +``` | |
270 | + | |
271 | +**2. Enable for Repository:** | |
272 | +Navigate to **Repository β Settings β AI Review** | |
273 | +- β
Enable AI Review | |
274 | +- Set review triggers (on PR open, on push, manual) | |
275 | +- Configure review rules | |
276 | + | |
277 | +#### What AI Reviews Catch | |
278 | + | |
279 | +π **Security Vulnerabilities** | |
280 | +- SQL injection risks | |
281 | +- XSS vulnerabilities | |
282 | +- Authentication bypasses | |
283 | +- Secrets in code | |
284 | + | |
285 | +β‘ **Performance Issues** | |
286 | +- N+1 queries | |
287 | +- Inefficient algorithms | |
288 | +- Memory leaks | |
289 | +- Blocking operations | |
290 | + | |
291 | +π **Code Quality** | |
292 | +- Best practice violations | |
293 | +- Code smells | |
294 | +- Incomplete error handling | |
295 | +- Missing documentation | |
296 | + | |
297 | +π **Potential Bugs** | |
298 | +- Null pointer exceptions | |
299 | +- Race conditions | |
300 | +- Off-by-one errors | |
301 | +- Logic flaws | |
302 | + | |
303 | +#### Using AI Review | |
304 | + | |
305 | +**Automatic (Recommended):** | |
306 | +AI review runs automatically on every PR based on configured triggers. | |
307 | + | |
308 | +**Manual:** | |
309 | +```bash | |
310 | +# Via CLI | |
311 | +och review ai \u003cPR_NUMBER\u003e | |
312 | + | |
313 | +# Via web UI: Click "Request AI Review" button on PR | |
314 | +``` | |
315 | + | |
316 | +#### Review Output | |
317 | + | |
318 | +AI provides: | |
319 | +- **Severity Score**: Critical / High / Medium / Low / Info | |
320 | +- **Specific Line Comments**: Inline code suggestions | |
321 | +- **Summary Report**: Overall code quality assessment | |
322 | +- **Fix Suggestions**: Actionable improvements | |
323 | + | |
324 | +**Example:** | |
325 | +``` | |
326 | +π΄ CRITICAL: SQL Injection Vulnerability | |
327 | +File: src/api/users.ts:45 | |
328 | +Issue: User input directly concatenated into SQL query | |
329 | +Fix: Use parameterized queries or ORM | |
330 | + | |
331 | +π‘ MEDIUM: Performance Concern | |
332 | +File: src/lib/data.ts:128 | |
333 | +Issue: N+1 query in loop (1000+ iterations) | |
334 | +Fix: Use batch loading or eager loading | |
335 | +``` | |
336 | + | |
337 | +--- | |
338 | + | |
339 | +### π Smart Merge Queue | |
340 | + | |
341 | +**Stack-aware merge queue with automatic conflict resolution and CI optimization.** | |
342 | + | |
343 | +#### Features | |
344 | + | |
345 | +**Stack-Aware Merging:** | |
346 | +- Detects PR dependencies | |
347 | +- Merges in correct order automatically | |
348 | +- Handles entire stacks atomically | |
349 | + | |
350 | +**CI Optimization:** | |
351 | +- Batches compatible PRs | |
352 | +- Runs CI once for multiple PRs | |
353 | +- Reduces CI cost by ~60% | |
354 | + | |
355 | +**Automatic Rebasing:** | |
356 | +- Detects conflicts early | |
357 | +- Auto-rebases on base changes | |
358 | +- Alerts author on conflicts | |
359 | + | |
360 | +**Smart Ordering:** | |
361 | +- Priority-based queue | |
362 | +- Dependency resolution | |
363 | +- Hotfix fast-tracking | |
364 | + | |
365 | +#### Using Merge Queue | |
366 | + | |
367 | +**Add to Queue:** | |
368 | +```bash | |
369 | +# Via CLI | |
370 | +och queue add \u003cPR_NUMBER\u003e | |
371 | + | |
372 | +# Via Web UI | |
373 | +Click "Add to Merge Queue" button on approved PR | |
374 | +``` | |
375 | + | |
376 | +**Queue Status:** | |
377 | +```bash | |
378 | +# View queue | |
379 | +och queue list | |
380 | + | |
381 | +# Output: | |
382 | +Position PR Title Status | |
383 | +1 #125 Add login UI β³ Running CI | |
384 | +2 #124 Auth service βΈοΈ Waiting (depends on #123) | |
385 | +3 #123 Database schema β
Ready to merge | |
386 | +``` | |
387 | + | |
388 | +**How It Works:** | |
389 | + | |
390 | +1. **PR Added**: Queue analyzes dependencies | |
391 | +2. **Position Assigned**: Ordered by priority and dependencies | |
392 | +3. **CI Running**: Runs tests in parallel where possible | |
393 | +4. **Auto-Merge**: Merges when ready, rebases dependents | |
394 | +5. **Notification**: Author notified on completion | |
395 | + | |
396 | +#### Queue Configuration | |
397 | + | |
398 | +**Repository β Settings β Merge Queue:** | |
399 | +- **Require CI**: Only merge if all checks pass | |
400 | +- **Batch Window**: Wait time to batch PRs (default: 5min) | |
401 | +- **Max Batch Size**: Max PRs to merge together (default: 3) | |
402 | +- **Auto-Rebase**: Rebase on conflicts vs. remove from queue | |
403 | + | |
404 | +--- | |
405 | + | |
406 | +### π Developer Metrics | |
407 | + | |
408 | +**Track team velocity, review efficiency, and identify bottlenecks.** | |
409 | + | |
410 | +#### Available Metrics | |
411 | + | |
412 | +**PR Velocity:** | |
413 | +- PRs merged per week | |
414 | +- Average time to merge | |
415 | +- Stack success rate | |
416 | +- Review cycle time | |
417 | + | |
418 | +**Review Efficiency:** | |
419 | +- Average review time | |
420 | +- Reviews per day | |
421 | +- Review quality score | |
422 | +- Reviewer responsiveness | |
423 | + | |
424 | +**Team Performance:** | |
425 | +- Lines changed per PR | |
426 | +- PR size distribution | |
427 | +- Revert rate | |
428 | +- CI success rate | |
429 | + | |
430 | +**Stack Metrics:** | |
431 | +- % PRs that use stacks | |
432 | +- Average stack depth | |
433 | +- Stack merge success rate | |
434 | +- Time saved vs. monolithic PRs | |
435 | + | |
436 | +#### Viewing Metrics | |
437 | + | |
438 | +**Personal Dashboard:** | |
439 | +``` | |
440 | +/metrics/me | |
441 | +``` | |
442 | + | |
443 | +**Team Dashboard (Admin):** | |
444 | +``` | |
445 | +/admin/metrics | |
446 | +``` | |
447 | + | |
448 | +**Via CLI:** | |
449 | +```bash | |
450 | +och metrics show | |
451 | +och metrics show --user @username | |
452 | +och metrics show --repo owner/repo | |
453 | +``` | |
454 | + | |
455 | +#### Example Metrics View | |
456 | + | |
457 | +``` | |
458 | +π Your Metrics (Last 30 Days) | |
459 | + | |
460 | +PR Velocity | |
461 | +ββ PRs Merged ββββββββββββββ 24 (+12%) | |
462 | +ββ Avg Time to Merge ββββββββββββββ 8.2 hours (-3h) | |
463 | +ββ Stack Usage ββββββββββββββ 65% (+15%) | |
464 | + | |
465 | +Review Efficiency | |
466 | +ββ Reviews Given ββββββββββββββ 18 | |
467 | +ββ Avg Review Time ββββββββββββββ 22 min | |
468 | +ββ Approval Rate ββββββββββββββ 94% | |
469 | +``` | |
470 | + | |
471 | +--- | |
472 | + | |
473 | +### π¬ Slack Notifications | |
474 | + | |
475 | +**Get actionable PR notifications directly in Slack.** | |
476 | + | |
477 | +#### Setup | |
478 | + | |
479 | +**1. Create Slack App:** | |
480 | +1. Go to https://api.slack.com/apps | |
481 | +2. Create New App β From Scratch | |
482 | +3. Add OAuth Scopes: | |
483 | + - `chat:write` | |
484 | + - `chat:write.public` | |
485 | + - `users:read` | |
486 | +4. Install to workspace | |
487 | + | |
488 | +**2. Connect to OpenCodeHub:** | |
489 | +Navigate to **Admin β Integrations β Slack** | |
490 | +``` | |
491 | +Workspace Name: Your Team | |
492 | +Bot Token: xoxb-... | |
493 | +Signing Secret: ... | |
494 | +``` | |
495 | + | |
496 | +**3. Map Channels:** | |
497 | +**Repository β Settings β Slack** | |
498 | +``` | |
499 | +#engineering β All PR events | |
500 | +#deployments β Merges only | |
501 | +#security β Security findings | |
502 | +``` | |
503 | + | |
504 | +**4. Map Users (Optional):** | |
505 | +Users can link their Slack account: | |
506 | +**Settings β Integrations β Connect Slack** | |
507 | + | |
508 | +#### Notification Types | |
509 | + | |
510 | +**PR Events:** | |
511 | +- π PR opened | |
512 | +- β
PR approved | |
513 | +- π PR merged | |
514 | +- β PR closed | |
515 | +- π¬ Review requested | |
516 | +- π΄ Changes requested | |
517 | + | |
518 | +**CI/CD Events:** | |
519 | +- β
Build passed | |
520 | +- β Build failed | |
521 | +- π Deployment started | |
522 | +- β
Deployment successful | |
523 | + | |
524 | +**Code Review:** | |
525 | +- π€ AI review completed | |
526 | +- π Security issue found | |
527 | +- β οΈ Performance concern | |
528 | + | |
529 | +**Merge Queue:** | |
530 | +- βοΈ Added to queue | |
531 | +- β
Merged from queue | |
532 | +- β οΈ Removed (conflict) | |
533 | + | |
534 | +#### Interactive Actions | |
535 | + | |
536 | +Notifications include action buttons: | |
537 | +``` | |
538 | +[swadhin] opened PR#125: Add login UI | |
539 | +Stack: #123 β #124 β #125 β’ files β32 β’ LoC +420/-18 | |
540 | + | |
541 | +[Approve] [Request Changes] [View PR] [Add to Queue] | |
542 | +``` | |
543 | + | |
544 | +Click buttons to: | |
545 | +- Approve/reject directly from Slack | |
546 | +- Request changes with comment | |
547 | +- Add to merge queue | |
548 | +- View full PR | |
549 | + | |
550 | +--- | |
551 | + | |
552 | +## π‘ Workflow Examples | |
553 | + | |
554 | +### Example 1: Complete Stacked PR Flow | |
555 | + | |
556 | +```bash | |
557 | +# 1. Start new feature | |
558 | +git checkout main | |
559 | +git pull | |
560 | +git checkout -b feature/user-profiles | |
561 | + | |
562 | +# 2. First PR: Database layer | |
563 | +git add migrations/ | |
564 | +git commit -m "Add user profile tables" | |
565 | +och stack create profile-db | |
566 | +och stack submit | |
567 | + | |
568 | +# β Creates PR #301 | |
569 | + | |
570 | +# 3. Second PR: Backend API | |
571 | +git add src/api/profiles/ | |
572 | +git commit -m "Add profile API" | |
573 | +och stack create profile-api | |
574 | +och stack submit | |
575 | + | |
576 | +# β Creates PR #302 (stacked on #301) | |
577 | + | |
578 | +# 4. Third PR: Frontend | |
579 | +git add src/pages/profile/ | |
580 | +git commit -m "Add profile UI" | |
581 | +och stack create profile-ui | |
582 | +och stack submit | |
583 | + | |
584 | +# β Creates PR #303 (stacked on #302) | |
585 | + | |
586 | +# 5. View stack | |
587 | +och stack view | |
588 | +``` | |
589 | + | |
590 | +**Output:** | |
591 | +``` | |
592 | +π Current Stack | |
593 | + | |
594 | +main (base) | |
595 | + ββ #301: profile-db β
Approved | |
596 | + ββ #302: profile-api π In Review | |
597 | + ββ #303: profile-ui β³ AI Review Running | |
598 | +``` | |
599 | + | |
600 | +### Example 2: Using AI Review + Merge Queue | |
601 | + | |
602 | +```bash | |
603 | +# 1. Create PR | |
604 | +git push origin feature-branch | |
605 | + | |
606 | +# 2. Request AI review | |
607 | +och review ai 304 | |
608 | + | |
609 | +# 3. Fix AI suggestions | |
610 | +# ... make changes based on review ... | |
611 | +git commit -m "Fix security issues from AI review" | |
612 | +git push | |
613 | + | |
614 | +# 4. Get human review | |
615 | +# Team reviews via web UI | |
616 | + | |
617 | +# 5. Add to merge queue when approved | |
618 | +och queue add 304 | |
619 | + | |
620 | +# Queue handles: | |
621 | +# - Waiting for CI | |
622 | +# - Merging when ready | |
623 | +# - Notifying on Slack | |
624 | +``` | |
625 | + | |
626 | +### Example 3: Team Collaboration | |
627 | + | |
628 | +```bash | |
629 | +# Developer A: Creates base | |
630 | +git checkout -b api/payments | |
631 | +# ... implements payment models ... | |
632 | +git commit -m "Add payment models" | |
633 | +git push | |
634 | + | |
635 | +# Developer B: Builds on A's work (before merge!) | |
636 | +git checkout api/payments | |
637 | +git checkout -b api/payment-processing | |
638 | +# ... implements payment processing ... | |
639 | +git commit -m "Add payment processing" | |
640 | +git push | |
641 | +# Create PR stacked on A's PR | |
642 | + | |
643 | +# Developer C: Builds on B's work | |
644 | +git checkout api/payment-processing | |
645 | +git checkout -b ui/payment-form | |
646 | +# ... implements payment UI ... | |
647 | +``` | |
648 | + | |
649 | +**Result**: 3 parallel PRs, all reviewed separately, merged in order. | |
650 | + | |
651 | +--- | |
652 | + | |
653 | +## π Additional Resources | |
654 | + | |
655 | +- **Full CLI Reference**: `cli/README.md` | |
656 | +- **API Documentation**: `/api/docs` | |
657 | +- **Stacked PR Guide**: `/docs#stacked-prs` | |
658 | +- **Slack Integration Guide**: `/docs#slack` | |
659 | + | |
660 | +--- | |
661 | + | |
132 | 662 | |
133 | 663 | ## βοΈ Configuration |
134 | 664 | |
| @@ -675,8 +1205,8 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. | ||
675 | 1205 | |
676 | 1206 | ```bash |
677 | 1207 | # Fork and clone |
678 | -git clone https://github.com/YOUR-USERNAME/OpenCodeHub.git | |
679 | -cd OpenCodeHub | |
1208 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
1209 | +cd OpencodeHub | |
680 | 1210 | |
681 | 1211 | # Install dependencies |
682 | 1212 | bun install |
| @@ -723,6 +1253,6 @@ MIT License - see [LICENSE](LICENSE) for details. | ||
723 | 1253 | <p align="center"> |
724 | 1254 | <strong>Made with β€οΈ for the open source community</strong> |
725 | 1255 | <br> |
726 | - <a href="https://github.com/yourusername/OpenCodeHub/issues">Report Bug</a> β’ | |
727 | - <a href="https://github.com/yourusername/OpenCodeHub/issues">Request Feature</a> | |
1256 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/issues">Report Bug</a> β’ | |
1257 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/issues">Request Feature</a> | |
728 | 1258 | </p> |
729 | 1259 | |
βΌREADME.md.backup
+728-0
| @@ -0,0 +1,728 @@ | ||
1 | +# OpenCodeHub | |
2 | + | |
3 | +<p align="center"> | |
4 | + <img src="public/logo.png" alt="OpenCodeHub Logo" width="200"> | |
5 | +</p> | |
6 | + | |
7 | +<p align="center"> | |
8 | + <strong>A powerful, self-hosted Git platform with CI/CD, code review, and enterprise-grade security</strong> | |
9 | +</p> | |
10 | + | |
11 | +<p align="center"> | |
12 | + <a href="#-features">Features</a> β’ | |
13 | + <a href="#-quick-start">Quick Start</a> β’ | |
14 | + <a href="#%EF%B8%8F-configuration">Configuration</a> β’ | |
15 | + <a href="#-security">Security</a> β’ | |
16 | + <a href="#-production-deployment">Production</a> β’ | |
17 | + <a href="#-api">API</a> β’ | |
18 | + <a href="#-contributing">Contributing</a> | |
19 | +</p> | |
20 | + | |
21 | +--- | |
22 | + | |
23 | +## π Production Ready! | |
24 | + | |
25 | +**All critical security vulnerabilities are now fixed. OpenCodeHub is production-ready!** π | |
26 | + | |
27 | +β
**Rate Limiting** - Prevents brute force attacks | |
28 | +β
**CSRF Protection** - Cross-site request forgery defense | |
29 | +β
**Input Validation** - SQL injection & XSS prevention | |
30 | +β
**Hook Authentication** - Secure git operations | |
31 | +β
**Environment Validation** - Safe configuration management | |
32 | + | |
33 | +π **See [DEPLOYMENT.md](DEPLOYMENT.md) for complete deployment guide** with SSL setup, Nginx configuration, monitoring, database backups, and production best practices. | |
34 | + | |
35 | +--- | |
36 | + | |
37 | +## π Features | |
38 | + | |
39 | +### Git Hosting & Collaboration | |
40 | +- **Full Git Support** - SSH and HTTP/HTTPS protocols with authentication | |
41 | +- **Repository Management** - Create, fork, archive, and transfer repositories | |
42 | +- **Branch Protection** - Enforce PR requirements, approvals, and code review policies | |
43 | +- **Pull Requests** - Code review with inline comments and approval workflows | |
44 | +- **Issues & Milestones** - Bug tracking with labels, assignees, and custom fields | |
45 | +- **Wiki** - Built-in documentation with markdown support | |
46 | +- **Organizations** - Team management with role-based access control (RBAC) | |
47 | + | |
48 | +### CI/CD & Automation | |
49 | +- **GitHub Actions Compatible** - Run workflows defined in `.github/workflows/` | |
50 | +- **Self-hosted Runners** - Docker-based job execution with artifact support | |
51 | +- **Merge Queue** - Automated PR merge management with conflict detection | |
52 | +- **Webhooks** - Real-time event notifications to external services | |
53 | +- **Secrets Management** - Encrypted storage for CI/CD credentials | |
54 | + | |
55 | +### Security Features β
| |
56 | +- **Rate Limiting** - Prevents brute force attacks (5 login attempts per 15min) | |
57 | +- **CSRF Protection** - Double-submit cookie pattern for all state-changing operations | |
58 | +- **Input Validation** - Zod schemas prevent injection attacks | |
59 | +- **Hook Authentication** - Shared secrets protect internal git hooks | |
60 | +- **Session Management** - Automatic expiration and rotation | |
61 | +- **Audit Logging** - Complete activity tracking for compliance | |
62 | + | |
63 | +### Modern Tech Stack | |
64 | +- **Astro + React** - Fast, modern frontend with island architecture | |
65 | +- **TailwindCSS + shadcn/ui** - Beautiful, accessible UI components | |
66 | +- **Universal Database Adapter** - PostgreSQL, MySQL, SQLite, MongoDB, Turso, PlanetScale | |
67 | +- **Flexible Storage** - Local, S3, MinIO, Google Drive, Azure Blob, R2 | |
68 | +- **Production-Ready** - Rate limiting, CSRF, input validation, error monitoring | |
69 | + | |
70 | +--- | |
71 | + | |
72 | +## π¦ Quick Start | |
73 | + | |
74 | +### Prerequisites | |
75 | +- **Node.js** 18+ or **Bun** 1.0+ | |
76 | +- **Database**: PostgreSQL 14+ (recommended) / MySQL 8+ / SQLite 3.35+ | |
77 | +- **Git** 2.30+ | |
78 | +- **Docker** (optional, for CI/CD runners) | |
79 | + | |
80 | +### Installation (5 minutes) | |
81 | + | |
82 | +```bash | |
83 | +# 1. Clone the repository | |
84 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
85 | +cd OpenCodeHub | |
86 | + | |
87 | +# 2. Install dependencies | |
88 | +npm install | |
89 | +# or | |
90 | +bun install | |
91 | + | |
92 | +# 3. Set up environment | |
93 | +cp .env.example .env | |
94 | + | |
95 | +# 4. Configure your .env file (see Configuration section) | |
96 | +nano .env # Set at minimum: JWT_SECRET, SESSION_SECRET, INTERNAL_HOOK_SECRET, SITE_URL | |
97 | + | |
98 | +# 5. Run database migrations | |
99 | +npm run db:push | |
100 | +# or generate migrations for version control: | |
101 | +npm run db:generate | |
102 | + | |
103 | +# 6. Seed an admin user | |
104 | +bun run scripts/seed-admin.ts | |
105 | +# Enter username, email, password when prompted | |
106 | + | |
107 | +# 7. Start development server | |
108 | +npm run dev | |
109 | +# Application available at http://localhost:3000 | |
110 | +``` | |
111 | + | |
112 | +### Using Docker (Recommended for Production) | |
113 | + | |
114 | +```bash | |
115 | +# 1. Clone and configure | |
116 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
117 | +cd OpenCodeHub | |
118 | +cp .env.example .env | |
119 | + | |
120 | +# 2. Edit .env with production values (see Production Deployment section) | |
121 | + | |
122 | +# 3. Start services | |
123 | +docker-compose up -d | |
124 | + | |
125 | +# 4. Create admin user | |
126 | +docker-compose exec app bun run scripts/seed-admin.ts | |
127 | + | |
128 | +# Access at http://localhost:3000 | |
129 | +``` | |
130 | + | |
131 | +--- | |
132 | + | |
133 | +## βοΈ Configuration | |
134 | + | |
135 | +### Critical Environment Variables | |
136 | + | |
137 | +These **MUST** be set before deploying to production: | |
138 | + | |
139 | +```env | |
140 | +# Security (CRITICAL - Generate strong secrets!) | |
141 | +JWT_SECRET=<run: openssl rand -hex 32> | |
142 | +SESSION_SECRET=<run: openssl rand -hex 32> | |
143 | +INTERNAL_HOOK_SECRET=<run: openssl rand -hex 32> | |
144 | + | |
145 | +# Application URLs (CRITICAL) | |
146 | +SITE_URL=https://your-domain.com # Must be HTTPS in production | |
147 | +PORT=3000 | |
148 | +HOST=0.0.0.0 | |
149 | +``` | |
150 | + | |
151 | +### Database Configuration | |
152 | + | |
153 | +**PostgreSQL (Recommended for Production):** | |
154 | +```env | |
155 | +DATABASE_DRIVER=postgres | |
156 | +DATABASE_URL=postgresql://user:password@localhost:5432/opencodehub | |
157 | +``` | |
158 | + | |
159 | +**MySQL:** | |
160 | +```env | |
161 | +DATABASE_DRIVER=mysql | |
162 | +DATABASE_URL=mysql://user:password@localhost:3306/opencodehub | |
163 | +``` | |
164 | + | |
165 | +**SQLite (Development Only):** | |
166 | +```env | |
167 | +DATABASE_DRIVER=sqlite | |
168 | +DATABASE_URL=./data/opencodehub.db | |
169 | +``` | |
170 | + | |
171 | +### Storage Configuration | |
172 | + | |
173 | +Storage can be configured via Admin Panel (`/admin/storage`) or environment variables: | |
174 | + | |
175 | +**Local Storage:** | |
176 | +```env | |
177 | +STORAGE_TYPE=local | |
178 | +STORAGE_PATH=./data/storage | |
179 | +``` | |
180 | + | |
181 | +**S3/MinIO/R2:** | |
182 | +```env | |
183 | +STORAGE_TYPE=s3 | |
184 | +STORAGE_BUCKET=opencodehub | |
185 | +STORAGE_REGION=us-east-1 | |
186 | +STORAGE_ENDPOINT=https://s3.amazonaws.com | |
187 | +S3_ACCESS_KEY=your-access-key | |
188 | +S3_SECRET_KEY=your-secret-key | |
189 | +``` | |
190 | + | |
191 | +**Google Drive (via Admin Panel):** | |
192 | +- Navigate to `/admin/storage` | |
193 | +- Select "Google Drive" | |
194 | +- Enter Client ID, Secret, and Refresh Token | |
195 | +- Test connection before saving | |
196 | + | |
197 | +### Security Configuration | |
198 | + | |
199 | +**Rate Limiting:** | |
200 | +```env | |
201 | +RATE_LIMIT_AUTH=5 # Login attempts per 15 minutes | |
202 | +RATE_LIMIT_API=100 # API requests per minute | |
203 | +RATE_LIMIT_GIT=200 # Git operations per minute | |
204 | +RATE_LIMIT_SKIP_DEV=false # Never skip in production | |
205 | +``` | |
206 | + | |
207 | +**CSRF Protection:** | |
208 | +```env | |
209 | +CSRF_SKIP_DEV=false # Never skip in production | |
210 | +``` | |
211 | + | |
212 | +### Optional Services | |
213 | + | |
214 | +**Email (SMTP):** | |
215 | +```env | |
216 | +SMTP_HOST=smtp.example.com | |
217 | +SMTP_PORT=587 | |
218 | ||
219 | +SMTP_PASSWORD=your-smtp-password | |
220 | ||
221 | +``` | |
222 | + | |
223 | +**Redis (Caching & Sessions):** | |
224 | +```env | |
225 | +REDIS_URL=redis://localhost:6379 | |
226 | +``` | |
227 | + | |
228 | +**Error Monitoring (Sentry):** | |
229 | +```env | |
230 | +SENTRY_DSN=https://... | |
231 | +ENABLE_TRACING=true | |
232 | +``` | |
233 | + | |
234 | +--- | |
235 | + | |
236 | +## π Security | |
237 | + | |
238 | +OpenCodeHub implements **production-grade security** out of the box: | |
239 | + | |
240 | +### Implemented Security Features | |
241 | + | |
242 | +β
**Authentication & Authorization** | |
243 | +- JWT-based sessions with automatic expiration | |
244 | +- Bcrypt password hashing (10 rounds) | |
245 | +- 2FA/TOTP support | |
246 | +- Role-based access control (RBAC) | |
247 | + | |
248 | +β
**Attack Prevention** | |
249 | +- **Rate Limiting**: Prevents brute force (5 login attempts per 15min) | |
250 | +- **CSRF Protection**: Double-submit cookie pattern on all state-changing operations | |
251 | +- **Input Validation**: Zod schemas prevent SQL injection and XSS | |
252 | +- **XSS Sanitization**: Markdown rendered with `rehype-sanitize` | |
253 | + | |
254 | +β
**Operational Security** | |
255 | +- **Audit Logging**: All admin actions logged to database | |
256 | +- **Hook Authentication**: Git hooks protected with shared secrets | |
257 | +- **Session Management**: Automatic cleanup and rotation | |
258 | +- **Environment Validation**: Startup checks for missing/weak secrets | |
259 | + | |
260 | +### Security Best Practices | |
261 | + | |
262 | +1. **Always use HTTPS** in production (Let's Encrypt recommended) | |
263 | +2. **Rotate secrets** regularly (especially after team changes) | |
264 | +3. **Enable 2FA** for all admin accounts | |
265 | +4. **Monitor audit logs** for suspicious activity | |
266 | +5. **Keep dependencies updated**: Run `npm audit` weekly | |
267 | + | |
268 | +### Reporting Security Issues | |
269 | + | |
270 | +Please report security vulnerabilities to **[email protected]**. Do not create public issues. | |
271 | + | |
272 | +--- | |
273 | + | |
274 | +## π Production Deployment | |
275 | + | |
276 | +### Pre-Deployment Checklist | |
277 | + | |
278 | +Before going live, ensure: | |
279 | + | |
280 | +- [ ] **Secrets**: All secrets generated with `openssl rand -hex 32` | |
281 | +- [ ] **HTTPS**: SSL certificate configured (Let's Encrypt recommended) | |
282 | +- [ ] **Database**: PostgreSQL with backups enabled | |
283 | +- [ ] **Storage**: S3/R2 configured (not local filesystem) | |
284 | +- [ ] **Monitoring**: Error tracking (Sentry) and APM configured | |
285 | +- [ ] **Rate Limits**: Configured for expected traffic | |
286 | +- [ ] **Admin Account**: Created via `bun run scripts/seed-admin.ts` | |
287 | +- [ ] **Environment Validation**: Test with `bun run src/lib/env-validation.ts` | |
288 | +- [ ] **Database Migrations**: Generated with `npm run db:generate` | |
289 | + | |
290 | +### Production Environment Variables | |
291 | + | |
292 | +```env | |
293 | +# Application | |
294 | +NODE_ENV=production | |
295 | +SITE_URL=https://git.yourcompany.com | |
296 | +PORT=3000 | |
297 | + | |
298 | +# Security (CRITICAL - Generate with: openssl rand -hex 32) | |
299 | +JWT_SECRET=<64-char-random-hex> | |
300 | +SESSION_SECRET=<64-char-random-hex> | |
301 | +INTERNAL_HOOK_SECRET=<64-char-random-hex> | |
302 | + | |
303 | +# Database (Use connection pooling) | |
304 | +DATABASE_DRIVER=postgres | |
305 | +DATABASE_URL=postgresql://user:password@db-host:5432/opencodehub?pool_timeout=30 | |
306 | + | |
307 | +# Storage (Use cloud storage) | |
308 | +STORAGE_TYPE=s3 | |
309 | +STORAGE_BUCKET=opencodehub-prod | |
310 | +STORAGE_REGION=us-east-1 | |
311 | + | |
312 | +# Rate Limiting (Adjust based on traffic) | |
313 | +RATE_LIMIT_AUTH=5 | |
314 | +RATE_LIMIT_API=200 | |
315 | +RATE_LIMIT_GIT=500 | |
316 | + | |
317 | +# Monitoring | |
318 | +SENTRY_DSN=https://your-sentry-dsn | |
319 | +LOG_LEVEL=info | |
320 | + | |
321 | +# Features | |
322 | +ENABLE_REGISTRATION=true # Set false for invite-only | |
323 | +``` | |
324 | + | |
325 | +### Docker Production Deployment | |
326 | + | |
327 | +```yaml | |
328 | +# docker-compose.prod.yml | |
329 | +version: '3.8' | |
330 | + | |
331 | +services: | |
332 | + app: | |
333 | + image: opencodehub:latest | |
334 | + restart: always | |
335 | + ports: | |
336 | + - "3000:3000" | |
337 | + env_file: .env.production | |
338 | + depends_on: | |
339 | + - postgres | |
340 | + - redis | |
341 | + volumes: | |
342 | + - ./data:/app/data | |
343 | + healthcheck: | |
344 | + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] | |
345 | + interval: 30s | |
346 | + timeout: 10s | |
347 | + retries: 3 | |
348 | + | |
349 | + postgres: | |
350 | + image: postgres:15-alpine | |
351 | + restart: always | |
352 | + environment: | |
353 | + POSTGRES_DB: opencodehub | |
354 | + POSTGRES_USER: opencodehub | |
355 | + POSTGRES_PASSWORD: <strong-password> | |
356 | + volumes: | |
357 | + - postgres_data:/var/lib/postgresql/data | |
358 | + | |
359 | + redis: | |
360 | + image: redis:7-alpine | |
361 | + restart: always | |
362 | + | |
363 | +volumes: | |
364 | + postgres_data: | |
365 | +``` | |
366 | + | |
367 | +```bash | |
368 | +# Deploy | |
369 | +docker-compose -f docker-compose.prod.yml up -d | |
370 | + | |
371 | +# Monitor logs | |
372 | +docker-compose -f docker-compose.prod.yml logs -f app | |
373 | + | |
374 | +# Health check | |
375 | +curl https://git.yourcompany.com/api/health | |
376 | +``` | |
377 | + | |
378 | +### Reverse Proxy (Nginx) | |
379 | + | |
380 | +```nginx | |
381 | +server { | |
382 | + listen 443 ssl http2; | |
383 | + server_name git.yourcompany.com; | |
384 | + | |
385 | + ssl_certificate /path/to/cert.pem; | |
386 | + ssl_certificate_key /path/to/key.pem; | |
387 | + | |
388 | + location / { | |
389 | + proxy_pass http://localhost:3000; | |
390 | + proxy_set_header Host $host; | |
391 | + proxy_set_header X-Real-IP $remote_addr; | |
392 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
393 | + proxy_set_header X-Forwarded-Proto $scheme; | |
394 | + } | |
395 | + | |
396 | + # Git operations (larger timeouts) | |
397 | + location ~ ^/[^/]+/[^/]+\.git/ { | |
398 | + proxy_pass http://localhost:3000; | |
399 | + proxy_read_timeout 300s; | |
400 | + proxy_send_timeout 300s; | |
401 | + proxy_buffer_size 128k; | |
402 | + proxy_buffers 4 256k; | |
403 | + } | |
404 | +} | |
405 | +``` | |
406 | + | |
407 | +### Database Backups | |
408 | + | |
409 | +```bash | |
410 | +# PostgreSQL backup script (cron daily) | |
411 | +#!/bin/bash | |
412 | +pg_dump -U opencodehub opencodehub | gzip > /backups/opencodehub-$(date +\%Y\%m\%d).sql.gz | |
413 | + | |
414 | +# Retain last 30 days | |
415 | +find /backups -name "opencodehub-*.sql.gz" -mtime +30 -delete | |
416 | +``` | |
417 | + | |
418 | +--- | |
419 | + | |
420 | +## π API Documentation | |
421 | + | |
422 | +### Authentication | |
423 | + | |
424 | +All authenticated endpoints require a session cookie or `Authorization: Bearer <token>` header. | |
425 | + | |
426 | +**Register:** | |
427 | +```bash | |
428 | +POST /api/auth/register | |
429 | +Content-Type: application/json | |
430 | + | |
431 | +{ | |
432 | + "username": "johndoe", | |
433 | + "email": "[email protected]", | |
434 | + "password": "SecureP@ssw0rd", | |
435 | + "displayName": "John Doe" | |
436 | +} | |
437 | + | |
438 | +# Response: 201 Created | |
439 | +{ | |
440 | + "user": { "id": "...", "username": "johndoe", ... }, | |
441 | + "token": "eyJ...", | |
442 | + "expiresAt": "2026-01-08T00:00:00Z" | |
443 | +} | |
444 | +``` | |
445 | + | |
446 | +**Login:** | |
447 | +```bash | |
448 | +POST /api/auth/login | |
449 | +Content-Type: application/json | |
450 | + | |
451 | +{ | |
452 | + "login": "[email protected]", # username or email | |
453 | + "password": "SecureP@ssw0rd" | |
454 | +} | |
455 | + | |
456 | +# With 2FA: | |
457 | +{ | |
458 | + "login": "[email protected]", | |
459 | + "password": "SecureP@ssw0rd", | |
460 | + "totpCode": "123456" | |
461 | +} | |
462 | +``` | |
463 | + | |
464 | +### Repositories | |
465 | + | |
466 | +**Create Repository:** | |
467 | +```bash | |
468 | +POST /api/repos | |
469 | +Authorization: Bearer <token> | |
470 | +Content-Type: application/json | |
471 | + | |
472 | +{ | |
473 | + "name": "my-project", | |
474 | + "description": "My awesome project", | |
475 | + "visibility": "public" # public, private, internal | |
476 | +} | |
477 | +``` | |
478 | + | |
479 | +**Branch Protection:** | |
480 | +```bash | |
481 | +POST /api/repos/{repoId}/branch-protection | |
482 | +Authorization: Bearer <token> | |
483 | +Content-Type: application/json | |
484 | + | |
485 | +{ | |
486 | + "pattern": "main", | |
487 | + "requiresPr": true, | |
488 | + "requiredApprovals": 2, | |
489 | + "dismissStaleReviews": true | |
490 | +} | |
491 | +``` | |
492 | + | |
493 | +### Admin Endpoints | |
494 | + | |
495 | +**List Users (Admin Only):** | |
496 | +```bash | |
497 | +GET /admin/users?q=search&page=1 | |
498 | +Cookie: och_session=... | |
499 | +``` | |
500 | + | |
501 | +**Update Storage Config (Admin Only):** | |
502 | +```bash | |
503 | +POST /api/admin/config/storage | |
504 | +Cookie: och_session=... | |
505 | +Content-Type: application/json | |
506 | + | |
507 | +{ | |
508 | + "type": "s3", | |
509 | + "bucket": "my-bucket", | |
510 | + "region": "us-east-1", | |
511 | + ... | |
512 | +} | |
513 | +``` | |
514 | + | |
515 | +### Rate Limits | |
516 | + | |
517 | +All endpoints return rate limit headers: | |
518 | +``` | |
519 | +X-RateLimit-Limit: 100 | |
520 | +X-RateLimit-Remaining: 95 | |
521 | +X-RateLimit-Reset: 2026-01-01T15:30:00Z | |
522 | +Retry-After: 30 (when rate limited) | |
523 | +``` | |
524 | + | |
525 | +**Full API documentation:** `/api/docs` (OpenAPI spec coming soon) | |
526 | + | |
527 | +--- | |
528 | + | |
529 | +## π§ Usage Guide | |
530 | + | |
531 | +### Creating Your First Repository | |
532 | + | |
533 | +1. **Log in** to your account | |
534 | +2. Click the **+** button β **New Repository** | |
535 | +3. Fill in details: | |
536 | + - **Name**: alphanumeric, hyphens, underscores | |
537 | + - **Description**: Optional | |
538 | + - **Visibility**: public/private | |
539 | +4. Click **Create Repository** | |
540 | + | |
541 | +### Cl | |
542 | + | |
543 | +oning Repositories | |
544 | + | |
545 | +**SSH (Recommended):** | |
546 | +```bash | |
547 | +# Add your SSH key in Settings β SSH Keys | |
548 | +git clone [email protected]:username/repo.git | |
549 | +``` | |
550 | + | |
551 | +**HTTPS:** | |
552 | +```bash | |
553 | +git clone https://your-domain.com/username/repo.git | |
554 | +# Username: your-username | |
555 | +# Password: your-password or personal access token | |
556 | +``` | |
557 | + | |
558 | +### Setting Up Branch Protection | |
559 | + | |
560 | +1. Navigate to **Repository β Settings β Branches** | |
561 | +2. Click **Add Rule** | |
562 | +3. Configure: | |
563 | + - **Pattern**: `main` or `release/*` | |
564 | + - **Require PR**: β
| |
565 | + - **Required Approvals**: 2 | |
566 | + - **Dismiss Stale Reviews**: β
| |
567 | +4. **Save Rule** | |
568 | + | |
569 | +### CI/CD Workflows | |
570 | + | |
571 | +Create `.github/workflows/ci.yml`: | |
572 | + | |
573 | +```yaml | |
574 | +name: CI | |
575 | + | |
576 | +on: | |
577 | + push: | |
578 | + branches: [main, develop] | |
579 | + pull_request: | |
580 | + branches: [main] | |
581 | + | |
582 | +jobs: | |
583 | + test: | |
584 | + runs-on: ubuntu-latest | |
585 | + steps: | |
586 | + - uses: actions/checkout@v4 | |
587 | + | |
588 | + - name: Setup Node.js | |
589 | + uses: actions/setup-node@v4 | |
590 | + with: | |
591 | + node-version: '20' | |
592 | + | |
593 | + - name: Install | |
594 | + run: npm ci | |
595 | + | |
596 | + - name: Test | |
597 | + run: npm test | |
598 | + | |
599 | + - name: Build | |
600 | + run: npm run build | |
601 | + | |
602 | + - name: Upload Artifacts | |
603 | + uses: actions/upload-artifact@v3 | |
604 | + with: | |
605 | + name: dist | |
606 | + path: dist/ | |
607 | +``` | |
608 | + | |
609 | +--- | |
610 | + | |
611 | +## π Troubleshooting | |
612 | + | |
613 | +### Common Issues | |
614 | + | |
615 | +**"Rate limit exceeded" on login:** | |
616 | +- Default: 5 attempts per 15 minutes | |
617 | +- Solution: Wait 15 minutes or adjust `RATE_LIMIT_AUTH` in `.env` | |
618 | + | |
619 | +**"CSRF token validation failed":** | |
620 | +- Ensure cookies are enabled | |
621 | +- Check `SITE_URL` matches your actual domain | |
622 | +- Verify HTTPS in production | |
623 | + | |
624 | +**"Unauthorized" on git hooks:** | |
625 | +- Check `INTERNAL_HOOK_SECRET` is set | |
626 | +- Verify hooks have correct secret in curl headers | |
627 | +- Re-run `git init` on repositories to reinstall hooks | |
628 | + | |
629 | +**Database connection errors:** | |
630 | +- Verify `DATABASE_URL` format | |
631 | +- Check database server is running | |
632 | +- Ensure user has correct permissions | |
633 | + | |
634 | +**Storage upload fails:** | |
635 | +- Test connection in `/admin/storage` | |
636 | +- Verify S3 credentials and bucket permissions | |
637 | +- Check firewall rules for external storage | |
638 | + | |
639 | +### Debug Mode | |
640 | + | |
641 | +```env | |
642 | +# Enable detailed logging | |
643 | +LOG_LEVEL=debug | |
644 | +NODE_ENV=development | |
645 | + | |
646 | +# View logs | |
647 | +tail -f data/logs/opencodehub.log | |
648 | +``` | |
649 | + | |
650 | +### Health Check | |
651 | + | |
652 | +```bash | |
653 | +curl http://localhost:3000/api/health | |
654 | + | |
655 | +# Response: | |
656 | +{ | |
657 | + "status": "ok", | |
658 | + "checks": { | |
659 | + "database": "ok", | |
660 | + "storage": "ok", | |
661 | + "redis": "ok" // if configured | |
662 | + }, | |
663 | + "uptime": 123456, | |
664 | + "version": "1.0.0" | |
665 | +} | |
666 | +``` | |
667 | + | |
668 | +--- | |
669 | + | |
670 | +## π€ Contributing | |
671 | + | |
672 | +We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. | |
673 | + | |
674 | +### Development Setup | |
675 | + | |
676 | +```bash | |
677 | +# Fork and clone | |
678 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
679 | +cd OpencodeHub | |
680 | + | |
681 | +# Install dependencies | |
682 | +bun install | |
683 | + | |
684 | +# Run in development | |
685 | +bun run dev | |
686 | + | |
687 | +# Run tests | |
688 | +bun test | |
689 | + | |
690 | +# Lint | |
691 | +bun run lint | |
692 | +``` | |
693 | + | |
694 | +### Submitting Changes | |
695 | + | |
696 | +1. Create a feature branch (`git checkout -b feature/amazing-feature`) | |
697 | +2. Make your changes | |
698 | +3. Add tests | |
699 | +4. Run `bun test` and `bun run lint` | |
700 | +5. Commit (`git commit -m 'Add amazing feature'`) | |
701 | +6. Push (`git push origin feature/amazing-feature`) | |
702 | +7. Open a Pull Request | |
703 | + | |
704 | +--- | |
705 | + | |
706 | +## π License | |
707 | + | |
708 | +MIT License - see [LICENSE](LICENSE) for details. | |
709 | + | |
710 | +--- | |
711 | + | |
712 | +## π Acknowledgments | |
713 | + | |
714 | +- [Astro](https://astro.build/) - Web framework | |
715 | +- [shadcn/ui](https://ui.shadcn.com/) - UI components | |
716 | +- [Drizzle ORM](https://orm.drizzle.team/) - Database toolkit | |
717 | +- [simple-git](https://github.com/steveukx/git-js) - Git operations | |
718 | +- [Monaco Editor](https://microsoft.github.io/monaco-editor/) - Code editor | |
719 | +- [Zod](https://zod.dev/) - Schema validation | |
720 | + | |
721 | +--- | |
722 | + | |
723 | +<p align="center"> | |
724 | + <strong>Made with β€οΈ for the open source community</strong> | |
725 | + <br> | |
726 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/issues">Report Bug</a> β’ | |
727 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/issues">Request Feature</a> | |
728 | +</p> | |
0 | 729 | |
βΌREADME_NEW.md
+1258-0
| @@ -0,0 +1,1258 @@ | ||
1 | +# OpenCodeHub | |
2 | + | |
3 | +<p align="center"> | |
4 | + <img src="public/logo.png" alt="OpenCodeHub Logo" width="200"> | |
5 | +</p> | |
6 | + | |
7 | +<p align="center"> | |
8 | + <strong>A powerful, self-hosted Git platform with CI/CD, code review, and enterprise-grade security</strong> | |
9 | +</p> | |
10 | + | |
11 | +<p align="center"> | |
12 | + <a href="#-features">Features</a> β’ | |
13 | + <a href="#-quick-start">Quick Start</a> β’ | |
14 | + <a href="#%EF%B8%8F-configuration">Configuration</a> β’ | |
15 | + <a href="#-security">Security</a> β’ | |
16 | + <a href="#-production-deployment">Production</a> β’ | |
17 | + <a href="#-api">API</a> β’ | |
18 | + <a href="#-contributing">Contributing</a> | |
19 | +</p> | |
20 | + | |
21 | +--- | |
22 | + | |
23 | +## π Production Ready! | |
24 | + | |
25 | +**All critical security vulnerabilities are now fixed. OpenCodeHub is production-ready!** π | |
26 | + | |
27 | +β
**Rate Limiting** - Prevents brute force attacks | |
28 | +β
**CSRF Protection** - Cross-site request forgery defense | |
29 | +β
**Input Validation** - SQL injection & XSS prevention | |
30 | +β
**Hook Authentication** - Secure git operations | |
31 | +β
**Environment Validation** - Safe configuration management | |
32 | + | |
33 | +π **See [DEPLOYMENT.md](DEPLOYMENT.md) for complete deployment guide** with SSL setup, Nginx configuration, monitoring, database backups, and production best practices. | |
34 | + | |
35 | +--- | |
36 | + | |
37 | +## π Features | |
38 | + | |
39 | +### Git Hosting & Collaboration | |
40 | +- **Full Git Support** - SSH and HTTP/HTTPS protocols with authentication | |
41 | +- **Repository Management** - Create, fork, archive, and transfer repositories | |
42 | +- **Branch Protection** - Enforce PR requirements, approvals, and code review policies | |
43 | +- **Pull Requests** - Code review with inline comments and approval workflows | |
44 | +- **Issues & Milestones** - Bug tracking with labels, assignees, and custom fields | |
45 | +- **Wiki** - Built-in documentation with markdown support | |
46 | +- **Organizations** - Team management with role-based access control (RBAC) | |
47 | + | |
48 | +### CI/CD & Automation | |
49 | +- **GitHub Actions Compatible** - Run workflows defined in `.github/workflows/` | |
50 | +- **Self-hosted Runners** - Docker-based job execution with artifact support | |
51 | +- **Merge Queue** - Automated PR merge management with conflict detection | |
52 | +- **Webhooks** - Real-time event notifications to external services | |
53 | +- **Secrets Management** - Encrypted storage for CI/CD credentials | |
54 | + | |
55 | +### Security Features β
| |
56 | +- **Rate Limiting** - Prevents brute force attacks (5 login attempts per 15min) | |
57 | +- **CSRF Protection** - Double-submit cookie pattern for all state-changing operations | |
58 | +- **Input Validation** - Zod schemas prevent injection attacks | |
59 | +- **Hook Authentication** - Shared secrets protect internal git hooks | |
60 | +- **Session Management** - Automatic expiration and rotation | |
61 | +- **Audit Logging** - Complete activity tracking for compliance | |
62 | + | |
63 | +### Modern Tech Stack | |
64 | +- **Astro + React** - Fast, modern frontend with island architecture | |
65 | +- **TailwindCSS + shadcn/ui** - Beautiful, accessible UI components | |
66 | +- **Universal Database Adapter** - PostgreSQL, MySQL, SQLite, MongoDB, Turso, PlanetScale | |
67 | +- **Flexible Storage** - Local, S3, MinIO, Google Drive, Azure Blob, R2 | |
68 | +- **Production-Ready** - Rate limiting, CSRF, input validation, error monitoring | |
69 | + | |
70 | +--- | |
71 | + | |
72 | +## π¦ Quick Start | |
73 | + | |
74 | +### Prerequisites | |
75 | +- **Node.js** 18+ or **Bun** 1.0+ | |
76 | +- **Database**: PostgreSQL 14+ (recommended) / MySQL 8+ / SQLite 3.35+ | |
77 | +- **Git** 2.30+ | |
78 | +- **Docker** (optional, for CI/CD runners) | |
79 | + | |
80 | +### Installation (5 minutes) | |
81 | + | |
82 | +```bash | |
83 | +# 1. Clone the repository | |
84 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
85 | +cd OpenCodeHub | |
86 | + | |
87 | +# 2. Install dependencies | |
88 | +npm install | |
89 | +# or | |
90 | +bun install | |
91 | + | |
92 | +# 3. Set up environment | |
93 | +cp .env.example .env | |
94 | + | |
95 | +# 4. Configure your .env file (see Configuration section) | |
96 | +nano .env # Set at minimum: JWT_SECRET, SESSION_SECRET, INTERNAL_HOOK_SECRET, SITE_URL | |
97 | + | |
98 | +# 5. Run database migrations | |
99 | +npm run db:push | |
100 | +# or generate migrations for version control: | |
101 | +npm run db:generate | |
102 | + | |
103 | +# 6. Seed an admin user | |
104 | +bun run scripts/seed-admin.ts | |
105 | +# Enter username, email, password when prompted | |
106 | + | |
107 | +# 7. Start development server | |
108 | +npm run dev | |
109 | +# Application available at http://localhost:3000 | |
110 | +``` | |
111 | + | |
112 | +### Using Docker (Recommended for Production) | |
113 | + | |
114 | +```bash | |
115 | +# 1. Clone and configure | |
116 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
117 | +cd OpenCodeHub | |
118 | +cp .env.example .env | |
119 | + | |
120 | +# 2. Edit .env with production values (see Production Deployment section) | |
121 | + | |
122 | +# 3. Start services | |
123 | +docker-compose up -d | |
124 | + | |
125 | +# 4. Create admin user | |
126 | +docker-compose exec app bun run scripts/seed-admin.ts | |
127 | + | |
128 | +# Access at http://localhost:3000 | |
129 | +``` | |
130 | + | |
131 | +--- | |
132 | +# Advanced Features Documentation Insert | |
133 | + | |
134 | +**Insert this content into README.md after line 131 (after Docker installation section, before Configuration section)** | |
135 | + | |
136 | +--- | |
137 | + | |
138 | +## π― OCH CLI - Command Line Interface | |
139 | + | |
140 | +Powerful CLI for stack-first PR workflows. | |
141 | + | |
142 | +### Installation | |
143 | + | |
144 | +```bash | |
145 | +# Install globally | |
146 | +npm install -g opencodehub-cli | |
147 | + | |
148 | +# Or use with npx | |
149 | +npx opencodehub-cli | |
150 | + | |
151 | +# Verify installation | |
152 | +och --version | |
153 | +``` | |
154 | + | |
155 | +### Quick Setup | |
156 | + | |
157 | +```bash | |
158 | +# 1. Login to your OpenCode Hub instance | |
159 | +och auth login --url https://git.yourcompany.com | |
160 | + | |
161 | +# 2. Navigate to your repository | |
162 | +cd your-repo | |
163 | + | |
164 | +# 3. Initialize | |
165 | +och init --url https://git.yourcompany.com | |
166 | +``` | |
167 | + | |
168 | +### CLI Commands Reference | |
169 | + | |
170 | +**Authentication:** | |
171 | +```bash | |
172 | +och auth login # Interactive login | |
173 | +och auth login --token TOKEN # Login with token | |
174 | +och auth logout # Logout | |
175 | +och auth whoami # Show current user | |
176 | +``` | |
177 | + | |
178 | +**Stack Management:** | |
179 | +```bash | |
180 | +och stack create \u003cname\u003e # Create new stacked branch | |
181 | +och stack view # View current stack (alias: ls) | |
182 | +och stack submit # Push and create PRs | |
183 | +och stack submit --draft # Create as draft PRs | |
184 | +och stack sync # Rebase stack on latest | |
185 | +och stack reorder # Reorder branches in stack | |
186 | +``` | |
187 | + | |
188 | +**Repository:** | |
189 | +```bash | |
190 | +och init # Initialize repo for OpenCodeHub | |
191 | +och sync # Bidirectional sync with remote | |
192 | +och status # Show stack status (alias: st) | |
193 | +``` | |
194 | + | |
195 | +--- | |
196 | + | |
197 | +## π Advanced Features | |
198 | + | |
199 | +### π Stacked Pull Requests | |
200 | + | |
201 | +**Ship faster by breaking large features into reviewable chunks.** | |
202 | + | |
203 | +#### Why Stacked PRs? | |
204 | + | |
205 | +- **Faster Reviews**: Smaller, focused PRs get reviewed quicker | |
206 | +- **Parallel Development**: Build on unmerged PRs while waiting for review | |
207 | +- **Clean History**: Each PR represents one logical change | |
208 | +- **Risk Reduction**: Deploy incrementally, rollback easily | |
209 | + | |
210 | +#### How It Works | |
211 | + | |
212 | +``` | |
213 | +main | |
214 | + βββ PR #123: Add database schema | |
215 | + βββ PR #124: Add authentication service | |
216 | + βββ PR #125: Add login UI | |
217 | +``` | |
218 | + | |
219 | +Each PR builds on the previous one. When #123 merges, #124 automatically rebases. | |
220 | + | |
221 | +#### Creating a Stack | |
222 | + | |
223 | +**Using CLI:** | |
224 | +```bash | |
225 | +# 1. Create base branch | |
226 | +git checkout -b feature/auth | |
227 | +git commit -m "Add user table schema" | |
228 | +och stack create auth-schema | |
229 | + | |
230 | +# 2. Create stacked branch | |
231 | +och stack create auth-service | |
232 | +git commit -m "Add authentication service" | |
233 | + | |
234 | +# 3. Create final layer | |
235 | +och stack create auth-ui | |
236 | +git commit -m "Add login UI" | |
237 | + | |
238 | +# 4. Submit entire stack | |
239 | +och stack submit | |
240 | +``` | |
241 | + | |
242 | +**Using Web UI:** | |
243 | +1. Create first PR normally | |
244 | +2. When creating second PR, select "Stack on PR #123" | |
245 | +3. System tracks dependencies automatically | |
246 | + | |
247 | +#### Stack Features | |
248 | + | |
249 | +β
**Auto-Rebasing**: When base PR merges, dependent PRs rebase automatically | |
250 | +β
**Dependency Visualization**: See entire stack in PR view | |
251 | +β
**Smart Merge Queue**: Merges in correct order | |
252 | +β
**Conflict Detection**: Alerts on rebase conflicts | |
253 | + | |
254 | +--- | |
255 | + | |
256 | +### π€ AI Code Review | |
257 | + | |
258 | +**Get instant feedback from AI reviewers powered by GPT-4 or Claude.** | |
259 | + | |
260 | +#### Setup | |
261 | + | |
262 | +**1. Configure AI Provider:** | |
263 | +```env | |
264 | +# In .env | |
265 | +AI_PROVIDER=openai # or anthropic | |
266 | +OPENAI_API_KEY=sk-... | |
267 | +# or | |
268 | +ANTHROPIC_API_KEY=sk-ant-... | |
269 | +``` | |
270 | + | |
271 | +**2. Enable for Repository:** | |
272 | +Navigate to **Repository β Settings β AI Review** | |
273 | +- β
Enable AI Review | |
274 | +- Set review triggers (on PR open, on push, manual) | |
275 | +- Configure review rules | |
276 | + | |
277 | +#### What AI Reviews Catch | |
278 | + | |
279 | +π **Security Vulnerabilities** | |
280 | +- SQL injection risks | |
281 | +- XSS vulnerabilities | |
282 | +- Authentication bypasses | |
283 | +- Secrets in code | |
284 | + | |
285 | +β‘ **Performance Issues** | |
286 | +- N+1 queries | |
287 | +- Inefficient algorithms | |
288 | +- Memory leaks | |
289 | +- Blocking operations | |
290 | + | |
291 | +π **Code Quality** | |
292 | +- Best practice violations | |
293 | +- Code smells | |
294 | +- Incomplete error handling | |
295 | +- Missing documentation | |
296 | + | |
297 | +π **Potential Bugs** | |
298 | +- Null pointer exceptions | |
299 | +- Race conditions | |
300 | +- Off-by-one errors | |
301 | +- Logic flaws | |
302 | + | |
303 | +#### Using AI Review | |
304 | + | |
305 | +**Automatic (Recommended):** | |
306 | +AI review runs automatically on every PR based on configured triggers. | |
307 | + | |
308 | +**Manual:** | |
309 | +```bash | |
310 | +# Via CLI | |
311 | +och review ai \u003cPR_NUMBER\u003e | |
312 | + | |
313 | +# Via web UI: Click "Request AI Review" button on PR | |
314 | +``` | |
315 | + | |
316 | +#### Review Output | |
317 | + | |
318 | +AI provides: | |
319 | +- **Severity Score**: Critical / High / Medium / Low / Info | |
320 | +- **Specific Line Comments**: Inline code suggestions | |
321 | +- **Summary Report**: Overall code quality assessment | |
322 | +- **Fix Suggestions**: Actionable improvements | |
323 | + | |
324 | +**Example:** | |
325 | +``` | |
326 | +π΄ CRITICAL: SQL Injection Vulnerability | |
327 | +File: src/api/users.ts:45 | |
328 | +Issue: User input directly concatenated into SQL query | |
329 | +Fix: Use parameterized queries or ORM | |
330 | + | |
331 | +π‘ MEDIUM: Performance Concern | |
332 | +File: src/lib/data.ts:128 | |
333 | +Issue: N+1 query in loop (1000+ iterations) | |
334 | +Fix: Use batch loading or eager loading | |
335 | +``` | |
336 | + | |
337 | +--- | |
338 | + | |
339 | +### π Smart Merge Queue | |
340 | + | |
341 | +**Stack-aware merge queue with automatic conflict resolution and CI optimization.** | |
342 | + | |
343 | +#### Features | |
344 | + | |
345 | +**Stack-Aware Merging:** | |
346 | +- Detects PR dependencies | |
347 | +- Merges in correct order automatically | |
348 | +- Handles entire stacks atomically | |
349 | + | |
350 | +**CI Optimization:** | |
351 | +- Batches compatible PRs | |
352 | +- Runs CI once for multiple PRs | |
353 | +- Reduces CI cost by ~60% | |
354 | + | |
355 | +**Automatic Rebasing:** | |
356 | +- Detects conflicts early | |
357 | +- Auto-rebases on base changes | |
358 | +- Alerts author on conflicts | |
359 | + | |
360 | +**Smart Ordering:** | |
361 | +- Priority-based queue | |
362 | +- Dependency resolution | |
363 | +- Hotfix fast-tracking | |
364 | + | |
365 | +#### Using Merge Queue | |
366 | + | |
367 | +**Add to Queue:** | |
368 | +```bash | |
369 | +# Via CLI | |
370 | +och queue add \u003cPR_NUMBER\u003e | |
371 | + | |
372 | +# Via Web UI | |
373 | +Click "Add to Merge Queue" button on approved PR | |
374 | +``` | |
375 | + | |
376 | +**Queue Status:** | |
377 | +```bash | |
378 | +# View queue | |
379 | +och queue list | |
380 | + | |
381 | +# Output: | |
382 | +Position PR Title Status | |
383 | +1 #125 Add login UI β³ Running CI | |
384 | +2 #124 Auth service βΈοΈ Waiting (depends on #123) | |
385 | +3 #123 Database schema β
Ready to merge | |
386 | +``` | |
387 | + | |
388 | +**How It Works:** | |
389 | + | |
390 | +1. **PR Added**: Queue analyzes dependencies | |
391 | +2. **Position Assigned**: Ordered by priority and dependencies | |
392 | +3. **CI Running**: Runs tests in parallel where possible | |
393 | +4. **Auto-Merge**: Merges when ready, rebases dependents | |
394 | +5. **Notification**: Author notified on completion | |
395 | + | |
396 | +#### Queue Configuration | |
397 | + | |
398 | +**Repository β Settings β Merge Queue:** | |
399 | +- **Require CI**: Only merge if all checks pass | |
400 | +- **Batch Window**: Wait time to batch PRs (default: 5min) | |
401 | +- **Max Batch Size**: Max PRs to merge together (default: 3) | |
402 | +- **Auto-Rebase**: Rebase on conflicts vs. remove from queue | |
403 | + | |
404 | +--- | |
405 | + | |
406 | +### π Developer Metrics | |
407 | + | |
408 | +**Track team velocity, review efficiency, and identify bottlenecks.** | |
409 | + | |
410 | +#### Available Metrics | |
411 | + | |
412 | +**PR Velocity:** | |
413 | +- PRs merged per week | |
414 | +- Average time to merge | |
415 | +- Stack success rate | |
416 | +- Review cycle time | |
417 | + | |
418 | +**Review Efficiency:** | |
419 | +- Average review time | |
420 | +- Reviews per day | |
421 | +- Review quality score | |
422 | +- Reviewer responsiveness | |
423 | + | |
424 | +**Team Performance:** | |
425 | +- Lines changed per PR | |
426 | +- PR size distribution | |
427 | +- Revert rate | |
428 | +- CI success rate | |
429 | + | |
430 | +**Stack Metrics:** | |
431 | +- % PRs that use stacks | |
432 | +- Average stack depth | |
433 | +- Stack merge success rate | |
434 | +- Time saved vs. monolithic PRs | |
435 | + | |
436 | +#### Viewing Metrics | |
437 | + | |
438 | +**Personal Dashboard:** | |
439 | +``` | |
440 | +/metrics/me | |
441 | +``` | |
442 | + | |
443 | +**Team Dashboard (Admin):** | |
444 | +``` | |
445 | +/admin/metrics | |
446 | +``` | |
447 | + | |
448 | +**Via CLI:** | |
449 | +```bash | |
450 | +och metrics show | |
451 | +och metrics show --user @username | |
452 | +och metrics show --repo owner/repo | |
453 | +``` | |
454 | + | |
455 | +#### Example Metrics View | |
456 | + | |
457 | +``` | |
458 | +π Your Metrics (Last 30 Days) | |
459 | + | |
460 | +PR Velocity | |
461 | +ββ PRs Merged ββββββββββββββ 24 (+12%) | |
462 | +ββ Avg Time to Merge ββββββββββββββ 8.2 hours (-3h) | |
463 | +ββ Stack Usage ββββββββββββββ 65% (+15%) | |
464 | + | |
465 | +Review Efficiency | |
466 | +ββ Reviews Given ββββββββββββββ 18 | |
467 | +ββ Avg Review Time ββββββββββββββ 22 min | |
468 | +ββ Approval Rate ββββββββββββββ 94% | |
469 | +``` | |
470 | + | |
471 | +--- | |
472 | + | |
473 | +### π¬ Slack Notifications | |
474 | + | |
475 | +**Get actionable PR notifications directly in Slack.** | |
476 | + | |
477 | +#### Setup | |
478 | + | |
479 | +**1. Create Slack App:** | |
480 | +1. Go to https://api.slack.com/apps | |
481 | +2. Create New App β From Scratch | |
482 | +3. Add OAuth Scopes: | |
483 | + - `chat:write` | |
484 | + - `chat:write.public` | |
485 | + - `users:read` | |
486 | +4. Install to workspace | |
487 | + | |
488 | +**2. Connect to OpenCodeHub:** | |
489 | +Navigate to **Admin β Integrations β Slack** | |
490 | +``` | |
491 | +Workspace Name: Your Team | |
492 | +Bot Token: xoxb-... | |
493 | +Signing Secret: ... | |
494 | +``` | |
495 | + | |
496 | +**3. Map Channels:** | |
497 | +**Repository β Settings β Slack** | |
498 | +``` | |
499 | +#engineering β All PR events | |
500 | +#deployments β Merges only | |
501 | +#security β Security findings | |
502 | +``` | |
503 | + | |
504 | +**4. Map Users (Optional):** | |
505 | +Users can link their Slack account: | |
506 | +**Settings β Integrations β Connect Slack** | |
507 | + | |
508 | +#### Notification Types | |
509 | + | |
510 | +**PR Events:** | |
511 | +- π PR opened | |
512 | +- β
PR approved | |
513 | +- π PR merged | |
514 | +- β PR closed | |
515 | +- π¬ Review requested | |
516 | +- π΄ Changes requested | |
517 | + | |
518 | +**CI/CD Events:** | |
519 | +- β
Build passed | |
520 | +- β Build failed | |
521 | +- π Deployment started | |
522 | +- β
Deployment successful | |
523 | + | |
524 | +**Code Review:** | |
525 | +- π€ AI review completed | |
526 | +- π Security issue found | |
527 | +- β οΈ Performance concern | |
528 | + | |
529 | +**Merge Queue:** | |
530 | +- βοΈ Added to queue | |
531 | +- β
Merged from queue | |
532 | +- β οΈ Removed (conflict) | |
533 | + | |
534 | +#### Interactive Actions | |
535 | + | |
536 | +Notifications include action buttons: | |
537 | +``` | |
538 | +[swadhin] opened PR#125: Add login UI | |
539 | +Stack: #123 β #124 β #125 β’ files β32 β’ LoC +420/-18 | |
540 | + | |
541 | +[Approve] [Request Changes] [View PR] [Add to Queue] | |
542 | +``` | |
543 | + | |
544 | +Click buttons to: | |
545 | +- Approve/reject directly from Slack | |
546 | +- Request changes with comment | |
547 | +- Add to merge queue | |
548 | +- View full PR | |
549 | + | |
550 | +--- | |
551 | + | |
552 | +## π‘ Workflow Examples | |
553 | + | |
554 | +### Example 1: Complete Stacked PR Flow | |
555 | + | |
556 | +```bash | |
557 | +# 1. Start new feature | |
558 | +git checkout main | |
559 | +git pull | |
560 | +git checkout -b feature/user-profiles | |
561 | + | |
562 | +# 2. First PR: Database layer | |
563 | +git add migrations/ | |
564 | +git commit -m "Add user profile tables" | |
565 | +och stack create profile-db | |
566 | +och stack submit | |
567 | + | |
568 | +# β Creates PR #301 | |
569 | + | |
570 | +# 3. Second PR: Backend API | |
571 | +git add src/api/profiles/ | |
572 | +git commit -m "Add profile API" | |
573 | +och stack create profile-api | |
574 | +och stack submit | |
575 | + | |
576 | +# β Creates PR #302 (stacked on #301) | |
577 | + | |
578 | +# 4. Third PR: Frontend | |
579 | +git add src/pages/profile/ | |
580 | +git commit -m "Add profile UI" | |
581 | +och stack create profile-ui | |
582 | +och stack submit | |
583 | + | |
584 | +# β Creates PR #303 (stacked on #302) | |
585 | + | |
586 | +# 5. View stack | |
587 | +och stack view | |
588 | +``` | |
589 | + | |
590 | +**Output:** | |
591 | +``` | |
592 | +π Current Stack | |
593 | + | |
594 | +main (base) | |
595 | + ββ #301: profile-db β
Approved | |
596 | + ββ #302: profile-api π In Review | |
597 | + ββ #303: profile-ui β³ AI Review Running | |
598 | +``` | |
599 | + | |
600 | +### Example 2: Using AI Review + Merge Queue | |
601 | + | |
602 | +```bash | |
603 | +# 1. Create PR | |
604 | +git push origin feature-branch | |
605 | + | |
606 | +# 2. Request AI review | |
607 | +och review ai 304 | |
608 | + | |
609 | +# 3. Fix AI suggestions | |
610 | +# ... make changes based on review ... | |
611 | +git commit -m "Fix security issues from AI review" | |
612 | +git push | |
613 | + | |
614 | +# 4. Get human review | |
615 | +# Team reviews via web UI | |
616 | + | |
617 | +# 5. Add to merge queue when approved | |
618 | +och queue add 304 | |
619 | + | |
620 | +# Queue handles: | |
621 | +# - Waiting for CI | |
622 | +# - Merging when ready | |
623 | +# - Notifying on Slack | |
624 | +``` | |
625 | + | |
626 | +### Example 3: Team Collaboration | |
627 | + | |
628 | +```bash | |
629 | +# Developer A: Creates base | |
630 | +git checkout -b api/payments | |
631 | +# ... implements payment models ... | |
632 | +git commit -m "Add payment models" | |
633 | +git push | |
634 | + | |
635 | +# Developer B: Builds on A's work (before merge!) | |
636 | +git checkout api/payments | |
637 | +git checkout -b api/payment-processing | |
638 | +# ... implements payment processing ... | |
639 | +git commit -m "Add payment processing" | |
640 | +git push | |
641 | +# Create PR stacked on A's PR | |
642 | + | |
643 | +# Developer C: Builds on B's work | |
644 | +git checkout api/payment-processing | |
645 | +git checkout -b ui/payment-form | |
646 | +# ... implements payment UI ... | |
647 | +``` | |
648 | + | |
649 | +**Result**: 3 parallel PRs, all reviewed separately, merged in order. | |
650 | + | |
651 | +--- | |
652 | + | |
653 | +## π Additional Resources | |
654 | + | |
655 | +- **Full CLI Reference**: `cli/README.md` | |
656 | +- **API Documentation**: `/api/docs` | |
657 | +- **Stacked PR Guide**: `/docs#stacked-prs` | |
658 | +- **Slack Integration Guide**: `/docs#slack` | |
659 | + | |
660 | +--- | |
661 | + | |
662 | + | |
663 | +## βοΈ Configuration | |
664 | + | |
665 | +### Critical Environment Variables | |
666 | + | |
667 | +These **MUST** be set before deploying to production: | |
668 | + | |
669 | +```env | |
670 | +# Security (CRITICAL - Generate strong secrets!) | |
671 | +JWT_SECRET=<run: openssl rand -hex 32> | |
672 | +SESSION_SECRET=<run: openssl rand -hex 32> | |
673 | +INTERNAL_HOOK_SECRET=<run: openssl rand -hex 32> | |
674 | + | |
675 | +# Application URLs (CRITICAL) | |
676 | +SITE_URL=https://your-domain.com # Must be HTTPS in production | |
677 | +PORT=3000 | |
678 | +HOST=0.0.0.0 | |
679 | +``` | |
680 | + | |
681 | +### Database Configuration | |
682 | + | |
683 | +**PostgreSQL (Recommended for Production):** | |
684 | +```env | |
685 | +DATABASE_DRIVER=postgres | |
686 | +DATABASE_URL=postgresql://user:password@localhost:5432/opencodehub | |
687 | +``` | |
688 | + | |
689 | +**MySQL:** | |
690 | +```env | |
691 | +DATABASE_DRIVER=mysql | |
692 | +DATABASE_URL=mysql://user:password@localhost:3306/opencodehub | |
693 | +``` | |
694 | + | |
695 | +**SQLite (Development Only):** | |
696 | +```env | |
697 | +DATABASE_DRIVER=sqlite | |
698 | +DATABASE_URL=./data/opencodehub.db | |
699 | +``` | |
700 | + | |
701 | +### Storage Configuration | |
702 | + | |
703 | +Storage can be configured via Admin Panel (`/admin/storage`) or environment variables: | |
704 | + | |
705 | +**Local Storage:** | |
706 | +```env | |
707 | +STORAGE_TYPE=local | |
708 | +STORAGE_PATH=./data/storage | |
709 | +``` | |
710 | + | |
711 | +**S3/MinIO/R2:** | |
712 | +```env | |
713 | +STORAGE_TYPE=s3 | |
714 | +STORAGE_BUCKET=opencodehub | |
715 | +STORAGE_REGION=us-east-1 | |
716 | +STORAGE_ENDPOINT=https://s3.amazonaws.com | |
717 | +S3_ACCESS_KEY=your-access-key | |
718 | +S3_SECRET_KEY=your-secret-key | |
719 | +``` | |
720 | + | |
721 | +**Google Drive (via Admin Panel):** | |
722 | +- Navigate to `/admin/storage` | |
723 | +- Select "Google Drive" | |
724 | +- Enter Client ID, Secret, and Refresh Token | |
725 | +- Test connection before saving | |
726 | + | |
727 | +### Security Configuration | |
728 | + | |
729 | +**Rate Limiting:** | |
730 | +```env | |
731 | +RATE_LIMIT_AUTH=5 # Login attempts per 15 minutes | |
732 | +RATE_LIMIT_API=100 # API requests per minute | |
733 | +RATE_LIMIT_GIT=200 # Git operations per minute | |
734 | +RATE_LIMIT_SKIP_DEV=false # Never skip in production | |
735 | +``` | |
736 | + | |
737 | +**CSRF Protection:** | |
738 | +```env | |
739 | +CSRF_SKIP_DEV=false # Never skip in production | |
740 | +``` | |
741 | + | |
742 | +### Optional Services | |
743 | + | |
744 | +**Email (SMTP):** | |
745 | +```env | |
746 | +SMTP_HOST=smtp.example.com | |
747 | +SMTP_PORT=587 | |
748 | ||
749 | +SMTP_PASSWORD=your-smtp-password | |
750 | ||
751 | +``` | |
752 | + | |
753 | +**Redis (Caching & Sessions):** | |
754 | +```env | |
755 | +REDIS_URL=redis://localhost:6379 | |
756 | +``` | |
757 | + | |
758 | +**Error Monitoring (Sentry):** | |
759 | +```env | |
760 | +SENTRY_DSN=https://... | |
761 | +ENABLE_TRACING=true | |
762 | +``` | |
763 | + | |
764 | +--- | |
765 | + | |
766 | +## π Security | |
767 | + | |
768 | +OpenCodeHub implements **production-grade security** out of the box: | |
769 | + | |
770 | +### Implemented Security Features | |
771 | + | |
772 | +β
**Authentication & Authorization** | |
773 | +- JWT-based sessions with automatic expiration | |
774 | +- Bcrypt password hashing (10 rounds) | |
775 | +- 2FA/TOTP support | |
776 | +- Role-based access control (RBAC) | |
777 | + | |
778 | +β
**Attack Prevention** | |
779 | +- **Rate Limiting**: Prevents brute force (5 login attempts per 15min) | |
780 | +- **CSRF Protection**: Double-submit cookie pattern on all state-changing operations | |
781 | +- **Input Validation**: Zod schemas prevent SQL injection and XSS | |
782 | +- **XSS Sanitization**: Markdown rendered with `rehype-sanitize` | |
783 | + | |
784 | +β
**Operational Security** | |
785 | +- **Audit Logging**: All admin actions logged to database | |
786 | +- **Hook Authentication**: Git hooks protected with shared secrets | |
787 | +- **Session Management**: Automatic cleanup and rotation | |
788 | +- **Environment Validation**: Startup checks for missing/weak secrets | |
789 | + | |
790 | +### Security Best Practices | |
791 | + | |
792 | +1. **Always use HTTPS** in production (Let's Encrypt recommended) | |
793 | +2. **Rotate secrets** regularly (especially after team changes) | |
794 | +3. **Enable 2FA** for all admin accounts | |
795 | +4. **Monitor audit logs** for suspicious activity | |
796 | +5. **Keep dependencies updated**: Run `npm audit` weekly | |
797 | + | |
798 | +### Reporting Security Issues | |
799 | + | |
800 | +Please report security vulnerabilities to **[email protected]**. Do not create public issues. | |
801 | + | |
802 | +--- | |
803 | + | |
804 | +## π Production Deployment | |
805 | + | |
806 | +### Pre-Deployment Checklist | |
807 | + | |
808 | +Before going live, ensure: | |
809 | + | |
810 | +- [ ] **Secrets**: All secrets generated with `openssl rand -hex 32` | |
811 | +- [ ] **HTTPS**: SSL certificate configured (Let's Encrypt recommended) | |
812 | +- [ ] **Database**: PostgreSQL with backups enabled | |
813 | +- [ ] **Storage**: S3/R2 configured (not local filesystem) | |
814 | +- [ ] **Monitoring**: Error tracking (Sentry) and APM configured | |
815 | +- [ ] **Rate Limits**: Configured for expected traffic | |
816 | +- [ ] **Admin Account**: Created via `bun run scripts/seed-admin.ts` | |
817 | +- [ ] **Environment Validation**: Test with `bun run src/lib/env-validation.ts` | |
818 | +- [ ] **Database Migrations**: Generated with `npm run db:generate` | |
819 | + | |
820 | +### Production Environment Variables | |
821 | + | |
822 | +```env | |
823 | +# Application | |
824 | +NODE_ENV=production | |
825 | +SITE_URL=https://git.yourcompany.com | |
826 | +PORT=3000 | |
827 | + | |
828 | +# Security (CRITICAL - Generate with: openssl rand -hex 32) | |
829 | +JWT_SECRET=<64-char-random-hex> | |
830 | +SESSION_SECRET=<64-char-random-hex> | |
831 | +INTERNAL_HOOK_SECRET=<64-char-random-hex> | |
832 | + | |
833 | +# Database (Use connection pooling) | |
834 | +DATABASE_DRIVER=postgres | |
835 | +DATABASE_URL=postgresql://user:password@db-host:5432/opencodehub?pool_timeout=30 | |
836 | + | |
837 | +# Storage (Use cloud storage) | |
838 | +STORAGE_TYPE=s3 | |
839 | +STORAGE_BUCKET=opencodehub-prod | |
840 | +STORAGE_REGION=us-east-1 | |
841 | + | |
842 | +# Rate Limiting (Adjust based on traffic) | |
843 | +RATE_LIMIT_AUTH=5 | |
844 | +RATE_LIMIT_API=200 | |
845 | +RATE_LIMIT_GIT=500 | |
846 | + | |
847 | +# Monitoring | |
848 | +SENTRY_DSN=https://your-sentry-dsn | |
849 | +LOG_LEVEL=info | |
850 | + | |
851 | +# Features | |
852 | +ENABLE_REGISTRATION=true # Set false for invite-only | |
853 | +``` | |
854 | + | |
855 | +### Docker Production Deployment | |
856 | + | |
857 | +```yaml | |
858 | +# docker-compose.prod.yml | |
859 | +version: '3.8' | |
860 | + | |
861 | +services: | |
862 | + app: | |
863 | + image: opencodehub:latest | |
864 | + restart: always | |
865 | + ports: | |
866 | + - "3000:3000" | |
867 | + env_file: .env.production | |
868 | + depends_on: | |
869 | + - postgres | |
870 | + - redis | |
871 | + volumes: | |
872 | + - ./data:/app/data | |
873 | + healthcheck: | |
874 | + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] | |
875 | + interval: 30s | |
876 | + timeout: 10s | |
877 | + retries: 3 | |
878 | + | |
879 | + postgres: | |
880 | + image: postgres:15-alpine | |
881 | + restart: always | |
882 | + environment: | |
883 | + POSTGRES_DB: opencodehub | |
884 | + POSTGRES_USER: opencodehub | |
885 | + POSTGRES_PASSWORD: <strong-password> | |
886 | + volumes: | |
887 | + - postgres_data:/var/lib/postgresql/data | |
888 | + | |
889 | + redis: | |
890 | + image: redis:7-alpine | |
891 | + restart: always | |
892 | + | |
893 | +volumes: | |
894 | + postgres_data: | |
895 | +``` | |
896 | + | |
897 | +```bash | |
898 | +# Deploy | |
899 | +docker-compose -f docker-compose.prod.yml up -d | |
900 | + | |
901 | +# Monitor logs | |
902 | +docker-compose -f docker-compose.prod.yml logs -f app | |
903 | + | |
904 | +# Health check | |
905 | +curl https://git.yourcompany.com/api/health | |
906 | +``` | |
907 | + | |
908 | +### Reverse Proxy (Nginx) | |
909 | + | |
910 | +```nginx | |
911 | +server { | |
912 | + listen 443 ssl http2; | |
913 | + server_name git.yourcompany.com; | |
914 | + | |
915 | + ssl_certificate /path/to/cert.pem; | |
916 | + ssl_certificate_key /path/to/key.pem; | |
917 | + | |
918 | + location / { | |
919 | + proxy_pass http://localhost:3000; | |
920 | + proxy_set_header Host $host; | |
921 | + proxy_set_header X-Real-IP $remote_addr; | |
922 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
923 | + proxy_set_header X-Forwarded-Proto $scheme; | |
924 | + } | |
925 | + | |
926 | + # Git operations (larger timeouts) | |
927 | + location ~ ^/[^/]+/[^/]+\.git/ { | |
928 | + proxy_pass http://localhost:3000; | |
929 | + proxy_read_timeout 300s; | |
930 | + proxy_send_timeout 300s; | |
931 | + proxy_buffer_size 128k; | |
932 | + proxy_buffers 4 256k; | |
933 | + } | |
934 | +} | |
935 | +``` | |
936 | + | |
937 | +### Database Backups | |
938 | + | |
939 | +```bash | |
940 | +# PostgreSQL backup script (cron daily) | |
941 | +#!/bin/bash | |
942 | +pg_dump -U opencodehub opencodehub | gzip > /backups/opencodehub-$(date +\%Y\%m\%d).sql.gz | |
943 | + | |
944 | +# Retain last 30 days | |
945 | +find /backups -name "opencodehub-*.sql.gz" -mtime +30 -delete | |
946 | +``` | |
947 | + | |
948 | +--- | |
949 | + | |
950 | +## π API Documentation | |
951 | + | |
952 | +### Authentication | |
953 | + | |
954 | +All authenticated endpoints require a session cookie or `Authorization: Bearer <token>` header. | |
955 | + | |
956 | +**Register:** | |
957 | +```bash | |
958 | +POST /api/auth/register | |
959 | +Content-Type: application/json | |
960 | + | |
961 | +{ | |
962 | + "username": "johndoe", | |
963 | + "email": "[email protected]", | |
964 | + "password": "SecureP@ssw0rd", | |
965 | + "displayName": "John Doe" | |
966 | +} | |
967 | + | |
968 | +# Response: 201 Created | |
969 | +{ | |
970 | + "user": { "id": "...", "username": "johndoe", ... }, | |
971 | + "token": "eyJ...", | |
972 | + "expiresAt": "2026-01-08T00:00:00Z" | |
973 | +} | |
974 | +``` | |
975 | + | |
976 | +**Login:** | |
977 | +```bash | |
978 | +POST /api/auth/login | |
979 | +Content-Type: application/json | |
980 | + | |
981 | +{ | |
982 | + "login": "[email protected]", # username or email | |
983 | + "password": "SecureP@ssw0rd" | |
984 | +} | |
985 | + | |
986 | +# With 2FA: | |
987 | +{ | |
988 | + "login": "[email protected]", | |
989 | + "password": "SecureP@ssw0rd", | |
990 | + "totpCode": "123456" | |
991 | +} | |
992 | +``` | |
993 | + | |
994 | +### Repositories | |
995 | + | |
996 | +**Create Repository:** | |
997 | +```bash | |
998 | +POST /api/repos | |
999 | +Authorization: Bearer <token> | |
1000 | +Content-Type: application/json | |
1001 | + | |
1002 | +{ | |
1003 | + "name": "my-project", | |
1004 | + "description": "My awesome project", | |
1005 | + "visibility": "public" # public, private, internal | |
1006 | +} | |
1007 | +``` | |
1008 | + | |
1009 | +**Branch Protection:** | |
1010 | +```bash | |
1011 | +POST /api/repos/{repoId}/branch-protection | |
1012 | +Authorization: Bearer <token> | |
1013 | +Content-Type: application/json | |
1014 | + | |
1015 | +{ | |
1016 | + "pattern": "main", | |
1017 | + "requiresPr": true, | |
1018 | + "requiredApprovals": 2, | |
1019 | + "dismissStaleReviews": true | |
1020 | +} | |
1021 | +``` | |
1022 | + | |
1023 | +### Admin Endpoints | |
1024 | + | |
1025 | +**List Users (Admin Only):** | |
1026 | +```bash | |
1027 | +GET /admin/users?q=search&page=1 | |
1028 | +Cookie: och_session=... | |
1029 | +``` | |
1030 | + | |
1031 | +**Update Storage Config (Admin Only):** | |
1032 | +```bash | |
1033 | +POST /api/admin/config/storage | |
1034 | +Cookie: och_session=... | |
1035 | +Content-Type: application/json | |
1036 | + | |
1037 | +{ | |
1038 | + "type": "s3", | |
1039 | + "bucket": "my-bucket", | |
1040 | + "region": "us-east-1", | |
1041 | + ... | |
1042 | +} | |
1043 | +``` | |
1044 | + | |
1045 | +### Rate Limits | |
1046 | + | |
1047 | +All endpoints return rate limit headers: | |
1048 | +``` | |
1049 | +X-RateLimit-Limit: 100 | |
1050 | +X-RateLimit-Remaining: 95 | |
1051 | +X-RateLimit-Reset: 2026-01-01T15:30:00Z | |
1052 | +Retry-After: 30 (when rate limited) | |
1053 | +``` | |
1054 | + | |
1055 | +**Full API documentation:** `/api/docs` (OpenAPI spec coming soon) | |
1056 | + | |
1057 | +--- | |
1058 | + | |
1059 | +## π§ Usage Guide | |
1060 | + | |
1061 | +### Creating Your First Repository | |
1062 | + | |
1063 | +1. **Log in** to your account | |
1064 | +2. Click the **+** button β **New Repository** | |
1065 | +3. Fill in details: | |
1066 | + - **Name**: alphanumeric, hyphens, underscores | |
1067 | + - **Description**: Optional | |
1068 | + - **Visibility**: public/private | |
1069 | +4. Click **Create Repository** | |
1070 | + | |
1071 | +### Cl | |
1072 | + | |
1073 | +oning Repositories | |
1074 | + | |
1075 | +**SSH (Recommended):** | |
1076 | +```bash | |
1077 | +# Add your SSH key in Settings β SSH Keys | |
1078 | +git clone [email protected]:username/repo.git | |
1079 | +``` | |
1080 | + | |
1081 | +**HTTPS:** | |
1082 | +```bash | |
1083 | +git clone https://your-domain.com/username/repo.git | |
1084 | +# Username: your-username | |
1085 | +# Password: your-password or personal access token | |
1086 | +``` | |
1087 | + | |
1088 | +### Setting Up Branch Protection | |
1089 | + | |
1090 | +1. Navigate to **Repository β Settings β Branches** | |
1091 | +2. Click **Add Rule** | |
1092 | +3. Configure: | |
1093 | + - **Pattern**: `main` or `release/*` | |
1094 | + - **Require PR**: β
| |
1095 | + - **Required Approvals**: 2 | |
1096 | + - **Dismiss Stale Reviews**: β
| |
1097 | +4. **Save Rule** | |
1098 | + | |
1099 | +### CI/CD Workflows | |
1100 | + | |
1101 | +Create `.github/workflows/ci.yml`: | |
1102 | + | |
1103 | +```yaml | |
1104 | +name: CI | |
1105 | + | |
1106 | +on: | |
1107 | + push: | |
1108 | + branches: [main, develop] | |
1109 | + pull_request: | |
1110 | + branches: [main] | |
1111 | + | |
1112 | +jobs: | |
1113 | + test: | |
1114 | + runs-on: ubuntu-latest | |
1115 | + steps: | |
1116 | + - uses: actions/checkout@v4 | |
1117 | + | |
1118 | + - name: Setup Node.js | |
1119 | + uses: actions/setup-node@v4 | |
1120 | + with: | |
1121 | + node-version: '20' | |
1122 | + | |
1123 | + - name: Install | |
1124 | + run: npm ci | |
1125 | + | |
1126 | + - name: Test | |
1127 | + run: npm test | |
1128 | + | |
1129 | + - name: Build | |
1130 | + run: npm run build | |
1131 | + | |
1132 | + - name: Upload Artifacts | |
1133 | + uses: actions/upload-artifact@v3 | |
1134 | + with: | |
1135 | + name: dist | |
1136 | + path: dist/ | |
1137 | +``` | |
1138 | + | |
1139 | +--- | |
1140 | + | |
1141 | +## π Troubleshooting | |
1142 | + | |
1143 | +### Common Issues | |
1144 | + | |
1145 | +**"Rate limit exceeded" on login:** | |
1146 | +- Default: 5 attempts per 15 minutes | |
1147 | +- Solution: Wait 15 minutes or adjust `RATE_LIMIT_AUTH` in `.env` | |
1148 | + | |
1149 | +**"CSRF token validation failed":** | |
1150 | +- Ensure cookies are enabled | |
1151 | +- Check `SITE_URL` matches your actual domain | |
1152 | +- Verify HTTPS in production | |
1153 | + | |
1154 | +**"Unauthorized" on git hooks:** | |
1155 | +- Check `INTERNAL_HOOK_SECRET` is set | |
1156 | +- Verify hooks have correct secret in curl headers | |
1157 | +- Re-run `git init` on repositories to reinstall hooks | |
1158 | + | |
1159 | +**Database connection errors:** | |
1160 | +- Verify `DATABASE_URL` format | |
1161 | +- Check database server is running | |
1162 | +- Ensure user has correct permissions | |
1163 | + | |
1164 | +**Storage upload fails:** | |
1165 | +- Test connection in `/admin/storage` | |
1166 | +- Verify S3 credentials and bucket permissions | |
1167 | +- Check firewall rules for external storage | |
1168 | + | |
1169 | +### Debug Mode | |
1170 | + | |
1171 | +```env | |
1172 | +# Enable detailed logging | |
1173 | +LOG_LEVEL=debug | |
1174 | +NODE_ENV=development | |
1175 | + | |
1176 | +# View logs | |
1177 | +tail -f data/logs/opencodehub.log | |
1178 | +``` | |
1179 | + | |
1180 | +### Health Check | |
1181 | + | |
1182 | +```bash | |
1183 | +curl http://localhost:3000/api/health | |
1184 | + | |
1185 | +# Response: | |
1186 | +{ | |
1187 | + "status": "ok", | |
1188 | + "checks": { | |
1189 | + "database": "ok", | |
1190 | + "storage": "ok", | |
1191 | + "redis": "ok" // if configured | |
1192 | + }, | |
1193 | + "uptime": 123456, | |
1194 | + "version": "1.0.0" | |
1195 | +} | |
1196 | +``` | |
1197 | + | |
1198 | +--- | |
1199 | + | |
1200 | +## π€ Contributing | |
1201 | + | |
1202 | +We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. | |
1203 | + | |
1204 | +### Development Setup | |
1205 | + | |
1206 | +```bash | |
1207 | +# Fork and clone | |
1208 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
1209 | +cd OpencodeHub | |
1210 | + | |
1211 | +# Install dependencies | |
1212 | +bun install | |
1213 | + | |
1214 | +# Run in development | |
1215 | +bun run dev | |
1216 | + | |
1217 | +# Run tests | |
1218 | +bun test | |
1219 | + | |
1220 | +# Lint | |
1221 | +bun run lint | |
1222 | +``` | |
1223 | + | |
1224 | +### Submitting Changes | |
1225 | + | |
1226 | +1. Create a feature branch (`git checkout -b feature/amazing-feature`) | |
1227 | +2. Make your changes | |
1228 | +3. Add tests | |
1229 | +4. Run `bun test` and `bun run lint` | |
1230 | +5. Commit (`git commit -m 'Add amazing feature'`) | |
1231 | +6. Push (`git push origin feature/amazing-feature`) | |
1232 | +7. Open a Pull Request | |
1233 | + | |
1234 | +--- | |
1235 | + | |
1236 | +## π License | |
1237 | + | |
1238 | +MIT License - see [LICENSE](LICENSE) for details. | |
1239 | + | |
1240 | +--- | |
1241 | + | |
1242 | +## π Acknowledgments | |
1243 | + | |
1244 | +- [Astro](https://astro.build/) - Web framework | |
1245 | +- [shadcn/ui](https://ui.shadcn.com/) - UI components | |
1246 | +- [Drizzle ORM](https://orm.drizzle.team/) - Database toolkit | |
1247 | +- [simple-git](https://github.com/steveukx/git-js) - Git operations | |
1248 | +- [Monaco Editor](https://microsoft.github.io/monaco-editor/) - Code editor | |
1249 | +- [Zod](https://zod.dev/) - Schema validation | |
1250 | + | |
1251 | +--- | |
1252 | + | |
1253 | +<p align="center"> | |
1254 | + <strong>Made with β€οΈ for the open source community</strong> | |
1255 | + <br> | |
1256 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/issues">Report Bug</a> β’ | |
1257 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/issues">Request Feature</a> | |
1258 | +</p> | |
0 | 1259 | |
βΌVercel Deploy.md
+794-0
| @@ -0,0 +1,794 @@ | ||
1 | +# Deploy OpenCodeHub to Vercel | |
2 | + | |
3 | +> Complete guide to deploying OpenCodeHub on Vercel with Turso, Google Drive, and Redis | |
4 | + | |
5 | +This guide walks you through deploying OpenCodeHub to Vercel using: | |
6 | +- **Turso** - Serverless SQLite database (edge-compatible) | |
7 | +- **Google Drive API** - File storage for repositories | |
8 | +- **Upstash Redis** - Serverless Redis for sessions and caching | |
9 | + | |
10 | +**Estimated time:** 30-45 minutes | |
11 | +**Cost:** Free tier available for all services | |
12 | + | |
13 | +--- | |
14 | + | |
15 | +## π Table of Contents | |
16 | + | |
17 | +1. [Prerequisites](#prerequisites) | |
18 | +2. [Setup Turso Database](#setup-turso-database) | |
19 | +3. [Setup Google Drive Storage](#setup-google-drive-storage) | |
20 | +4. [Setup Upstash Redis](#setup-upstash-redis) | |
21 | +5. [Configure Project](#configure-project) | |
22 | +6. [Deploy to Vercel](#deploy-to-vercel) | |
23 | +7. [Post-Deployment](#post-deployment) | |
24 | +8. [Troubleshooting](#troubleshooting) | |
25 | + | |
26 | +--- | |
27 | + | |
28 | +## Prerequisites | |
29 | + | |
30 | +Before starting, ensure you have: | |
31 | + | |
32 | +- [x] GitHub account | |
33 | +- [x] Vercel account ([vercel.com](https://vercel.com)) | |
34 | +- [x] Turso account ([turso.tech](https://turso.tech)) | |
35 | +- [x] Google Cloud account ([console.cloud.google.com](https://console.cloud.google.com)) | |
36 | +- [x] Upstash account ([upstash.com](https://upstash.com)) | |
37 | +- [x] OpenCodeHub repository forked/cloned | |
38 | + | |
39 | +--- | |
40 | + | |
41 | +## 1. Setup Turso Database | |
42 | + | |
43 | +Turso provides edge-compatible, serverless SQLite databases perfect for Vercel. | |
44 | + | |
45 | +### 1.1. Install Turso CLI | |
46 | + | |
47 | +```bash | |
48 | +# macOS/Linux | |
49 | +curl -sSfL https://get.tur.so/install.sh | bash | |
50 | + | |
51 | +# Windows (PowerShell) | |
52 | +irm get.tur.so/install.ps1 | iex | |
53 | + | |
54 | +# Verify installation | |
55 | +turso --version | |
56 | +``` | |
57 | + | |
58 | +### 1.2. Login to Turso | |
59 | + | |
60 | +```bash | |
61 | +turso auth login | |
62 | +``` | |
63 | + | |
64 | +This opens your browser to authenticate. | |
65 | + | |
66 | +### 1.3. Create Database | |
67 | + | |
68 | +```bash | |
69 | +# Create database | |
70 | +turso db create opencodehub | |
71 | + | |
72 | +# Create auth token | |
73 | +turso db tokens create opencodehub | |
74 | + | |
75 | +# Get database URL | |
76 | +turso db show opencodehub --url | |
77 | +``` | |
78 | + | |
79 | +**Save these values:** | |
80 | +```env | |
81 | +TURSO_DATABASE_URL=libsql://opencodehub-[your-org].turso.io | |
82 | +TURSO_AUTH_TOKEN=eyJhbGciOiJ... | |
83 | +``` | |
84 | + | |
85 | +### 1.4. Initialize Database Schema | |
86 | + | |
87 | +```bash | |
88 | +# Download schema | |
89 | +curl -O https://raw.githubusercontent.com/swadhinbiswas/OpencodeHub/main/drizzle/schema.sql | |
90 | + | |
91 | +# Apply schema | |
92 | +turso db shell opencodehub < schema.sql | |
93 | + | |
94 | +# Verify tables | |
95 | +turso db shell opencodehub "SELECT name FROM sqlite_master WHERE type='table';" | |
96 | +``` | |
97 | + | |
98 | +**Expected output:** | |
99 | +``` | |
100 | +users | |
101 | +repositories | |
102 | +pull_requests | |
103 | +pr_stacks | |
104 | +merge_queue_items | |
105 | +ai_reviews | |
106 | +... | |
107 | +``` | |
108 | + | |
109 | +--- | |
110 | + | |
111 | +## 2. Setup Google Drive Storage | |
112 | + | |
113 | +Google Drive API will store Git repositories and LFS files. | |
114 | + | |
115 | +### 2.1. Create Google Cloud Project | |
116 | + | |
117 | +1. Go to [Google Cloud Console](https://console.cloud.google.com) | |
118 | +2. Click **Select a project** β **New Project** | |
119 | +3. Name: `opencodehub-storage` | |
120 | +4. Click **Create** | |
121 | + | |
122 | +### 2.2. Enable Google Drive API | |
123 | + | |
124 | +1. In the Cloud Console, go to **APIs & Services** β **Enable APIs** | |
125 | +2. Search for "Google Drive API" | |
126 | +3. Click **Enable** | |
127 | + | |
128 | +### 2.3. Create Service Account | |
129 | + | |
130 | +1. Go to **APIs & Services** β **Credentials** | |
131 | +2. Click **Create Credentials** β **Service Account** | |
132 | +3. Name: `opencodehub-storage-sa` | |
133 | +4. Click **Create and Continue** | |
134 | +5. Grant role: **Editor** | |
135 | +6. Click **Done** | |
136 | + | |
137 | +### 2.4. Generate Service Account Key | |
138 | + | |
139 | +1. Click on the service account you just created | |
140 | +2. Go to **Keys** tab | |
141 | +3. Click **Add Key** β **Create new key** | |
142 | +4. Choose **JSON** | |
143 | +5. Click **Create** (downloads `opencodehub-storage-xxxxx.json`) | |
144 | + | |
145 | +### 2.5. Create Google Drive Folder | |
146 | + | |
147 | +1. Go to [Google Drive](https://drive.google.com) | |
148 | +2. Create a new folder: `OpenCodeHub-Repositories` | |
149 | +3. Right-click β **Share** | |
150 | +4. Add the service account email: `opencodehub-storage-sa@opencodehub-storage.iam.gserviceaccount.com` | |
151 | +5. Grant **Editor** access | |
152 | +6. Copy the **Folder ID** from the URL: | |
153 | + ``` | |
154 | + https://drive.google.com/drive/folders/1a2B3c4D5e6F7g8H9i0J | |
155 | + ^^^^^^^^^^^^^^^^^^^^ | |
156 | + This is your folder ID | |
157 | + ``` | |
158 | + | |
159 | +### 2.6. Prepare Service Account Credentials | |
160 | + | |
161 | +Open the downloaded JSON file and extract these values: | |
162 | + | |
163 | +```json | |
164 | +{ | |
165 | + "type": "service_account", | |
166 | + "project_id": "opencodehub-storage", | |
167 | + "private_key_id": "abc123...", | |
168 | + "private_key": "-----BEGIN PRIVATE KEY-----\n...", | |
169 | + "client_email": "opencodehub-storage-sa@...", | |
170 | + "client_id": "123456789...", | |
171 | + ... | |
172 | +} | |
173 | +``` | |
174 | + | |
175 | +**Save for later:** | |
176 | +```env | |
177 | +GOOGLE_DRIVE_FOLDER_ID=1a2B3c4D5e6F7g8H9i0J | |
178 | +GOOGLE_SERVICE_ACCOUNT_EMAIL=opencodehub-storage-sa@opencodehub-storage.iam.gserviceaccount.com | |
179 | +GOOGLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\n... | |
180 | +GOOGLE_CLIENT_ID=123456789... | |
181 | +``` | |
182 | + | |
183 | +--- | |
184 | + | |
185 | +## 3. Setup Upstash Redis | |
186 | + | |
187 | +Upstash provides serverless Redis, perfect for Vercel edge functions. | |
188 | + | |
189 | +### 3.1. Create Redis Database | |
190 | + | |
191 | +1. Go to [Upstash Console](https://console.upstash.com) | |
192 | +2. Click **Create Database** | |
193 | +3. **Name:** `opencodehub-sessions` | |
194 | +4. **Region:** Choose closest to your users (e.g., `us-east-1`) | |
195 | +5. **Type:** Regional (free tier) | |
196 | +6. Click **Create** | |
197 | + | |
198 | +### 3.2. Get Connection Details | |
199 | + | |
200 | +After creation, copy these values: | |
201 | + | |
202 | +**REST API:** | |
203 | +```env | |
204 | +UPSTASH_REDIS_REST_URL=https://us1-prepared-moose-12345.upstash.io | |
205 | +UPSTASH_REDIS_REST_TOKEN=AaBbCc...== | |
206 | +``` | |
207 | + | |
208 | +**Redis URL (optional):** | |
209 | +```env | |
210 | +REDIS_URL=redis://default:[email protected]:port | |
211 | +``` | |
212 | + | |
213 | +### 3.3. Configure Redis for Sessions | |
214 | + | |
215 | +Upstash Redis will handle: | |
216 | +- User sessions | |
217 | +- Rate limiting | |
218 | +- Temporary locks | |
219 | +- Cache | |
220 | + | |
221 | +No additional setup needed! | |
222 | + | |
223 | +--- | |
224 | + | |
225 | +## 4. Configure Project | |
226 | + | |
227 | +### 4.1. Update Database Configuration | |
228 | + | |
229 | +Create/update `src/db/turso.ts`: | |
230 | + | |
231 | +```typescript | |
232 | +import { drizzle } from 'drizzle-orm/libsql'; | |
233 | +import { createClient } from '@libsql/client'; | |
234 | + | |
235 | +const turso = createClient({ | |
236 | + url: process.env.TURSO_DATABASE_URL!, | |
237 | + authToken: process.env.TURSO_AUTH_TOKEN!, | |
238 | +}); | |
239 | + | |
240 | +export const db = drizzle(turso); | |
241 | +``` | |
242 | + | |
243 | +### 4.2. Update Storage Configuration | |
244 | + | |
245 | +Create/update `src/lib/storage/google-drive.ts`: | |
246 | + | |
247 | +```typescript | |
248 | +import { google } from 'googleapis'; | |
249 | + | |
250 | +const auth = new google.auth.GoogleAuth({ | |
251 | + credentials: { | |
252 | + client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL, | |
253 | + private_key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n'), | |
254 | + }, | |
255 | + scopes: ['https://www.googleapis.com/auth/drive.file'], | |
256 | +}); | |
257 | + | |
258 | +const drive = google.drive({ version: 'v3', auth }); | |
259 | +const folderId = process.env.GOOGLE_DRIVE_FOLDER_ID; | |
260 | + | |
261 | +export async function uploadFile(fileName: string, content: Buffer) { | |
262 | + const response = await drive.files.create({ | |
263 | + requestBody: { | |
264 | + name: fileName, | |
265 | + parents: [folderId!], | |
266 | + }, | |
267 | + media: { | |
268 | + body: content, | |
269 | + }, | |
270 | + }); | |
271 | + | |
272 | + return response.data.id; | |
273 | +} | |
274 | + | |
275 | +export async function downloadFile(fileId: string) { | |
276 | + const response = await drive.files.get( | |
277 | + { fileId, alt: 'media' }, | |
278 | + { responseType: 'arraybuffer' } | |
279 | + ); | |
280 | + | |
281 | + return Buffer.from(response.data as ArrayBuffer); | |
282 | +} | |
283 | + | |
284 | +export async function deleteFile(fileId: string) { | |
285 | + await drive.files.delete({ fileId }); | |
286 | +} | |
287 | +``` | |
288 | + | |
289 | +### 4.3. Update Redis Configuration | |
290 | + | |
291 | +Create/update `src/lib/redis.ts`: | |
292 | + | |
293 | +```typescript | |
294 | +import { Redis } from '@upstash/redis'; | |
295 | + | |
296 | +export const redis = new Redis({ | |
297 | + url: process.env.UPSTASH_REDIS_REST_URL!, | |
298 | + token: process.env.UPSTASH_REDIS_REST_TOKEN!, | |
299 | +}); | |
300 | + | |
301 | +// Session management | |
302 | +export async function setSession(sessionId: string, data: any, ttl = 7 * 24 * 60 * 60) { | |
303 | + await redis.setex(sessionId, ttl, JSON.stringify(data)); | |
304 | +} | |
305 | + | |
306 | +export async function getSession(sessionId: string) { | |
307 | + const data = await redis.get(sessionId); | |
308 | + return data ? JSON.parse(data as string) : null; | |
309 | +} | |
310 | + | |
311 | +export async function deleteSession(sessionId: string) { | |
312 | + await redis.del(sessionId); | |
313 | +} | |
314 | +``` | |
315 | + | |
316 | +### 4.4. Install Dependencies | |
317 | + | |
318 | +```bash | |
319 | +# Add Turso client | |
320 | +bun add @libsql/client drizzle-orm | |
321 | + | |
322 | +# Add Google Drive API | |
323 | +bun add googleapis | |
324 | + | |
325 | +# Add Upstash Redis | |
326 | +bun add @upstash/redis | |
327 | +``` | |
328 | + | |
329 | +### 4.5. Update Environment Variables Template | |
330 | + | |
331 | +Create `.env.example`: | |
332 | + | |
333 | +```env | |
334 | +# === Turso Database === | |
335 | +TURSO_DATABASE_URL=libsql://your-db.turso.io | |
336 | +TURSO_AUTH_TOKEN=your-token | |
337 | + | |
338 | +# === Google Drive Storage === | |
339 | +GOOGLE_DRIVE_FOLDER_ID=your-folder-id | |
340 | +GOOGLE_SERVICE_ACCOUNT_EMAIL=your-sa@project.iam.gserviceaccount.com | |
341 | +GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" | |
342 | +GOOGLE_CLIENT_ID=your-client-id | |
343 | + | |
344 | +# === Upstash Redis === | |
345 | +UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io | |
346 | +UPSTASH_REDIS_REST_TOKEN=your-token | |
347 | + | |
348 | +# === Application === | |
349 | +SITE_URL=https://your-app.vercel.app | |
350 | +JWT_SECRET=generate-a-strong-secret | |
351 | +SESSION_SECRET=generate-another-strong-secret | |
352 | +INTERNAL_HOOK_SECRET=generate-hook-secret | |
353 | + | |
354 | +# === Optional: AI Review === | |
355 | +AI_PROVIDER=openai | |
356 | +OPENAI_API_KEY=sk-... | |
357 | +``` | |
358 | + | |
359 | +### 4.6. Generate Secrets | |
360 | + | |
361 | +```bash | |
362 | +# Generate secrets | |
363 | +openssl rand -base64 32 # JWT_SECRET | |
364 | +openssl rand -base64 32 # SESSION_SECRET | |
365 | +openssl rand -base64 32 # INTERNAL_HOOK_SECRET | |
366 | +``` | |
367 | + | |
368 | +--- | |
369 | + | |
370 | +## 5. Deploy to Vercel | |
371 | + | |
372 | +### 5.1. Install Vercel CLI (Optional) | |
373 | + | |
374 | +```bash | |
375 | +npm install -g vercel | |
376 | +vercel login | |
377 | +``` | |
378 | + | |
379 | +### 5.2. Connect Repository to Vercel | |
380 | + | |
381 | +**Option A: Via Vercel Dashboard** | |
382 | + | |
383 | +1. Go to [Vercel Dashboard](https://vercel.com/dashboard) | |
384 | +2. Click **Add New** β **Project** | |
385 | +3. Import your GitHub repository | |
386 | +4. Select `OpencodeHub` | |
387 | + | |
388 | +**Option B: Via CLI** | |
389 | + | |
390 | +```bash | |
391 | +cd opencodehub | |
392 | +vercel | |
393 | +``` | |
394 | + | |
395 | +### 5.3. Configure Environment Variables | |
396 | + | |
397 | +In Vercel Dashboard: | |
398 | + | |
399 | +1. Go to **Project Settings** β **Environment Variables** | |
400 | +2. Add all variables from step 4.5: | |
401 | + | |
402 | +**Turso:** | |
403 | +- `TURSO_DATABASE_URL` = `libsql://opencodehub-xxxx.turso.io` | |
404 | +- `TURSO_AUTH_TOKEN` = `eyJhbG...` | |
405 | + | |
406 | +**Google Drive:** | |
407 | +- `GOOGLE_DRIVE_FOLDER_ID` = `1a2B3c4D5e6F7g8H9i0J` | |
408 | +- `GOOGLE_SERVICE_ACCOUNT_EMAIL` = `opencodehub-storage-sa@...` | |
409 | +- `GOOGLE_PRIVATE_KEY` = Paste entire private key (including `\n`) | |
410 | +- `GOOGLE_CLIENT_ID` = `123456789...` | |
411 | + | |
412 | +**Upstash Redis:** | |
413 | +- `UPSTASH_REDIS_REST_URL` = `https://...upstash.io` | |
414 | +- `UPSTASH_REDIS_REST_TOKEN` = `AaBbCc...` | |
415 | + | |
416 | +**Application:** | |
417 | +- `SITE_URL` = `https://your-project.vercel.app` (update later) | |
418 | +- `JWT_SECRET` = Generated secret | |
419 | +- `SESSION_SECRET` = Generated secret | |
420 | +- `INTERNAL_HOOK_SECRET` = Generated secret | |
421 | + | |
422 | +**AI (Optional):** | |
423 | +- `AI_PROVIDER` = `openai` | |
424 | +- `OPENAI_API_KEY` = `sk-...` | |
425 | + | |
426 | +### 5.4. Configure Build Settings | |
427 | + | |
428 | +In **Project Settings** β **Build & Development Settings**: | |
429 | + | |
430 | +- **Framework Preset:** Astro | |
431 | +- **Build Command:** `bun run build` | |
432 | +- **Output Directory:** `dist` | |
433 | +- **Install Command:** `bun install` | |
434 | + | |
435 | +### 5.5. Deploy! | |
436 | + | |
437 | +Click **Deploy** or run: | |
438 | + | |
439 | +```bash | |
440 | +vercel --prod | |
441 | +``` | |
442 | + | |
443 | +**Deployment will:** | |
444 | +1. Install dependencies | |
445 | +2. Build the project | |
446 | +3. Deploy to Vercel Edge Network | |
447 | +4. Provide deployment URL | |
448 | + | |
449 | +--- | |
450 | + | |
451 | +## 6. Post-Deployment | |
452 | + | |
453 | +### 6.1. Update SITE_URL | |
454 | + | |
455 | +1. Copy your Vercel deployment URL: `https://opencodehub-abc123.vercel.app` | |
456 | +2. Update environment variable: | |
457 | + - Go to **Settings** β **Environment Variables** | |
458 | + - Edit `SITE_URL` = your actual URL | |
459 | +3. **Redeploy** (Vercel Dashboard β Deployments β Redeploy) | |
460 | + | |
461 | +### 6.2. Create Admin User | |
462 | + | |
463 | +**Option A: Via Turso CLI** | |
464 | + | |
465 | +```bash | |
466 | +turso db shell opencodehub | |
467 | + | |
468 | +-- In the Turso shell: | |
469 | +INSERT INTO users (id, email, password_hash, username, role, created_at) | |
470 | +VALUES ( | |
471 | + 'admin-user-id', | |
472 | + '[email protected]', | |
473 | + '$2b$10$...', -- bcrypt hash of your password | |
474 | + 'admin', | |
475 | + 'admin', | |
476 | + current_timestamp | |
477 | +); | |
478 | +``` | |
479 | + | |
480 | +**Generate bcrypt hash:** | |
481 | +```javascript | |
482 | +// Run in Node.js | |
483 | +const bcrypt = require('bcrypt'); | |
484 | +const hash = await bcrypt.hash('your-password', 10); | |
485 | +console.log(hash); | |
486 | +``` | |
487 | + | |
488 | +**Option B: Via Deployment Script** | |
489 | + | |
490 | +Create `scripts/create-admin-vercel.ts`: | |
491 | + | |
492 | +```typescript | |
493 | +import { db } from '../src/db/turso'; | |
494 | +import { users } from '../src/db/schema'; | |
495 | +import bcrypt from 'bcrypt'; | |
496 | + | |
497 | +const email = process.env.ADMIN_EMAIL || '[email protected]'; | |
498 | +const password = process.env.ADMIN_PASSWORD || 'changeme'; | |
499 | + | |
500 | +const hash = await bcrypt.hash(password, 10); | |
501 | + | |
502 | +await db.insert(users).values({ | |
503 | + id: crypto.randomUUID(), | |
504 | + email, | |
505 | + username: 'admin', | |
506 | + password_hash: hash, | |
507 | + role: 'admin', | |
508 | + createdAt: new Date(), | |
509 | +}); | |
510 | + | |
511 | +console.log(`β
Admin user created: ${email}`); | |
512 | +``` | |
513 | + | |
514 | +Run once: | |
515 | +```bash | |
516 | +[email protected] ADMIN_PASSWORD=strong123 bun scripts/create-admin-vercel.ts | |
517 | +``` | |
518 | + | |
519 | +### 6.3. Configure Custom Domain (Optional) | |
520 | + | |
521 | +1. Go to **Settings** β **Domains** | |
522 | +2. Add your custom domain: `git.yourcompany.com` | |
523 | +3. Follow DNS configuration instructions | |
524 | +4. Update `SITE_URL` to your custom domain | |
525 | + | |
526 | +### 6.4. Setup Monitoring | |
527 | + | |
528 | +**Vercel Analytics:** | |
529 | +1. Go to **Analytics** tab | |
530 | +2. Enable **Web Analytics** | |
531 | +3. Enable **Speed Insights** | |
532 | + | |
533 | +**Sentry (Optional):** | |
534 | +```bash | |
535 | +bun add @sentry/astro | |
536 | +``` | |
537 | + | |
538 | +Add to `.env`: | |
539 | +```env | |
540 | +SENTRY_DSN=https://[email protected]/... | |
541 | +``` | |
542 | + | |
543 | +--- | |
544 | + | |
545 | +## 7. Verification | |
546 | + | |
547 | +### 7.1. Test Database Connection | |
548 | + | |
549 | +Visit: `https://your-app.vercel.app/api/health` | |
550 | + | |
551 | +Should return: | |
552 | +```json | |
553 | +{ | |
554 | + "status": "ok", | |
555 | + "database": "connected", | |
556 | + "redis": "connected", | |
557 | + "storage": "configured" | |
558 | +} | |
559 | +``` | |
560 | + | |
561 | +### 7.2. Test Storage | |
562 | + | |
563 | +1. Login as admin | |
564 | +2. Create a repository | |
565 | +3. Push a commit | |
566 | +4. Verify file appears in Google Drive folder | |
567 | + | |
568 | +### 7.3. Test Redis | |
569 | + | |
570 | +1. Login | |
571 | +2. Check session persists | |
572 | +3. Logout | |
573 | +4. Session cleared | |
574 | + | |
575 | +### 7.4. Load Test | |
576 | + | |
577 | +```bash | |
578 | +# Install hey | |
579 | +go install github.com/rakyll/hey@latest | |
580 | + | |
581 | +# Test | |
582 | +hey -n 1000 -c 10 https://your-app.vercel.app | |
583 | +``` | |
584 | + | |
585 | +Expected: >90% success rate | |
586 | + | |
587 | +--- | |
588 | + | |
589 | +## 8. Troubleshooting | |
590 | + | |
591 | +### Database Connection Fails | |
592 | + | |
593 | +**Error:** `Failed to connect to Turso` | |
594 | + | |
595 | +**Solution:** | |
596 | +```bash | |
597 | +# Verify token is valid | |
598 | +turso db tokens validate opencodehub YOUR_TOKEN | |
599 | + | |
600 | +# Regenerate if needed | |
601 | +turso db tokens create opencodehub | |
602 | +``` | |
603 | + | |
604 | +Update environment variable and redeploy. | |
605 | + | |
606 | +### Google Drive Upload Fails | |
607 | + | |
608 | +**Error:** `Insufficient permissions` | |
609 | + | |
610 | +**Solution:** | |
611 | +1. Verify service account has Editor access to folder | |
612 | +2. Check private key doesn't have extra spaces/newlines | |
613 | +3. Test locally: | |
614 | + ```bash | |
615 | + bun test src/lib/storage/google-drive.test.ts | |
616 | + ``` | |
617 | + | |
618 | +### Redis Connection Timeout | |
619 | + | |
620 | +**Error:** `ETIMEDOUT connecting to Redis` | |
621 | + | |
622 | +**Solution:** | |
623 | +1. Verify Upstash Redis region matches Vercel deployment region | |
624 | +2. Check REST URL and token are correct | |
625 | +3. Test connection: | |
626 | + ```bash | |
627 | + curl https://YOUR_REDIS_URL/get/test \ | |
628 | + -H "Authorization: Bearer YOUR_TOKEN" | |
629 | + ``` | |
630 | + | |
631 | +### Build Fails on Vercel | |
632 | + | |
633 | +**Error:** `Command "build" exited with 1` | |
634 | + | |
635 | +**Solution:** | |
636 | +```bash | |
637 | +# Test build locally | |
638 | +bun run build | |
639 | + | |
640 | +# Check for TypeScript errors | |
641 | +bun run typecheck | |
642 | + | |
643 | +# Check environment variables are set | |
644 | +vercel env ls | |
645 | +``` | |
646 | + | |
647 | +### Out of Memory | |
648 | + | |
649 | +**Error:** `JavaScript heap out of memory` | |
650 | + | |
651 | +** Solution:** | |
652 | +Upgrade Vercel plan or optimize build: | |
653 | + | |
654 | +```json | |
655 | +// package.json | |
656 | +{ | |
657 | + "scripts": { | |
658 | + "build": "NODE_OPTIONS=--max_old_space_size=4096 astro build" | |
659 | + } | |
660 | +} | |
661 | +``` | |
662 | + | |
663 | +--- | |
664 | + | |
665 | +## Cost Estimation | |
666 | + | |
667 | +### Free Tier Limits | |
668 | + | |
669 | +**Turso:** | |
670 | +- Database: Free up to 8 GB | |
671 | +- Rows: 500M | |
672 | +- Locations: 1 primary + 2 replicas | |
673 | + | |
674 | +**Upstash Redis:** | |
675 | +- Commands: 10,000/day | |
676 | +- Storage: 256 MB | |
677 | +- Bandwidth: 256 MB/month | |
678 | + | |
679 | +**Google Drive:** | |
680 | +- Storage: 15 GB free | |
681 | +- API calls: Unlimited | |
682 | + | |
683 | +**Vercel:** | |
684 | +- Bandwidth: 100 GB/month | |
685 | +- Deployments: Unlimited | |
686 | +- Edge Functions: 100 GB-hrs | |
687 | + | |
688 | +### Estimated Monthly Cost | |
689 | + | |
690 | +**Low traffic (< 1,000 users):** | |
691 | +- All free tier β
| |
692 | + | |
693 | +**Medium traffic (1,000-10,000 users):** | |
694 | +- Turso: $0 (within limits) | |
695 | +- Upstash: $10/month (higher tier) | |
696 | +- Google Drive: $2/month (Google One 100GB) | |
697 | +- Vercel: $20/month (Pro plan) | |
698 | +**Total: ~$32/month** | |
699 | + | |
700 | +**High traffic (>10,000 users):** | |
701 | +- Turso: $29/month (Pro) | |
702 | +- Upstash: $50/month (Pro) | |
703 | +- Google Drive: $10/month (Google Workspace) | |
704 | +- Vercel: $20/month (Pro) | |
705 | +**Total: ~$109/month** | |
706 | + | |
707 | +--- | |
708 | + | |
709 | +## Performance Optimization | |
710 | + | |
711 | +### Edge Caching | |
712 | + | |
713 | +Add to `vercel.json`: | |
714 | + | |
715 | +```json | |
716 | +{ | |
717 | + "headers": [ | |
718 | + { | |
719 | + "source": "/assets/(.*)", | |
720 | + "headers": [ | |
721 | + { | |
722 | + "key": "Cache-Control", | |
723 | + "value": "public, max-age=31536000, immutable" | |
724 | + } | |
725 | + ] | |
726 | + } | |
727 | + ] | |
728 | +} | |
729 | +``` | |
730 | + | |
731 | +### Database Connection Pooling | |
732 | + | |
733 | +Turso handles this automatically with libSQL. | |
734 | + | |
735 | +### Redis Caching Strategy | |
736 | + | |
737 | +```typescript | |
738 | +// Cache expensive queries | |
739 | +async function getRepository(id: string) { | |
740 | + const cached = await redis.get(`repo:${id}`); | |
741 | + if (cached) return JSON.parse(cached as string); | |
742 | + | |
743 | + const repo = await db.query.repositories.findFirst({ where: eq(repositories.id, id) }); | |
744 | + await redis.setex(`repo:${id}`, 300, JSON.stringify(repo)); // Cache 5 min | |
745 | + | |
746 | + return repo; | |
747 | +} | |
748 | +``` | |
749 | + | |
750 | +--- | |
751 | + | |
752 | +## Security Checklist | |
753 | + | |
754 | +- [ ] All secrets in environment variables (not committed to Git) | |
755 | +- [ ] HTTPS enabled (automatic on Vercel) | |
756 | +- [ ] Database access restricted to Vercel IPs | |
757 | +- [ ] Google Drive folder shared only with service account | |
758 | +- [ ] Redis requires authentication | |
759 | +- [ ] Rate limiting enabled | |
760 | +- [ ] CORS configured properly | |
761 | +- [ ] CSP headers set | |
762 | + | |
763 | +--- | |
764 | + | |
765 | +## Next Steps | |
766 | + | |
767 | +1. **Setup CI/CD**: Automatic deployments on push to main | |
768 | +2. **Add Monitoring**: Set up Sentry for error tracking | |
769 | +3. **Configure Backups**: Turso auto-backups + manual exports | |
770 | +4. **Scale Redis**: Upgrade to Global for multi-region | |
771 | +5. **Add CDN**: Use Vercel Edge Network or Cloudflare | |
772 | + | |
773 | +--- | |
774 | + | |
775 | +## Additional Resources | |
776 | + | |
777 | +- [Turso Documentation](https://docs.turso.tech) | |
778 | +- [Google Drive API Reference](https://developers.google.com/drive/api/v3/reference) | |
779 | +- [Upstash Redis Docs](https://docs.upstash.com/redis) | |
780 | +- [Vercel Deployment Docs](https://vercel.com/docs) | |
781 | +- [OpenCodeHub GitHub](https://github.com/swadhinbiswas/OpencodeHub) | |
782 | + | |
783 | +--- | |
784 | + | |
785 | +## Support | |
786 | + | |
787 | +**Need help?** | |
788 | +- π¬ [Discord Community](https://discord.gg/opencodehub) | |
789 | +- π [GitHub Issues](https://github.com/swadhinbiswas/OpencodeHub/issues) | |
790 | +- π§ [Email Support](mailto:[email protected]) | |
791 | + | |
792 | +--- | |
793 | + | |
794 | +**Congratulations!** π Your OpenCodeHub instance is now running on Vercel with world-class serverless infrastructure! | |
0 | 795 | |
βΌbun.lock
+293-9
| @@ -10,6 +10,7 @@ | ||
10 | 10 | "@astrojs/tailwind": "^5.1.0", |
11 | 11 | "@aws-sdk/client-s3": "^3.948.0", |
12 | 12 | "@aws-sdk/s3-request-presigner": "^3.948.0", |
13 | + "@libsql/client": "^0.15.15", | |
13 | 14 | "@monaco-editor/react": "^4.6.0", |
14 | 15 | "@opentelemetry/auto-instrumentations-node": "^0.67.2", |
15 | 16 | "@opentelemetry/exporter-trace-otlp-http": "^0.208.0", |
| @@ -39,6 +40,8 @@ | ||
39 | 40 | "@radix-ui/react-toggle": "^1.1.0", |
40 | 41 | "@radix-ui/react-toggle-group": "^1.1.0", |
41 | 42 | "@radix-ui/react-tooltip": "^1.1.2", |
43 | + "@types/nodemailer": "^7.0.4", | |
44 | + "@upstash/redis": "^1.36.0", | |
42 | 45 | "astro": "^4.11.0", |
43 | 46 | "bcryptjs": "^2.4.3", |
44 | 47 | "better-sqlite3": "^11.1.2", |
| @@ -46,10 +49,12 @@ | ||
46 | 49 | "class-variance-authority": "^0.7.0", |
47 | 50 | "clsx": "^2.1.1", |
48 | 51 | "cmdk": "^1.1.1", |
52 | + "cobe": "^0.6.5", | |
49 | 53 | "dockerode": "^4.0.2", |
50 | 54 | "drizzle-orm": "^0.45.1", |
51 | - "framer-motion": "^11.2.12", | |
55 | + "framer-motion": "^11.18.2", | |
52 | 56 | "github-markdown-css": "^5.8.1", |
57 | + "googleapis": "^169.0.0", | |
53 | 58 | "ioredis": "^5.8.2", |
54 | 59 | "isomorphic-git": "^1.25.10", |
55 | 60 | "jose": "^5.6.3", |
| @@ -60,6 +65,7 @@ | ||
60 | 65 | "mysql2": "^3.15.3", |
61 | 66 | "nanoid": "^5.0.7", |
62 | 67 | "nodegit": "^0.28.0-alpha.27", |
68 | + "nodemailer": "^7.0.12", | |
63 | 69 | "openpgp": "^6.3.0", |
64 | 70 | "otplib": "^12.0.1", |
65 | 71 | "pg": "^8.16.3", |
| @@ -76,7 +82,7 @@ | ||
76 | 82 | "remark-rehype": "^11.1.2", |
77 | 83 | "simple-git": "^3.25.0", |
78 | 84 | "ssh2": "^1.15.0", |
79 | - "tailwind-merge": "^2.3.0", | |
85 | + "tailwind-merge": "^2.6.0", | |
80 | 86 | "tailwindcss-animate": "^1.0.7", |
81 | 87 | "three": "^0.182.0", |
82 | 88 | "unified": "^11.0.5", |
| @@ -84,6 +90,7 @@ | ||
84 | 90 | "zustand": "^4.5.4", |
85 | 91 | }, |
86 | 92 | "devDependencies": { |
93 | + "@faker-js/faker": "^10.1.0", | |
87 | 94 | "@playwright/test": "^1.57.0", |
88 | 95 | "@tailwindcss/typography": "^0.5.19", |
89 | 96 | "@types/bcryptjs": "^2.4.6", |
| @@ -91,10 +98,11 @@ | ||
91 | 98 | "@types/dockerode": "^3.3.29", |
92 | 99 | "@types/js-yaml": "^4.0.9", |
93 | 100 | "@types/node": "^20.14.9", |
94 | - "@types/pg": "^8.11.6", | |
101 | + "@types/pg": "^8.16.0", | |
95 | 102 | "@types/react": "^18.3.3", |
96 | 103 | "@types/react-dom": "^18.3.0", |
97 | 104 | "@types/ssh2": "^1.15.0", |
105 | + "@types/three": "^0.182.0", | |
98 | 106 | "autoprefixer": "^10.4.19", |
99 | 107 | "drizzle-kit": "^0.31.8", |
100 | 108 | "pino-pretty": "^13.1.3", |
| @@ -140,6 +148,8 @@ | ||
140 | 148 | |
141 | 149 | "@aws-sdk/client-s3": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-node": "3.948.0", "@aws-sdk/middleware-bucket-endpoint": "3.936.0", "@aws-sdk/middleware-expect-continue": "3.936.0", "@aws-sdk/middleware-flexible-checksums": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-location-constraint": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-sdk-s3": "3.947.0", "@aws-sdk/middleware-ssec": "3.936.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/signature-v4-multi-region": "3.947.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-uvEjds8aYA9SzhBS8RKDtsDUhNV9VhqKiHTcmvhM7gJO92q0WTn8/QeFTdNyLc6RxpiDyz+uBxS7PcdNiZzqfA=="], |
142 | 150 | |
151 | + "@aws-sdk/client-sesv2": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/credential-provider-node": "3.958.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/signature-v4-multi-region": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-3x3n8IIxIMAkdpt9wy9zS7MO2lqTcJwQTdHMn6BlD7YUohb+r5Q4KCOEQ2uHWd4WIJv2tlbXnfypHaXReO/WXA=="], | |
152 | + | |
143 | 153 | "@aws-sdk/client-sso": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iWjchXy8bIAVBUsKnbfKYXRwhLgRg3EqCQ5FTr3JbR+QR75rZm4ZOYXlvHGztVTmtAZ+PQVA1Y4zO7v7N87C0A=="], |
144 | 154 | |
145 | 155 | "@aws-sdk/core": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], |
| @@ -258,6 +268,8 @@ | ||
258 | 268 | |
259 | 269 | "@balena/dockerignore": ["@balena/[email protected]", "", {}, "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="], |
260 | 270 | |
271 | + "@dimforge/rapier3d-compat": ["@dimforge/[email protected]", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], | |
272 | + | |
261 | 273 | "@drizzle-team/brocli": ["@drizzle-team/[email protected]", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], |
262 | 274 | |
263 | 275 | "@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], |
| @@ -318,6 +330,8 @@ | ||
318 | 330 | |
319 | 331 | "@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], |
320 | 332 | |
333 | + "@faker-js/faker": ["@faker-js/[email protected]", "", {}, "sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg=="], | |
334 | + | |
321 | 335 | "@floating-ui/core": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], |
322 | 336 | |
323 | 337 | "@floating-ui/dom": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], |
| @@ -392,6 +406,34 @@ | ||
392 | 406 | |
393 | 407 | "@kwsites/promise-deferred": ["@kwsites/[email protected]", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], |
394 | 408 | |
409 | + "@libsql/client": ["@libsql/[email protected]", "", { "dependencies": { "@libsql/core": "^0.15.14", "@libsql/hrana-client": "^0.7.0", "js-base64": "^3.7.5", "libsql": "^0.5.22", "promise-limit": "^2.7.0" } }, "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w=="], | |
410 | + | |
411 | + "@libsql/core": ["@libsql/[email protected]", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA=="], | |
412 | + | |
413 | + "@libsql/darwin-arm64": ["@libsql/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA=="], | |
414 | + | |
415 | + "@libsql/darwin-x64": ["@libsql/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA=="], | |
416 | + | |
417 | + "@libsql/hrana-client": ["@libsql/[email protected]", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.3.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw=="], | |
418 | + | |
419 | + "@libsql/isomorphic-fetch": ["@libsql/[email protected]", "", {}, "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw=="], | |
420 | + | |
421 | + "@libsql/isomorphic-ws": ["@libsql/[email protected]", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], | |
422 | + | |
423 | + "@libsql/linux-arm-gnueabihf": ["@libsql/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA=="], | |
424 | + | |
425 | + "@libsql/linux-arm-musleabihf": ["@libsql/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg=="], | |
426 | + | |
427 | + "@libsql/linux-arm64-gnu": ["@libsql/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA=="], | |
428 | + | |
429 | + "@libsql/linux-arm64-musl": ["@libsql/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw=="], | |
430 | + | |
431 | + "@libsql/linux-x64-gnu": ["@libsql/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew=="], | |
432 | + | |
433 | + "@libsql/linux-x64-musl": ["@libsql/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg=="], | |
434 | + | |
435 | + "@libsql/win32-x64-msvc": ["@libsql/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA=="], | |
436 | + | |
395 | 437 | "@mapbox/node-pre-gyp": ["@mapbox/[email protected]", "", { "dependencies": { "consola": "^3.2.3", "detect-libc": "^2.0.0", "https-proxy-agent": "^7.0.5", "node-fetch": "^2.6.7", "nopt": "^8.0.0", "semver": "^7.5.3", "tar": "^7.4.0" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg=="], |
396 | 438 | |
397 | 439 | "@monaco-editor/loader": ["@monaco-editor/[email protected]", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="], |
| @@ -410,6 +452,8 @@ | ||
410 | 452 | |
411 | 453 | "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], |
412 | 454 | |
455 | + "@neon-rs/load": ["@neon-rs/[email protected]", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], | |
456 | + | |
413 | 457 | "@nodelib/fs.scandir": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], |
414 | 458 | |
415 | 459 | "@nodelib/fs.stat": ["@nodelib/[email protected]", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], |
| @@ -896,7 +940,7 @@ | ||
896 | 940 | |
897 | 941 | "@turf/invariant": ["@turf/[email protected]", "", { "dependencies": { "@turf/helpers": "7.3.1", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-IdZJfDjIDCLH+Gu2yLFoSM7H23sdetIo5t4ET1/25X8gi3GE2XSqbZwaGjuZgNh02nisBewLqNiJs2bo+hrqZA=="], |
898 | 942 | |
899 | - "@tweenjs/tween.js": ["@tweenjs/[email protected]", "", {}, "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A=="], | |
943 | + "@tweenjs/tween.js": ["@tweenjs/[email protected]", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="], | |
900 | 944 | |
901 | 945 | "@types/aws-lambda": ["@types/[email protected]", "", {}, "sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg=="], |
902 | 946 | |
| @@ -950,6 +994,8 @@ | ||
950 | 994 | |
951 | 995 | "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg=="], |
952 | 996 | |
997 | + "@types/nodemailer": ["@types/[email protected]", "", { "dependencies": { "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow=="], | |
998 | + | |
953 | 999 | "@types/oracledb": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ=="], |
954 | 1000 | |
955 | 1001 | "@types/pg": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], |
| @@ -964,12 +1010,22 @@ | ||
964 | 1010 | |
965 | 1011 | "@types/ssh2": ["@types/[email protected]", "", { "dependencies": { "@types/node": "^18.11.18" } }, "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ=="], |
966 | 1012 | |
1013 | + "@types/stats.js": ["@types/[email protected]", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="], | |
1014 | + | |
967 | 1015 | "@types/tedious": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], |
968 | 1016 | |
1017 | + "@types/three": ["@types/[email protected]", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.22.0" } }, "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q=="], | |
1018 | + | |
969 | 1019 | "@types/unist": ["@types/[email protected]", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], |
970 | 1020 | |
1021 | + "@types/webxr": ["@types/[email protected]", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="], | |
1022 | + | |
1023 | + "@types/ws": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], | |
1024 | + | |
971 | 1025 | "@ungap/structured-clone": ["@ungap/[email protected]", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], |
972 | 1026 | |
1027 | + "@upstash/redis": ["@upstash/[email protected]", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-9zN2UV9QJGPnXfWU3yZBLVQaqqENDh7g+Y4J2vJuSxBCi9FQ0aUOtaXlzuFhnsiZvCqM+eS27ic+tgmkWUsfOg=="], | |
1028 | + | |
973 | 1029 | "@vitejs/plugin-react": ["@vitejs/[email protected]", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], |
974 | 1030 | |
975 | 1031 | "@vitest/expect": ["@vitest/[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.15", "@vitest/utils": "4.0.15", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w=="], |
| @@ -986,6 +1042,8 @@ | ||
986 | 1042 | |
987 | 1043 | "@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" } }, "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA=="], |
988 | 1044 | |
1045 | + "@webgpu/types": ["@webgpu/[email protected]", "", {}, "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA=="], | |
1046 | + | |
989 | 1047 | "abbrev": ["[email protected]", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], |
990 | 1048 | |
991 | 1049 | "abort-controller": ["[email protected]", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], |
| @@ -1088,6 +1146,8 @@ | ||
1088 | 1146 | |
1089 | 1147 | "buffer": ["[email protected]", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], |
1090 | 1148 | |
1149 | + "buffer-equal-constant-time": ["[email protected]", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], | |
1150 | + | |
1091 | 1151 | "buffer-from": ["[email protected]", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], |
1092 | 1152 | |
1093 | 1153 | "buildcheck": ["[email protected]", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="], |
| @@ -1152,6 +1212,8 @@ | ||
1152 | 1212 | |
1153 | 1213 | "cmdk": ["[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], |
1154 | 1214 | |
1215 | + "cobe": ["[email protected]", "", { "dependencies": { "phenomenon": "^1.6.0" } }, "sha512-MA8bu81EFY6JjQpj+FovEuhyJ25khx2Q7Lh+ot/UkCJe5yKyDgzdc6u2lGZIOmsZTXK6Itg1i4lQZIJZbPWnAg=="], | |
1216 | + | |
1155 | 1217 | "color": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], |
1156 | 1218 | |
1157 | 1219 | "color-convert": ["[email protected]", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], |
| @@ -1216,6 +1278,8 @@ | ||
1216 | 1278 | |
1217 | 1279 | "data-bind-mapper": ["[email protected]", "", { "dependencies": { "accessor-fn": "1" } }, "sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw=="], |
1218 | 1280 | |
1281 | + "data-uri-to-buffer": ["[email protected]", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], | |
1282 | + | |
1219 | 1283 | "dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], |
1220 | 1284 | |
1221 | 1285 | "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], |
| @@ -1238,7 +1302,7 @@ | ||
1238 | 1302 | |
1239 | 1303 | "destroy": ["[email protected]", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], |
1240 | 1304 | |
1241 | - "detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
1305 | + "detect-libc": ["[email protected]", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], | |
1242 | 1306 | |
1243 | 1307 | "detect-node-es": ["[email protected]", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], |
1244 | 1308 | |
| @@ -1272,6 +1336,8 @@ | ||
1272 | 1336 | |
1273 | 1337 | "eastasianwidth": ["[email protected]", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], |
1274 | 1338 | |
1339 | + "ecdsa-sig-formatter": ["[email protected]", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], | |
1340 | + | |
1275 | 1341 | "ee-first": ["[email protected]", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], |
1276 | 1342 | |
1277 | 1343 | "electron-to-chromium": ["[email protected]", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], |
| @@ -1348,6 +1414,10 @@ | ||
1348 | 1414 | |
1349 | 1415 | "fdir": ["[email protected]", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], |
1350 | 1416 | |
1417 | + "fetch-blob": ["[email protected]", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], | |
1418 | + | |
1419 | + "fflate": ["[email protected]", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], | |
1420 | + | |
1351 | 1421 | "file-uri-to-path": ["[email protected]", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], |
1352 | 1422 | |
1353 | 1423 | "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], |
| @@ -1368,6 +1438,8 @@ | ||
1368 | 1438 | |
1369 | 1439 | "form-data-encoder": ["[email protected]", "", {}, "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="], |
1370 | 1440 | |
1441 | + "formdata-polyfill": ["[email protected]", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], | |
1442 | + | |
1371 | 1443 | "forwarded-parse": ["[email protected]", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], |
1372 | 1444 | |
1373 | 1445 | "fraction.js": ["[email protected]", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], |
| @@ -1388,7 +1460,7 @@ | ||
1388 | 1460 | |
1389 | 1461 | "function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], |
1390 | 1462 | |
1391 | - "gaxios": ["[email protected]", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], | |
1463 | + "gaxios": ["[email protected]", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], | |
1392 | 1464 | |
1393 | 1465 | "gcp-metadata": ["[email protected]", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], |
1394 | 1466 | |
| @@ -1422,7 +1494,13 @@ | ||
1422 | 1494 | |
1423 | 1495 | "globe.gl": ["[email protected]", "", { "dependencies": { "@tweenjs/tween.js": "18 - 25", "accessor-fn": "1", "kapsule": "^1.16", "three": ">=0.154 <1", "three-globe": "^2.45", "three-render-objects": "^1.40" } }, "sha512-fjkLHVBrnbESkUgklTd4UbcGLciu4nIl49IIi1hclLjI6MU3ASu6JYmf/K5qwPf7I+tNOauQRr4i5Y28JTtHQg=="], |
1424 | 1496 | |
1425 | - "google-logging-utils": ["[email protected]", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], | |
1497 | + "google-auth-library": ["[email protected]", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], | |
1498 | + | |
1499 | + "google-logging-utils": ["[email protected]", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], | |
1500 | + | |
1501 | + "googleapis": ["[email protected]", "", { "dependencies": { "google-auth-library": "^10.2.0", "googleapis-common": "^8.0.0" } }, "sha512-IOGMG8tljCZSLvYgdojRu6mB10KEsK0J7X62sXXlQz9koe5BUAW+rqkY3qhQM9wXM6hVL3/Hase7XbxoMyeYiQ=="], | |
1502 | + | |
1503 | + "googleapis-common": ["[email protected]", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" } }, "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A=="], | |
1426 | 1504 | |
1427 | 1505 | "gopd": ["[email protected]", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], |
1428 | 1506 | |
| @@ -1432,6 +1510,8 @@ | ||
1432 | 1510 | |
1433 | 1511 | "gray-matter": ["[email protected]", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], |
1434 | 1512 | |
1513 | + "gtoken": ["[email protected]", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], | |
1514 | + | |
1435 | 1515 | "h3-js": ["[email protected]", "", {}, "sha512-DvJh07MhGgY2KcC4OeZc8SSyA+ZXpdvoh6uCzGpoKvWtZxJB+g6VXXC1+eWYkaMIsLz7J/ErhOalHCpcs1KYog=="], |
1436 | 1516 | |
1437 | 1517 | "has-property-descriptors": ["[email protected]", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], |
| @@ -1556,6 +1636,8 @@ | ||
1556 | 1636 | |
1557 | 1637 | "joycon": ["[email protected]", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], |
1558 | 1638 | |
1639 | + "js-base64": ["[email protected]", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="], | |
1640 | + | |
1559 | 1641 | "js-tokens": ["[email protected]", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], |
1560 | 1642 | |
1561 | 1643 | "js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], |
| @@ -1568,6 +1650,10 @@ | ||
1568 | 1650 | |
1569 | 1651 | "jsonfile": ["[email protected]", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], |
1570 | 1652 | |
1653 | + "jwa": ["[email protected]", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], | |
1654 | + | |
1655 | + "jws": ["[email protected]", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], | |
1656 | + | |
1571 | 1657 | "kapsule": ["[email protected]", "", { "dependencies": { "lodash-es": "4" } }, "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg=="], |
1572 | 1658 | |
1573 | 1659 | "keyv": ["[email protected]", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ=="], |
| @@ -1576,6 +1662,8 @@ | ||
1576 | 1662 | |
1577 | 1663 | "kleur": ["[email protected]", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], |
1578 | 1664 | |
1665 | + "libsql": ["[email protected]", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.22", "@libsql/darwin-x64": "0.5.22", "@libsql/linux-arm-gnueabihf": "0.5.22", "@libsql/linux-arm-musleabihf": "0.5.22", "@libsql/linux-arm64-gnu": "0.5.22", "@libsql/linux-arm64-musl": "0.5.22", "@libsql/linux-x64-gnu": "0.5.22", "@libsql/linux-x64-musl": "0.5.22", "@libsql/win32-x64-msvc": "0.5.22" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA=="], | |
1666 | + | |
1579 | 1667 | "lilconfig": ["[email protected]", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], |
1580 | 1668 | |
1581 | 1669 | "lines-and-columns": ["[email protected]", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], |
| @@ -1652,6 +1740,8 @@ | ||
1652 | 1740 | |
1653 | 1741 | "merge2": ["[email protected]", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], |
1654 | 1742 | |
1743 | + "meshoptimizer": ["[email protected]", "", {}, "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg=="], | |
1744 | + | |
1655 | 1745 | "micromark": ["[email protected]", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], |
1656 | 1746 | |
1657 | 1747 | "micromark-core-commonmark": ["[email protected]", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], |
| @@ -1776,7 +1866,9 @@ | ||
1776 | 1866 | |
1777 | 1867 | "node-abort-controller": ["[email protected]", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="], |
1778 | 1868 | |
1779 | - "node-fetch": ["[email protected]", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], | |
1869 | + "node-domexception": ["[email protected]", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], | |
1870 | + | |
1871 | + "node-fetch": ["[email protected]", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], | |
1780 | 1872 | |
1781 | 1873 | "node-gyp": ["[email protected]", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], |
1782 | 1874 | |
| @@ -1786,6 +1878,8 @@ | ||
1786 | 1878 | |
1787 | 1879 | "nodegit": ["[email protected]", "", { "dependencies": { "@axosoft/nan": "^2.22.0-gk.1", "@mapbox/node-pre-gyp": "^2.0.0", "fs-extra": "^7.0.0", "got": "^14.4.7", "json5": "^2.1.0", "lodash": "^4.17.14", "node-gyp": "^11.2.0", "tar-fs": "^3.0.9" } }, "sha512-aHNrupRANyg0oy8dlHhG3Ipyd7Fh2nDOV1apPDXgRoM4gxo1icfHdMJmLUvCCCan9nUmz+wH7nEUTSsn7QLDuA=="], |
1788 | 1880 | |
1881 | + "nodemailer": ["[email protected]", "", {}, "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA=="], | |
1882 | + | |
1789 | 1883 | "nopt": ["[email protected]", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], |
1790 | 1884 | |
1791 | 1885 | "normalize-path": ["[email protected]", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], |
| @@ -1798,6 +1892,8 @@ | ||
1798 | 1892 | |
1799 | 1893 | "object-hash": ["[email protected]", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], |
1800 | 1894 | |
1895 | + "object-inspect": ["[email protected]", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], | |
1896 | + | |
1801 | 1897 | "obug": ["[email protected]", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], |
1802 | 1898 | |
1803 | 1899 | "on-exit-leak-free": ["[email protected]", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], |
| @@ -1864,6 +1960,8 @@ | ||
1864 | 1960 | |
1865 | 1961 | "pgpass": ["[email protected]", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], |
1866 | 1962 | |
1963 | + "phenomenon": ["[email protected]", "", {}, "sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A=="], | |
1964 | + | |
1867 | 1965 | "picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], |
1868 | 1966 | |
1869 | 1967 | "picomatch": ["[email protected]", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], |
| @@ -1932,6 +2030,8 @@ | ||
1932 | 2030 | |
1933 | 2031 | "prom-client": ["[email protected]", "", { "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" } }, "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g=="], |
1934 | 2032 | |
2033 | + "promise-limit": ["[email protected]", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], | |
2034 | + | |
1935 | 2035 | "promise-retry": ["[email protected]", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], |
1936 | 2036 | |
1937 | 2037 | "prompts": ["[email protected]", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], |
| @@ -1944,6 +2044,8 @@ | ||
1944 | 2044 | |
1945 | 2045 | "pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], |
1946 | 2046 | |
2047 | + "qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], | |
2048 | + | |
1947 | 2049 | "queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], |
1948 | 2050 | |
1949 | 2051 | "quick-format-unescaped": ["[email protected]", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], |
| @@ -2036,6 +2138,8 @@ | ||
2036 | 2138 | |
2037 | 2139 | "reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], |
2038 | 2140 | |
2141 | + "rimraf": ["[email protected]", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], | |
2142 | + | |
2039 | 2143 | "robust-predicates": ["[email protected]", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], |
2040 | 2144 | |
2041 | 2145 | "rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="], |
| @@ -2076,6 +2180,14 @@ | ||
2076 | 2180 | |
2077 | 2181 | "shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], |
2078 | 2182 | |
2183 | + "side-channel": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], | |
2184 | + | |
2185 | + "side-channel-list": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], | |
2186 | + | |
2187 | + "side-channel-map": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], | |
2188 | + | |
2189 | + "side-channel-weakmap": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], | |
2190 | + | |
2079 | 2191 | "siginfo": ["[email protected]", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], |
2080 | 2192 | |
2081 | 2193 | "signal-exit": ["[email protected]", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], |
| @@ -2234,6 +2346,8 @@ | ||
2234 | 2346 | |
2235 | 2347 | "ultrahtml": ["[email protected]", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], |
2236 | 2348 | |
2349 | + "uncrypto": ["[email protected]", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], | |
2350 | + | |
2237 | 2351 | "undici-types": ["[email protected]", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], |
2238 | 2352 | |
2239 | 2353 | "unified": ["[email protected]", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], |
| @@ -2264,6 +2378,8 @@ | ||
2264 | 2378 | |
2265 | 2379 | "update-browserslist-db": ["[email protected]", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="], |
2266 | 2380 | |
2381 | + "url-template": ["[email protected]", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="], | |
2382 | + | |
2267 | 2383 | "use-callback-ref": ["[email protected]", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], |
2268 | 2384 | |
2269 | 2385 | "use-sidecar": ["[email protected]", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], |
| @@ -2288,6 +2404,8 @@ | ||
2288 | 2404 | |
2289 | 2405 | "web-namespaces": ["[email protected]", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], |
2290 | 2406 | |
2407 | + "web-streams-polyfill": ["[email protected]", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], | |
2408 | + | |
2291 | 2409 | "webidl-conversions": ["[email protected]", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], |
2292 | 2410 | |
2293 | 2411 | "whatwg-url": ["[email protected]", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], |
| @@ -2310,6 +2428,8 @@ | ||
2310 | 2428 | |
2311 | 2429 | "wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], |
2312 | 2430 | |
2431 | + "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], | |
2432 | + | |
2313 | 2433 | "xtend": ["[email protected]", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], |
2314 | 2434 | |
2315 | 2435 | "xxhash-wasm": ["[email protected]", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], |
| @@ -2346,6 +2466,72 @@ | ||
2346 | 2466 | |
2347 | 2467 | "@aws-crypto/util/@smithy/util-utf8": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], |
2348 | 2468 | |
2469 | + "@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws-sdk/xml-builder": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw=="], | |
2470 | + | |
2471 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.957.0", "@aws-sdk/credential-provider-http": "3.957.0", "@aws-sdk/credential-provider-ini": "3.958.0", "@aws-sdk/credential-provider-process": "3.957.0", "@aws-sdk/credential-provider-sso": "3.958.0", "@aws-sdk/credential-provider-web-identity": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ=="], | |
2472 | + | |
2473 | + "@aws-sdk/client-sesv2/@aws-sdk/middleware-host-header": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA=="], | |
2474 | + | |
2475 | + "@aws-sdk/client-sesv2/@aws-sdk/middleware-logger": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ=="], | |
2476 | + | |
2477 | + "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA=="], | |
2478 | + | |
2479 | + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ=="], | |
2480 | + | |
2481 | + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A=="], | |
2482 | + | |
2483 | + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg=="], | |
2484 | + | |
2485 | + "@aws-sdk/client-sesv2/@aws-sdk/types": ["@aws-sdk/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg=="], | |
2486 | + | |
2487 | + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-endpoints": "^3.2.7", "tslib": "^2.6.2" } }, "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw=="], | |
2488 | + | |
2489 | + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-browser": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw=="], | |
2490 | + | |
2491 | + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q=="], | |
2492 | + | |
2493 | + "@aws-sdk/client-sesv2/@smithy/config-resolver": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg=="], | |
2494 | + | |
2495 | + "@aws-sdk/client-sesv2/@smithy/core": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.8", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ=="], | |
2496 | + | |
2497 | + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/querystring-builder": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg=="], | |
2498 | + | |
2499 | + "@aws-sdk/client-sesv2/@smithy/hash-node": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw=="], | |
2500 | + | |
2501 | + "@aws-sdk/client-sesv2/@smithy/invalid-dependency": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ=="], | |
2502 | + | |
2503 | + "@aws-sdk/client-sesv2/@smithy/middleware-content-length": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg=="], | |
2504 | + | |
2505 | + "@aws-sdk/client-sesv2/@smithy/middleware-endpoint": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/core": "^3.20.0", "@smithy/middleware-serde": "^4.2.8", "@smithy/node-config-provider": "^4.3.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg=="], | |
2506 | + | |
2507 | + "@aws-sdk/client-sesv2/@smithy/middleware-retry": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/service-error-classification": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg=="], | |
2508 | + | |
2509 | + "@aws-sdk/client-sesv2/@smithy/middleware-serde": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w=="], | |
2510 | + | |
2511 | + "@aws-sdk/client-sesv2/@smithy/middleware-stack": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw=="], | |
2512 | + | |
2513 | + "@aws-sdk/client-sesv2/@smithy/node-config-provider": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw=="], | |
2514 | + | |
2515 | + "@aws-sdk/client-sesv2/@smithy/node-http-handler": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/querystring-builder": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ=="], | |
2516 | + | |
2517 | + "@aws-sdk/client-sesv2/@smithy/protocol-http": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA=="], | |
2518 | + | |
2519 | + "@aws-sdk/client-sesv2/@smithy/smithy-client": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/core": "^3.20.0", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-stack": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" } }, "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g=="], | |
2520 | + | |
2521 | + "@aws-sdk/client-sesv2/@smithy/types": ["@smithy/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA=="], | |
2522 | + | |
2523 | + "@aws-sdk/client-sesv2/@smithy/url-parser": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg=="], | |
2524 | + | |
2525 | + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/property-provider": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ=="], | |
2526 | + | |
2527 | + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/config-resolver": "^4.4.5", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA=="], | |
2528 | + | |
2529 | + "@aws-sdk/client-sesv2/@smithy/util-endpoints": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg=="], | |
2530 | + | |
2531 | + "@aws-sdk/client-sesv2/@smithy/util-middleware": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w=="], | |
2532 | + | |
2533 | + "@aws-sdk/client-sesv2/@smithy/util-retry": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg=="], | |
2534 | + | |
2349 | 2535 | "@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], |
2350 | 2536 | |
2351 | 2537 | "@babel/helper-compilation-targets/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], |
| @@ -2358,6 +2544,10 @@ | ||
2358 | 2544 | |
2359 | 2545 | "@isaacs/cliui/wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], |
2360 | 2546 | |
2547 | + "@mapbox/node-pre-gyp/detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
2548 | + | |
2549 | + "@mapbox/node-pre-gyp/node-fetch": ["[email protected]", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], | |
2550 | + | |
2361 | 2551 | "@npmcli/agent/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], |
2362 | 2552 | |
2363 | 2553 | "@opentelemetry/instrumentation-pg/@types/pg": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="], |
| @@ -2434,10 +2624,16 @@ | ||
2434 | 2624 | |
2435 | 2625 | "fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], |
2436 | 2626 | |
2437 | - "gaxios/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], | |
2627 | + "gcp-metadata/gaxios": ["[email protected]", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], | |
2628 | + | |
2629 | + "gcp-metadata/google-logging-utils": ["[email protected]", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], | |
2438 | 2630 | |
2439 | 2631 | "get-stream/is-stream": ["[email protected]", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], |
2440 | 2632 | |
2633 | + "globe.gl/@tweenjs/tween.js": ["@tweenjs/[email protected]", "", {}, "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A=="], | |
2634 | + | |
2635 | + "google-auth-library/gcp-metadata": ["[email protected]", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], | |
2636 | + | |
2441 | 2637 | "got/decompress-response": ["[email protected]", "", { "dependencies": { "mimic-response": "^4.0.0" } }, "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q=="], |
2442 | 2638 | |
2443 | 2639 | "gray-matter/js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], |
| @@ -2456,6 +2652,8 @@ | ||
2456 | 2652 | |
2457 | 2653 | "minipass-sized/minipass": ["[email protected]", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], |
2458 | 2654 | |
2655 | + "node-gyp-build-optional-packages/detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
2656 | + | |
2459 | 2657 | "nodegit/tar-fs": ["[email protected]", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="], |
2460 | 2658 | |
2461 | 2659 | "p-locate/p-limit": ["[email protected]", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], |
| @@ -2468,6 +2666,8 @@ | ||
2468 | 2666 | |
2469 | 2667 | "postcss-nested/postcss-selector-parser": ["[email protected]", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], |
2470 | 2668 | |
2669 | + "prebuild-install/detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
2670 | + | |
2471 | 2671 | "prompts/kleur": ["[email protected]", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], |
2472 | 2672 | |
2473 | 2673 | "rc/strip-json-comments": ["[email protected]", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], |
| @@ -2480,6 +2680,8 @@ | ||
2480 | 2680 | |
2481 | 2681 | "send/debug": ["[email protected]", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], |
2482 | 2682 | |
2683 | + "sharp/detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
2684 | + | |
2483 | 2685 | "string-width-cjs/emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], |
2484 | 2686 | |
2485 | 2687 | "string-width-cjs/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], |
| @@ -2492,6 +2694,10 @@ | ||
2492 | 2694 | |
2493 | 2695 | "tar-stream/readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], |
2494 | 2696 | |
2697 | + "three-globe/@tweenjs/tween.js": ["@tweenjs/[email protected]", "", {}, "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A=="], | |
2698 | + | |
2699 | + "three-render-objects/@tweenjs/tween.js": ["@tweenjs/[email protected]", "", {}, "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A=="], | |
2700 | + | |
2495 | 2701 | "vite/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], |
2496 | 2702 | |
2497 | 2703 | "wrap-ansi-cjs/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], |
| @@ -2512,6 +2718,62 @@ | ||
2512 | 2718 | |
2513 | 2719 | "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], |
2514 | 2720 | |
2721 | + "@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA=="], | |
2722 | + | |
2723 | + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/property-provider": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="], | |
2724 | + | |
2725 | + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/signature-v4": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg=="], | |
2726 | + | |
2727 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog=="], | |
2728 | + | |
2729 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" } }, "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw=="], | |
2730 | + | |
2731 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/credential-provider-env": "3.957.0", "@aws-sdk/credential-provider-http": "3.957.0", "@aws-sdk/credential-provider-login": "3.958.0", "@aws-sdk/credential-provider-process": "3.957.0", "@aws-sdk/credential-provider-sso": "3.958.0", "@aws-sdk/credential-provider-web-identity": "3.958.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg=="], | |
2732 | + | |
2733 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg=="], | |
2734 | + | |
2735 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/client-sso": "3.958.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/token-providers": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg=="], | |
2736 | + | |
2737 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA=="], | |
2738 | + | |
2739 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA=="], | |
2740 | + | |
2741 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="], | |
2742 | + | |
2743 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg=="], | |
2744 | + | |
2745 | + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-arn-parser": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg=="], | |
2746 | + | |
2747 | + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg=="], | |
2748 | + | |
2749 | + "@aws-sdk/client-sesv2/@smithy/core/@smithy/util-stream": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w=="], | |
2750 | + | |
2751 | + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg=="], | |
2752 | + | |
2753 | + "@aws-sdk/client-sesv2/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg=="], | |
2754 | + | |
2755 | + "@aws-sdk/client-sesv2/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0" } }, "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA=="], | |
2756 | + | |
2757 | + "@aws-sdk/client-sesv2/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="], | |
2758 | + | |
2759 | + "@aws-sdk/client-sesv2/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg=="], | |
2760 | + | |
2761 | + "@aws-sdk/client-sesv2/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw=="], | |
2762 | + | |
2763 | + "@aws-sdk/client-sesv2/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg=="], | |
2764 | + | |
2765 | + "@aws-sdk/client-sesv2/@smithy/smithy-client/@smithy/util-stream": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w=="], | |
2766 | + | |
2767 | + "@aws-sdk/client-sesv2/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w=="], | |
2768 | + | |
2769 | + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="], | |
2770 | + | |
2771 | + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA=="], | |
2772 | + | |
2773 | + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="], | |
2774 | + | |
2775 | + "@aws-sdk/client-sesv2/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.11.0" } }, "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA=="], | |
2776 | + | |
2515 | 2777 | "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], |
2516 | 2778 | |
2517 | 2779 | "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], |
| @@ -2620,6 +2882,10 @@ | ||
2620 | 2882 | |
2621 | 2883 | "cross-spawn/which/isexe": ["[email protected]", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], |
2622 | 2884 | |
2885 | + "gcp-metadata/gaxios/node-fetch": ["[email protected]", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], | |
2886 | + | |
2887 | + "gcp-metadata/gaxios/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], | |
2888 | + | |
2623 | 2889 | "got/decompress-response/mimic-response": ["[email protected]", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], |
2624 | 2890 | |
2625 | 2891 | "gray-matter/js-yaml/argparse": ["[email protected]", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], |
| @@ -2698,8 +2964,26 @@ | ||
2698 | 2964 | |
2699 | 2965 | "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], |
2700 | 2966 | |
2967 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w=="], | |
2968 | + | |
2969 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA=="], | |
2970 | + | |
2971 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw=="], | |
2972 | + | |
2973 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg=="], | |
2974 | + | |
2975 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q=="], | |
2976 | + | |
2977 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw=="], | |
2978 | + | |
2979 | + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g=="], | |
2980 | + | |
2981 | + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w=="], | |
2982 | + | |
2701 | 2983 | "ansi-align/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], |
2702 | 2984 | |
2703 | 2985 | "yargs/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], |
2986 | + | |
2987 | + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw=="], | |
2704 | 2988 | } |
2705 | 2989 | } |
2706 | 2990 | |
βΌcli/README.md
+365-0
| @@ -0,0 +1,365 @@ | ||
1 | +# OCH CLI - OpenCodeHub Command Line Interface | |
2 | + | |
3 | +**Stack-first PR workflows from your terminal.** | |
4 | + | |
5 | +## Installation | |
6 | + | |
7 | +```bash | |
8 | +# Install globally | |
9 | +npm install -g opencodehub-cli | |
10 | + | |
11 | +# Or use with npx (no installation needed) | |
12 | +npx opencodehub-cli | |
13 | + | |
14 | +# Verify installation | |
15 | +och --version | |
16 | +``` | |
17 | + | |
18 | +## Quick Start | |
19 | + | |
20 | +```bash | |
21 | +# 1. Login to your OpenCodeHub instance | |
22 | +och auth login | |
23 | + | |
24 | +# Enter your email and password when prompted | |
25 | +# Or use a personal access token: | |
26 | +och auth login --token YOUR_TOKEN | |
27 | + | |
28 | +# 2. Navigate to your repository | |
29 | +cd your-repo | |
30 | + | |
31 | +# 3. Initialize (one-time setup) | |
32 | +och init --url https://git.yourcompany.com | |
33 | + | |
34 | +# 4. You're ready! Try: | |
35 | +och status | |
36 | +``` | |
37 | + | |
38 | +## Commands | |
39 | + | |
40 | +### Authentication | |
41 | + | |
42 | +```bash | |
43 | +# Interactive login | |
44 | +och auth login | |
45 | + | |
46 | +# Login with server URL | |
47 | +och auth login --url https://git.yourcompany.com | |
48 | + | |
49 | +# Login with token (CI/CD) | |
50 | +och auth login --token YOUR_ACCESS_TOKEN | |
51 | + | |
52 | +# Check who you're logged in as | |
53 | +och auth whoami | |
54 | + | |
55 | +# Logout | |
56 | +och auth logout | |
57 | +``` | |
58 | + | |
59 | +### Stack Management | |
60 | + | |
61 | +**Create Stacked Branches:** | |
62 | +```bash | |
63 | +# Create a new branch in current stack | |
64 | +och stack create \u003cbranch-name\u003e | |
65 | + | |
66 | +# Example: | |
67 | +git checkout main | |
68 | +och stack create database-schema | |
69 | +# Make changes, commit | |
70 | +och stack create auth-service | |
71 | +# Make changes, commit | |
72 | +``` | |
73 | + | |
74 | +**View Stack:** | |
75 | +```bash | |
76 | +# See all branches in current stack | |
77 | +och stack view | |
78 | + | |
79 | +# Short alias: | |
80 | +och stack ls | |
81 | + | |
82 | +# Example output: | |
83 | +# π Current Stack | |
84 | +# ββ main (base) | |
85 | +# ββ stack/database-schema β Pushed | |
86 | +# ββ stack/auth-service * Current | |
87 | +``` | |
88 | + | |
89 | +**Submit Stack:** | |
90 | +```bash | |
91 | +# Push current branch and create/update PR | |
92 | +och stack submit | |
93 | + | |
94 | +# Create as draft PR | |
95 | +och stack submit --draft | |
96 | + | |
97 | +# This will: | |
98 | +# - Push your branch to origin | |
99 | +# - Create PR or update existing one | |
100 | +# - Link it to parent PR if in a stack | |
101 | +``` | |
102 | + | |
103 | +**Sync Stack:** | |
104 | +```bash | |
105 | +# Rebase entire stack on latest main | |
106 | +och stack sync | |
107 | + | |
108 | +# This handles: | |
109 | +# - Fetching latest from origin | |
110 | +# - Rebasing each branch in order | |
111 | +# - Detecting and reporting conflicts | |
112 | +``` | |
113 | + | |
114 | +**Reorder Stack:** | |
115 | +```bash | |
116 | +# Interactively reorder branches | |
117 | +och stack reorder | |
118 | + | |
119 | +# Choose new order from list | |
120 | +# Stack dependencies will be updated | |
121 | +``` | |
122 | + | |
123 | +### Repository Operations | |
124 | + | |
125 | +```bash | |
126 | +# Initialize repo for OpenCodeHub | |
127 | +och init [--url \u003cserver-url\u003e] | |
128 | + | |
129 | +# Show current stack status | |
130 | +och status | |
131 | +# Alias: och st | |
132 | + | |
133 | +# Sync with remote (bi-directional) | |
134 | +och sync | |
135 | +``` | |
136 | + | |
137 | +## Workflow Examples | |
138 | + | |
139 | +### Example 1: Create a Feature Stack | |
140 | + | |
141 | +```bash | |
142 | +# Start from main | |
143 | +git checkout main | |
144 | +git pull | |
145 | + | |
146 | +# Create base layer | |
147 | +och stack create user-models | |
148 | +# ... edit files ... | |
149 | +git add . | |
150 | +git commit -m "Add user database models" | |
151 | + | |
152 | +# Create middleware layer (builds on user-models) | |
153 | +och stack create auth-middleware | |
154 | +# ... edit files ... | |
155 | +git add . | |
156 | +git commit -m "Add authentication middleware" | |
157 | + | |
158 | +# Create UI layer (builds on auth-middleware) | |
159 | +och stack create login-page | |
160 | +# ... edit files ... | |
161 | +git add . | |
162 | +git commit -m "Add login page UI" | |
163 | + | |
164 | +# Submit entire stack | |
165 | +och stack submit | |
166 | + | |
167 | +# Result: | |
168 | +# PR #301: Add user database models (main β user-models) | |
169 | +# PR #302: Add auth middleware (user-models β auth-middleware) | |
170 | +# PR #303: Add login page (auth-middleware β login-page) | |
171 | +``` | |
172 | + | |
173 | +### Example 2: Update Stack After Review | |
174 | + | |
175 | +```bash | |
176 | +# You're on stack/login-page | |
177 | +# Reviewer requested changes | |
178 | + | |
179 | +# Make changes | |
180 | +git add . | |
181 | +git commit -m "Address review feedback" | |
182 | + | |
183 | +# Resubmit (updates PR automatically) | |
184 | +och stack submit | |
185 | + | |
186 | +# If base changed, sync the whole stack | |
187 | +och stack sync | |
188 | +``` | |
189 | + | |
190 | +### Example 3: collaboration on Stacks | |
191 | + | |
192 | +```bash | |
193 | +# Teammate created a stack, you want to build on it | |
194 | +git fetch | |
195 | +git checkout their-branch | |
196 | + | |
197 | +# Create your branch on top | |
198 | +och stack create my-feature | |
199 | +# ... make changes ... | |
200 | +git commit -m "My changes" | |
201 | + | |
202 | +# Submit (will link your PR to theirs) | |
203 | +och stack submit | |
204 | +``` | |
205 | + | |
206 | +## Configuration | |
207 | + | |
208 | +OCH CLI stores configuration in `~/.ochrc` (JSON format). | |
209 | + | |
210 | +**View config:** | |
211 | +```bash | |
212 | +och config list | |
213 | +``` | |
214 | + | |
215 | +**Set config:** | |
216 | +```bash | |
217 | +och config set host https://git.yourcompany.com | |
218 | +och config set token YOUR_TOKEN | |
219 | +``` | |
220 | + | |
221 | +**Config file structure:** | |
222 | +```json | |
223 | +{ | |
224 | + "host": "https://git.yourcompany.com", | |
225 | + "token": "your-access-token", | |
226 | + "defaultBranch": "main" | |
227 | +} | |
228 | +``` | |
229 | + | |
230 | +## Troubleshooting | |
231 | + | |
232 | +### "Not logged in" Error | |
233 | + | |
234 | +```bash | |
235 | +# Check auth status | |
236 | +och auth whoami | |
237 | + | |
238 | +# If not logged in: | |
239 | +och auth login | |
240 | +``` | |
241 | + | |
242 | +### "No stack found" Error | |
243 | + | |
244 | +```bash | |
245 | +# Ensure you're in a git repository | |
246 | +git status | |
247 | + | |
248 | +# Initialize if needed | |
249 | +och init | |
250 | + | |
251 | +# Check current branch | |
252 | +git branch | |
253 | +``` | |
254 | + | |
255 | +### Permission Denied | |
256 | + | |
257 | +```bash | |
258 | +# Verify your token has correct permissions | |
259 | +och auth whoami | |
260 | + | |
261 | +# Login again with fresh token | |
262 | +och auth logout | |
263 | +och auth login --token NEW_TOKEN | |
264 | +``` | |
265 | + | |
266 | +### Stack Sync Conflicts | |
267 | + | |
268 | +```bash | |
269 | +# If sync encounters conflicts: | |
270 | +# 1. Resolve conflicts in each branch | |
271 | +# 2. Continue sync: | |
272 | +git rebase --continue | |
273 | + | |
274 | +# 3. Or abort and do manual rebase: | |
275 | +git rebase --abort | |
276 | +git checkout main | |
277 | +git pull | |
278 | +git checkout your-branch | |
279 | +git rebase main | |
280 | +``` | |
281 | + | |
282 | +## CI/CD Usage | |
283 | + | |
284 | +```yaml | |
285 | +# .github/workflows/deploy.yml | |
286 | +name: Deploy Stack | |
287 | +on: | |
288 | + push: | |
289 | + branches: ['stack/**'] | |
290 | + | |
291 | +jobs: | |
292 | + deploy: | |
293 | + runs-on: ubuntu-latest | |
294 | + steps: | |
295 | + - uses: actions/checkout@v3 | |
296 | + | |
297 | + - name: Setup Node | |
298 | + uses: actions/setup-node@v3 | |
299 | + | |
300 | + - name: Install OCH CLI | |
301 | + run: npm install -g opencodehub-cli | |
302 | + | |
303 | + - name: Login | |
304 | + run: och auth login --token ${{ secrets.OCH_TOKEN }} | |
305 | + | |
306 | + - name: Submit PR | |
307 | + run: och stack submit | |
308 | +``` | |
309 | + | |
310 | +## Advanced Features | |
311 | + | |
312 | +### Custom PR Templates | |
313 | + | |
314 | +Create `.github/pull_request_template.md` in your repo: | |
315 | + | |
316 | +```markdown | |
317 | +## Description | |
318 | +<!-- What does this PR do? --> | |
319 | + | |
320 | +## Stack Position | |
321 | +<!-- Part of stack: #XXX β This PR β #YYY --> | |
322 | + | |
323 | +## Checklist | |
324 | +- [ ] Tests pass | |
325 | +- [ ] Docs updated | |
326 | +``` | |
327 | + | |
328 | +### Hooks | |
329 | + | |
330 | +OCH CLI supports git hooks for validation: | |
331 | + | |
332 | +```bash | |
333 | +# .git/hooks/pre-push | |
334 | +#!/bin/bash | |
335 | +# Validate stack before push | |
336 | +och stack verify || exit 1 | |
337 | +``` | |
338 | + | |
339 | +## API Integration | |
340 | + | |
341 | +OCH CLI uses OpenCodeHub REST API. Token permissions needed: | |
342 | +- `repo` - Repository read/write | |
343 | +- `pr` - Pull request create/update | |
344 | +- `user` - Read user profile | |
345 | + | |
346 | +Generate token: `https://your-server/settings/tokens` | |
347 | + | |
348 | +## Contributing | |
349 | + | |
350 | +OCH CLI is open source! Contribute at: | |
351 | +https://github.com/swadhinbiswas/OpencodeHub/tree/main/cli | |
352 | + | |
353 | +## Support | |
354 | + | |
355 | +- **Documentation**: https://git.yourcompany.com/docs | |
356 | +- **Issues**: https://github.com/swadhinbiswas/OpencodeHub/issues | |
357 | +- **Discussions**: https://github.com/swadhinbiswas/OpencodeHub/discussions | |
358 | + | |
359 | +## changelog | |
360 | + | |
361 | +See [CHANGELOG.md](./CHANGELOG.md) for version history. | |
362 | + | |
363 | +## License | |
364 | + | |
365 | +MIT License - see [LICENSE](../LICENSE) | |
0 | 366 | |
βΌdocs/features/ai-review.md
+600-0
| @@ -0,0 +1,600 @@ | ||
1 | +# AI Code Review | |
2 | + | |
3 | +> Get instant, intelligent code reviews powered by GPT-4 or Claude | |
4 | + | |
5 | +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. | |
6 | + | |
7 | +## π Table of Contents | |
8 | + | |
9 | +- [Overview](#overview) | |
10 | +- [Setup](#setup) | |
11 | + - [Configure AI Provider](#configure-ai-provider) | |
12 | + - [Enable for Repository](#enable-for-repository) | |
13 | +- [Using AI Review](#using-ai-review) | |
14 | +- [Understanding Review Output](#understanding-review-output) | |
15 | +- [Customizing Reviews](#customizing-reviews) | |
16 | +- [Cost Considerations](#cost-considerations) | |
17 | +- [Best Practices](#best-practices) | |
18 | +- [Troubleshooting](#troubleshooting) | |
19 | + | |
20 | +--- | |
21 | + | |
22 | +## Overview | |
23 | + | |
24 | +### What AI Review Catches | |
25 | + | |
26 | +π **Security Vulnerabilities** | |
27 | +- SQL injection risks | |
28 | +- XSS (Cross-Site Scripting) vectors | |
29 | +- Authentication bypasses | |
30 | +- Secrets/credentials in code | |
31 | +- Insecure dependencies | |
32 | + | |
33 | +β‘ **Performance Issues** | |
34 | +- N+1 database queries | |
35 | +- Inefficient algorithms (O(nΒ²) β O(n log n)) | |
36 | +- Memory leaks | |
37 | +- Blocking operations in async code | |
38 | +- Missing indexes | |
39 | + | |
40 | +π **Code Quality** | |
41 | +- Best practice violations | |
42 | +- Code smells (long functions, deep nesting) | |
43 | +- Incomplete error handling | |
44 | +- Missing input validation | |
45 | +- Poor naming conventions | |
46 | + | |
47 | +π **Potential Bugs** | |
48 | +- Null/undefined pointer exceptions | |
49 | +- Race conditions | |
50 | +- Off-by-one errors | |
51 | +- Logic flaws | |
52 | +- Type mismatches | |
53 | + | |
54 | +### How It Works | |
55 | + | |
56 | +``` | |
57 | +1. Developer opens/updates PR | |
58 | + β | |
59 | +2. OpenCodeHub sends diff to AI | |
60 | + β | |
61 | +3. AI analyzes code changes | |
62 | + β | |
63 | +4. AI posts inline comments + summary | |
64 | + β | |
65 | +5. Developer addresses feedback | |
66 | +``` | |
67 | + | |
68 | +**Privacy note:** Code is sent to AI provider (OpenAI/Anthropic). Use on-premise models for sensitive code. | |
69 | + | |
70 | +--- | |
71 | + | |
72 | +## Setup | |
73 | + | |
74 | +### Configure AI Provider | |
75 | + | |
76 | +**Option 1: OpenAI (GPT-4)** | |
77 | + | |
78 | +1. Get API key from [platform.openai.com](https://platform.openai.com/api-keys) | |
79 | + | |
80 | +2. Add to `.env`: | |
81 | + ```env | |
82 | + AI_PROVIDER=openai | |
83 | + OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx | |
84 | + OPENAI_MODEL=gpt-4-turbo-preview # or gpt-4, gpt-3.5-turbo | |
85 | + ``` | |
86 | + | |
87 | +3. Restart OpenCodeHub | |
88 | + | |
89 | +**Option 2: Anthropic (Claude)** | |
90 | + | |
91 | +1. Get API key from [console.anthropic.com](https://console.anthropic.com/) | |
92 | + | |
93 | +2. Add to `.env`: | |
94 | + ```env | |
95 | + AI_PROVIDER=anthropic | |
96 | + ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx | |
97 | + ANTHROPIC_MODEL=claude-3-opus-20240229 # or claude-3-sonnet | |
98 | + ``` | |
99 | + | |
100 | +3. Restart OpenCodeHub | |
101 | + | |
102 | +**Option 3: Azure OpenAI** | |
103 | + | |
104 | +```env | |
105 | +AI_PROVIDER=azure-openai | |
106 | +AZURE_OPENAI_API_KEY=xxxxx | |
107 | +AZURE_OPENAI_ENDPOINT=https://yourinstance.openai.azure.com/ | |
108 | +AZURE_OPENAI_DEPLOYMENT=your-deployment-name | |
109 | +``` | |
110 | + | |
111 | +**Option 4: Self-Hosted (Coming Soon)** | |
112 | + | |
113 | +OpenCodeHub will support local models via Ollama: | |
114 | +```env | |
115 | +AI_PROVIDER=ollama | |
116 | +OLLAMA_ENDPOINT=http://localhost:11434 | |
117 | +OLLAMA_MODEL=codellama:13b | |
118 | +``` | |
119 | + | |
120 | +### Enable for Repository | |
121 | + | |
122 | +**Via Web UI:** | |
123 | + | |
124 | +1. Navigate to **Repository β Settings β AI Review** | |
125 | +2. Toggle **"Enable AI Code Review"** | |
126 | +3. Configure settings: | |
127 | + - **Trigger**: `On PR open`, `On push`, or `Manual only` | |
128 | + - **Auto-comment**: Post comments automatically | |
129 | + - **Severity threshold**: `All`, `Warning+`, or `Critical only` | |
130 | + - **Max files**: Limit review to N files (cost control) | |
131 | +4. Click **Save** | |
132 | + | |
133 | +**Via API:** | |
134 | + | |
135 | +```bash | |
136 | +curl -X PATCH https://git.yourcompany.com/api/repos/123/ai-review \ | |
137 | + -H "Authorization: Bearer $TOKEN" \ | |
138 | + -d '{ | |
139 | + "enabled": true, | |
140 | + "trigger": "on_pr_open", | |
141 | + "auto_comment": true, | |
142 | + "severity_threshold": "warning" | |
143 | + }' | |
144 | +``` | |
145 | + | |
146 | +**Via CLI:** | |
147 | + | |
148 | +```bash | |
149 | +och repo config ai-review --enable \ | |
150 | + --trigger on_pr_open \ | |
151 | + --auto-comment | |
152 | +``` | |
153 | + | |
154 | +--- | |
155 | + | |
156 | +## Using AI Review | |
157 | + | |
158 | +### Automatic Reviews | |
159 | + | |
160 | +If enabled, AI reviews trigger automatically: | |
161 | + | |
162 | +```bash | |
163 | +# 1. Create PR | |
164 | +git push origin feature-branch | |
165 | +# β AI review starts automatically | |
166 | + | |
167 | +# 2. Update PR | |
168 | +git push origin feature-branch | |
169 | +# β AI re-reviews changed files only | |
170 | +``` | |
171 | + | |
172 | +You'll see: | |
173 | +- β
**Status check**: "AI Review: In Progress" β "AI Review: Complete" | |
174 | +- π¬ **Inline comments**: On specific lines with issues | |
175 | +- π **Summary comment**: Overall assessment | |
176 | + | |
177 | +### Manual Reviews | |
178 | + | |
179 | +Request AI review on-demand: | |
180 | + | |
181 | +**Via Web UI:** | |
182 | +1. Open PR page | |
183 | +2. Click **"Request AI Review"** button | |
184 | +3. Wait ~30-60 seconds | |
185 | +4. Review appears as comments | |
186 | + | |
187 | +**Via CLI:** | |
188 | +```bash | |
189 | +# Review specific PR | |
190 | +och review ai 125 | |
191 | + | |
192 | +# Review with custom prompt | |
193 | +och review ai 125 --focus security | |
194 | + | |
195 | +# Re-review after changes | |
196 | +och review ai 125 --force | |
197 | +``` | |
198 | + | |
199 | +**Via API:** | |
200 | +```bash | |
201 | +curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \ | |
202 | + -H "Authorization: Bearer $TOKEN" | |
203 | +``` | |
204 | + | |
205 | +--- | |
206 | + | |
207 | +## Understanding Review Output | |
208 | + | |
209 | +### Severity Levels | |
210 | + | |
211 | +AI assigns severity to each finding: | |
212 | + | |
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 | | |
218 | + | |
219 | +### Example Review Comment | |
220 | + | |
221 | +```markdown | |
222 | +π΄ CRITICAL: SQL Injection Vulnerability | |
223 | + | |
224 | +**File:** `src/api/users.ts` | |
225 | +**Line:** 45 | |
226 | + | |
227 | +**Issue:** | |
228 | +User input is directly concatenated into SQL query without sanitization. | |
229 | + | |
230 | +**Current Code:** | |
231 | +```typescript | |
232 | +const query = `SELECT * FROM users WHERE email = '${userEmail}'`; | |
233 | +``` | |
234 | + | |
235 | +**Risk:** | |
236 | +An attacker could inject malicious SQL: | |
237 | +``` | |
238 | +userEmail = "' OR '1'='1" | |
239 | +β Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1' | |
240 | +β Returns ALL users (authentication bypass) | |
241 | +``` | |
242 | + | |
243 | +**Fix:** | |
244 | +Use parameterized queries or ORM: | |
245 | +```typescript | |
246 | +const query = `SELECT * FROM users WHERE email = ?`; | |
247 | +const results = await db.query(query, [userEmail]); | |
248 | + | |
249 | +// Or with ORM: | |
250 | +const user = await db.users.findOne({ email: userEmail }); | |
251 | +``` | |
252 | + | |
253 | +**References:** | |
254 | +- [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) | |
255 | +- [Node.js SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) | |
256 | +``` | |
257 | + | |
258 | +### Summary Comment | |
259 | + | |
260 | +AI posts an overall summary: | |
261 | + | |
262 | +```markdown | |
263 | +## π€ AI Code Review Summary | |
264 | + | |
265 | +**Overall Assessment:** β οΈ Needs Attention | |
266 | + | |
267 | +### Statistics | |
268 | +- Files reviewed: 8 | |
269 | +- Critical issues: 2 | |
270 | +- Warnings: 5 | |
271 | +- Info: 3 | |
272 | + | |
273 | +### Critical Issues | |
274 | +1. π΄ SQL Injection in `src/api/users.ts:45` | |
275 | +2. π΄ Hardcoded API key in `src/config.ts:12` | |
276 | + | |
277 | +### Recommendations | |
278 | +1. Add input validation to all API endpoints | |
279 | +2. Move secrets to environment variables | |
280 | +3. Consider adding integration tests for auth flows | |
281 | + | |
282 | +### Positive Notes | |
283 | +- β
Good error handling in payment service | |
284 | +- β
Well-structured database migrations | |
285 | +- β
Comprehensive TypeScript types | |
286 | + | |
287 | +**Estimated fix time:** 2-3 hours | |
288 | +``` | |
289 | + | |
290 | +--- | |
291 | + | |
292 | +## Customizing Reviews | |
293 | + | |
294 | +### Review Rules | |
295 | + | |
296 | +Create `.opencodehub/ai-review.yml` in your repository: | |
297 | + | |
298 | +```yaml | |
299 | +# AI Review Configuration | |
300 | + | |
301 | +# Which files to review | |
302 | +include: | |
303 | + - "src/**/*.ts" | |
304 | + - "src/**/*.tsx" | |
305 | + - "lib/**/*.js" | |
306 | + | |
307 | +# Which files to skip | |
308 | +exclude: | |
309 | + - "**/*.test.ts" | |
310 | + - "**/*.spec.ts" | |
311 | + - "**/generated/**" | |
312 | + - "dist/**" | |
313 | + | |
314 | +# Focus areas (weighted 1-10) | |
315 | +focus: | |
316 | + security: 10 # Highest priority | |
317 | + performance: 8 | |
318 | + bugs: 8 | |
319 | + code_quality: 5 | |
320 | + documentation: 3 | |
321 | + | |
322 | +# Severity thresholds | |
323 | +report: | |
324 | + critical: always | |
325 | + warning: always | |
326 | + info: on_request # Only if --verbose flag used | |
327 | + | |
328 | +# Custom rules | |
329 | +custom_rules: | |
330 | + - name: "No console.log in production" | |
331 | + pattern: 'console\\.log\\(' | |
332 | + severity: warning | |
333 | + message: "Remove console.log before merging" | |
334 | + | |
335 | + - name: "Require error handling" | |
336 | + pattern: 'await.*\\(.*\\)' | |
337 | + check: must_have_try_catch | |
338 | + severity: warning | |
339 | + | |
340 | +# AI instructions | |
341 | +instructions: > | |
342 | + Pay special attention to: | |
343 | + 1. Authentication and authorization logic | |
344 | + 2. Database query efficiency | |
345 | + 3. Input validation | |
346 | + 4. Proper error handling | |
347 | + | |
348 | + Our coding standards: | |
349 | + - TypeScript strict mode | |
350 | + - Functional programming preferred | |
351 | + - Maximum function length: 50 lines | |
352 | + - Maximum cyclomatic complexity: 10 | |
353 | +``` | |
354 | + | |
355 | +### Custom Prompts | |
356 | + | |
357 | +**Focus on specific aspects:** | |
358 | + | |
359 | +```bash | |
360 | +# Security-focused review | |
361 | +och review ai 125 --focus security | |
362 | + | |
363 | +# Performance-focused review | |
364 | +och review ai 125 --focus performance | |
365 | + | |
366 | +# Architecture review | |
367 | +och review ai 125 --prompt "Review system architecture and design patterns" | |
368 | +``` | |
369 | + | |
370 | +### Language-Specific Reviews | |
371 | + | |
372 | +AI automatically adapts to programming language: | |
373 | + | |
374 | +- **TypeScript/JavaScript**: Type safety, async/await, React best practices | |
375 | +- **Python**: PEP 8, type hints, Django/Flask patterns | |
376 | +- **Go**: Go idioms, error handling, concurrency | |
377 | +- **Rust**: Borrow checker, unsafe code, idiomatic Rust | |
378 | +- **Java**: Design patterns, Spring best practices, thread safety | |
379 | + | |
380 | +--- | |
381 | + | |
382 | +## Cost Considerations | |
383 | + | |
384 | +### Pricing | |
385 | + | |
386 | +AI review costs depend on provider and model: | |
387 | + | |
388 | +**OpenAI (GPT-4 Turbo):** | |
389 | +- ~$0.01 per 1K tokens (input) | |
390 | +- ~$0.03 per 1K tokens (output) | |
391 | +- **Typical PR**: 500-2000 tokens = **$0.02-0.10 per review** | |
392 | + | |
393 | +**Anthropic (Claude 3 Opus):** | |
394 | +- ~$0.015 per 1K tokens (input) | |
395 | +- ~$0.075 per 1K tokens (output) | |
396 | +- **Typical PR**: **$0.03-0.15 per review** | |
397 | + | |
398 | +**Example monthly cost:** | |
399 | +- 50 PRs/month Γ $0.05/review = **$2.50/month** | |
400 | +- 500 PRs/month Γ $0.05/review = **$25/month** | |
401 | + | |
402 | +### Cost Optimization | |
403 | + | |
404 | +**1. Limit file count:** | |
405 | +```yaml | |
406 | +max_files_per_review: 10 # Review only 10 largest changed files | |
407 | +``` | |
408 | + | |
409 | +**2. Use cheaper models for simple changes:** | |
410 | +```yaml | |
411 | +rules: | |
412 | + - if: changed_files < 3 | |
413 | + model: gpt-3.5-turbo # 10x cheaper | |
414 | + - if: changed_files >= 3 | |
415 | + model: gpt-4-turbo | |
416 | +``` | |
417 | + | |
418 | +**3. Skip test files:** | |
419 | +```yaml | |
420 | +exclude: | |
421 | + - "**/*.test.*" | |
422 | + - "**/*.spec.*" | |
423 | +``` | |
424 | + | |
425 | +**4. Review only on-request:** | |
426 | +```yaml | |
427 | +trigger: manual # Don't auto-review every push | |
428 | +``` | |
429 | + | |
430 | +**5. Use severity threshold:** | |
431 | +```yaml | |
432 | +severity_threshold: warning # Skip "info" level comments | |
433 | +``` | |
434 | + | |
435 | +--- | |
436 | + | |
437 | +## Best Practices | |
438 | + | |
439 | +### π‘ When to Use AI Review | |
440 | + | |
441 | +**β
Good Use Cases:** | |
442 | +- Security-sensitive code (auth, payments) | |
443 | +- Complex algorithms | |
444 | +- Database queries | |
445 | +- API endpoints | |
446 | +- Third-party integrations | |
447 | + | |
448 | +**β Less Useful:** | |
449 | +- Generated code | |
450 | +- Configuration files | |
451 | +- Simple refactoring | |
452 | +- Documentation-only changes | |
453 | + | |
454 | +### π‘ Responding to AI Feedback | |
455 | + | |
456 | +**1. Don't auto-fix everything** | |
457 | +- AI can be wrong | |
458 | +- Context matters | |
459 | +- Use judgment | |
460 | + | |
461 | +**2. Engage in discussion** | |
462 | +```markdown | |
463 | +@ai-review That's a good catch, but in this case we're using a | |
464 | +trusted source and the data is already sanitized earlier in the pipeline. | |
465 | + | |
466 | +See line 23 where we validate input. | |
467 | +``` | |
468 | + | |
469 | +**3. Track false positives** | |
470 | +```yaml | |
471 | +# .opencodehub/ai-review.yml | |
472 | +ignore: | |
473 | + - file: src/utils/legacy.ts | |
474 | + reason: "Legacy code, requires major refactor" | |
475 | + until: 2024-12-31 | |
476 | +``` | |
477 | + | |
478 | +### π‘ Combining with Human Review | |
479 | + | |
480 | +AI complements, doesn't replace humans: | |
481 | + | |
482 | +``` | |
483 | +1. AI reviews first (catches obvious issues) | |
484 | +2. Developer fixes critical/warnings | |
485 | +3. Human reviewer approves | |
486 | +``` | |
487 | + | |
488 | +This workflow: | |
489 | +- Reduces human reviewer burden | |
490 | +- Catches more bugs | |
491 | +- Faster feedback cycles | |
492 | + | |
493 | +--- | |
494 | + | |
495 | +## Troubleshooting | |
496 | + | |
497 | +### "AI review taking too long" | |
498 | + | |
499 | +**Cause:** Large PR (many files/lines). | |
500 | + | |
501 | +**Solution:** | |
502 | +```bash | |
503 | +# Check review status | |
504 | +och review status 125 | |
505 | + | |
506 | +# Cancel and retry | |
507 | +och review cancel 125 | |
508 | +och review ai 125 --max-files 5 | |
509 | +``` | |
510 | + | |
511 | +### "AI review failed" | |
512 | + | |
513 | +**Causes:** | |
514 | +1. API key invalid/expired | |
515 | +2. Rate limit exceeded | |
516 | +3. Timeout (very large PR) | |
517 | + | |
518 | +**Solutions:** | |
519 | +```bash | |
520 | +# 1. Verify API key | |
521 | +echo $OPENAI_API_KEY | |
522 | + | |
523 | +# 2. Check rate limits | |
524 | +curl https://api.openai.com/v1/usage \ | |
525 | + -H "Authorization: Bearer $OPENAI_API_KEY" | |
526 | + | |
527 | +# 3. Split large PR or review manually: | |
528 | +och review ai 125 --files src/api/users.ts src/api/auth.ts | |
529 | +``` | |
530 | + | |
531 | +### "Too many false positives" | |
532 | + | |
533 | +**Solution:** Tune detection thresholds: | |
534 | + | |
535 | +```yaml | |
536 | +# .opencodehub/ai-review.yml | |
537 | +sensitivity: | |
538 | + security: high # Strict | |
539 | + performance: medium | |
540 | + code_quality: low # Lenient | |
541 | +``` | |
542 | + | |
543 | +### "Missing obvious bugs" | |
544 | + | |
545 | +**Cause:** AI missed context or edge case. | |
546 | + | |
547 | +**Solution:** Add custom instructions: | |
548 | + | |
549 | +```yaml | |
550 | +instructions: > | |
551 | + Our codebase uses a custom ORM. When reviewing database code: | |
552 | + 1. `db.raw()` is already parameterized | |
553 | + 2. All user input goes through `sanitize()` middleware | |
554 | + 3. Check for missing `await` on async functions | |
555 | +``` | |
556 | + | |
557 | +--- | |
558 | + | |
559 | +## Advanced: CI/CD Integration | |
560 | + | |
561 | +### Block merge on critical findings | |
562 | + | |
563 | +```yaml | |
564 | +# .github/workflows/ai-review.yml | |
565 | +name: AI Code Review | |
566 | +on: [pull_request] | |
567 | + | |
568 | +jobs: | |
569 | + ai-review: | |
570 | + runs-on: ubuntu-latest | |
571 | + steps: | |
572 | + - name: Run AI Review | |
573 | + run: och review ai ${{ github.event.pull_request.number }} | |
574 | + env: | |
575 | + OCH_TOKEN: ${{ secrets.OCH_TOKEN }} | |
576 | + | |
577 | + - name: Check for critical issues | |
578 | + run: | | |
579 | + CRITICAL=$(och review status ${{ github.event.pull_request.number }} --json | jq '.critical_count') | |
580 | + if [ "$CRITICAL" -gt 0 ]; then | |
581 | + echo "β Critical issues found" | |
582 | + exit 1 | |
583 | + fi | |
584 | +``` | |
585 | + | |
586 | +--- | |
587 | + | |
588 | +## See Also | |
589 | + | |
590 | +- [Stacked Pull Requests](stacked-prs.md) | |
591 | +- [Smart Merge Queue](merge-queue.md) | |
592 | +- [CI/CD Integration](ci-cd.md) | |
593 | + | |
594 | +--- | |
595 | + | |
596 | +## Need Help? | |
597 | + | |
598 | +- π¬ [Discord Community](https://discord.gg/opencodehub) | |
599 | +- π [Report Issues](https://github.com/swadhinbiswas/OpencodeHub/issues) | |
600 | +- π [Full Documentation](/) | |
0 | 601 | |
βΌdocs/features/merge-queue.md
+564-0
| @@ -0,0 +1,564 @@ | ||
1 | +# Smart Merge Queue | |
2 | + | |
3 | +> Automate merging with stack-aware CI optimization and conflict resolution | |
4 | + | |
5 | +The Smart Merge Queue automatically merges approved pull requests in the correct order, running CI efficiently and handling rebases automatically. | |
6 | + | |
7 | +## οΏ½οΏ½ Table of Contents | |
8 | + | |
9 | +- [Overview](#overview) | |
10 | +- [How It Works](#how-it-works) | |
11 | +- [Getting Started](#getting-started) | |
12 | +- [Queue Management](#queue-management) | |
13 | +- [Configuration](#configuration) | |
14 | +- [Best Practices](#best-practices) | |
15 | +- [Troubleshooting](#troubleshooting) | |
16 | + | |
17 | +--- | |
18 | + | |
19 | +## Overview | |
20 | + | |
21 | +### The Problem | |
22 | + | |
23 | +Manual merging is: | |
24 | +- β° **Slow** - Wait for CI, merge, repeat | |
25 | +- π **Error-prone** - Merge conflicts, wrong order | |
26 | +- πΈ **Expensive** - Run CI for every PR individually | |
27 | +- π **Tedious** - Babysit the merge process | |
28 | + | |
29 | +### The Solution | |
30 | + | |
31 | +Smart Merge Queue: | |
32 | + | |
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 | |
37 | +β
**Priority Handling** - Hotfixes jump the queue | |
38 | + | |
39 | +### Real-World Impact | |
40 | + | |
41 | +**Without Queue:** | |
42 | +``` | |
43 | +PR #123 β Wait for CI (5 min) β Merge β | |
44 | +PR #124 β Rebase β Wait for CI (5 min) β Merge β | |
45 | +PR #125 β Rebase β Wait for CI (5 min) β Merge | |
46 | +Total: ~20 minutes, 3 CI runs | |
47 | +``` | |
48 | + | |
49 | +**With Queue:** | |
50 | +``` | |
51 | +Add #123, #124, #125 to queue β | |
52 | +Queue detects dependencies β | |
53 | +Merges #123 β | |
54 | +Auto-rebases #124 β | |
55 | +Batches #124 + #125 for CI (if compatible) β | |
56 | +Merges both | |
57 | +Total: ~8 minutes, 1-2 CI runs π | |
58 | +``` | |
59 | + | |
60 | +--- | |
61 | + | |
62 | +## How It Works | |
63 | + | |
64 | +### Queue Flow | |
65 | + | |
66 | +``` | |
67 | +1. PR gets approved | |
68 | + β | |
69 | +2. Add to merge queue | |
70 | + β | |
71 | +3. Queue checks: | |
72 | + - All approvals? β | |
73 | + - CI passing? β | |
74 | + - Conflicts? Auto-rebase | |
75 | + - Dependencies? Order correctly | |
76 | + β | |
77 | +4. Run final CI check | |
78 | + β | |
79 | +5. Merge to main | |
80 | + β | |
81 | +6. Update dependent PRs | |
82 | +``` | |
83 | + | |
84 | +### Stack-Aware Merging | |
85 | + | |
86 | +Queue understands stacks: | |
87 | + | |
88 | +``` | |
89 | +Queue contains: | |
90 | +- PR #123 (base) | |
91 | +- PR #124 (depends on #123) | |
92 | +- PR #125 (depends on #124) | |
93 | + | |
94 | +Queue automatically: | |
95 | +1. Merges #123 first | |
96 | +2. Rebases #124 onto new main | |
97 | +3. Runs CI for #124 | |
98 | +4. Merges #124 | |
99 | +5. Rebases #125 | |
100 | +6. Merges #125 | |
101 | + | |
102 | +All automatic! | |
103 | +``` | |
104 | + | |
105 | +### CI Optimization | |
106 | + | |
107 | +**Batching:** | |
108 | +``` | |
109 | +PR #126: Fix typo in docs (no code changes) | |
110 | +PR #127: Update README | |
111 | +PR #128: Fix CSS bug | |
112 | + | |
113 | +Queue batches #126 + #127 (docs only) | |
114 | +β Single CI run | |
115 | +β Merge both together | |
116 | +β 50% CI cost savings! | |
117 | +``` | |
118 | + | |
119 | +**Smart Scheduling:** | |
120 | +- Long CI jobs run in parallel | |
121 | +- Short jobs batch together | |
122 | +- Stack dependencies respected | |
123 | + | |
124 | +--- | |
125 | + | |
126 | +## Getting Started | |
127 | + | |
128 | +### Prerequisites | |
129 | + | |
130 | +- Repository with CI configured | |
131 | +- Merge permissions | |
132 | +- Branch protection (optional but recommended) | |
133 | + | |
134 | +### Adding PR to Queue | |
135 | + | |
136 | +**Via Web UI:** | |
137 | + | |
138 | +1. Open approved PR | |
139 | +2. Click **"Add to Merge Queue"** button | |
140 | +3. PR status changes to "In Queue" | |
141 | +4. Watch it merge automatically! | |
142 | + | |
143 | +**Via CLI:** | |
144 | + | |
145 | +```bash | |
146 | +# Add single PR | |
147 | +och queue add 125 | |
148 | + | |
149 | +# Add multiple PRs | |
150 | +och queue add 123 124 125 | |
151 | + | |
152 | +# Add with priority (hotfix) | |
153 | +och queue add 126 --priority high | |
154 | + | |
155 | +# Add when approved | |
156 | +och queue add 127 --when approved | |
157 | +``` | |
158 | + | |
159 | +**Via API:** | |
160 | + | |
161 | +```bash | |
162 | +curl -X POST https://git.yourcompany.com/api/queue \ | |
163 | + -H "Authorization: Bearer $TOKEN" \ | |
164 | + -d '{"pr_id": 125, "priority": "normal"}' | |
165 | +``` | |
166 | + | |
167 | +### Viewing Queue Status | |
168 | + | |
169 | +**Web UI:** | |
170 | +Navigate to `/queue` or click "Merge Queue" in repository navigation | |
171 | + | |
172 | +**CLI:** | |
173 | +```bash | |
174 | +# View entire queue | |
175 | +och queue list | |
176 | + | |
177 | +# Output: | |
178 | +# Position PR Title Status ETA | |
179 | +# 1 #125 Login UI π Running CI 2 min | |
180 | +# 2 #124 Auth service β³ Waiting 5 min | |
181 | +# 3 #123 Database schema β
Ready 7 min | |
182 | +# 4 #126 Hotfix: XSS π₯ Priority 1 min | |
183 | + | |
184 | +# Watch queue in real-time | |
185 | +och queue watch | |
186 | +``` | |
187 | + | |
188 | +**API:** | |
189 | +```bash | |
190 | +curl https://git.yourcompany.com/api/queue \ | |
191 | + -H "Authorization: Bearer $TOKEN" | |
192 | +``` | |
193 | + | |
194 | +--- | |
195 | + | |
196 | +## Queue Management | |
197 | + | |
198 | +### Priority Levels | |
199 | + | |
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 | | |
206 | + | |
207 | +**Setting priority:** | |
208 | +```bash | |
209 | +# CLI | |
210 | +och queue add 126 --priority critical | |
211 | + | |
212 | +# Web UI: Select priority dropdown when adding to queue | |
213 | +``` | |
214 | + | |
215 | +### Removing from Queue | |
216 | + | |
217 | +```bash | |
218 | +# Remove specific PR | |
219 | +och queue remove 125 | |
220 | + | |
221 | +# Remove all your PRs | |
222 | +och queue remove --mine | |
223 | + | |
224 | +# Clear entire queue (admin only) | |
225 | +och queue clear | |
226 | +``` | |
227 | + | |
228 | +### Pausing the Queue | |
229 | + | |
230 | +```bash | |
231 | +# Pause (emergency) | |
232 | +och queue pause --reason "Investigating production issue" | |
233 | + | |
234 | +# Resume | |
235 | +och queue resume | |
236 | +``` | |
237 | + | |
238 | +--- | |
239 | + | |
240 | +## Configuration | |
241 | + | |
242 | +### Repository Settings | |
243 | + | |
244 | +**Repository β Settings β Merge Queue** | |
245 | + | |
246 | +```yaml | |
247 | +# Merge strategy | |
248 | +merge_method: squash # or merge, rebase | |
249 | + | |
250 | +# CI requirements | |
251 | +require_ci: true | |
252 | +required_checks: | |
253 | + - "test" | |
254 | + - "lint" | |
255 | + - "build" | |
256 | + | |
257 | +# Approval requirements | |
258 | +require_approvals: 1 | |
259 | +dismiss_stale_reviews: true | |
260 | + | |
261 | +# Batching | |
262 | +enable_batching: true | |
263 | +batch_window: 5m # Wait 5 min to batch PRs | |
264 | +max_batch_size: 3 # Max 3 PRs per batch | |
265 | + | |
266 | +# Rebasing | |
267 | +auto_rebase: true # Auto-rebase on conflicts | |
268 | +rebase_strategy: merge # or rebase, squash | |
269 | + | |
270 | +# Priorities | |
271 | +allow_priority_override: true # Let users set priority | |
272 | +max_queue_size: 50 # Max PRs in queue | |
273 | +``` | |
274 | + | |
275 | +### Branch Protection Rules | |
276 | + | |
277 | +Recommended settings: | |
278 | + | |
279 | +```yaml | |
280 | +# .github/branch-protection.yml | |
281 | +branches: | |
282 | + main: | |
283 | + required_status_checks: | |
284 | + - test | |
285 | + - lint | |
286 | + required_approvals: 1 | |
287 | + allow_force_push: false | |
288 | + allow_queue_bypass: false # Even admins use queue! | |
289 | +``` | |
290 | + | |
291 | +### Webhook Notifications | |
292 | + | |
293 | +Get notified when PRs merge: | |
294 | + | |
295 | +```bash | |
296 | +# Configure webhook | |
297 | +curl -X POST https://git.yourcompany.com/api/webhooks \ | |
298 | + -d '{ | |
299 | + "url": "https://yourapp.com/webhook", | |
300 | + "events": ["merge_queue.merged", "merge_queue.failed"], | |
301 | + "secret": "your-secret" | |
302 | + }' | |
303 | +``` | |
304 | + | |
305 | +**Payload:** | |
306 | +```json | |
307 | +{ | |
308 | + "event": "merge_queue.merged", | |
309 | + "pr": { | |
310 | + "number": 125, | |
311 | + "title": "Add login UI", | |
312 | + "author": "swadhin" | |
313 | + }, | |
314 | + "queue": { | |
315 | + "position": 1, | |
316 | + "wait_time_seconds": 180 | |
317 | + }, | |
318 | + "timestamp": "2024-01-01T12:00:00Z" | |
319 | +} | |
320 | +``` | |
321 | + | |
322 | +--- | |
323 | + | |
324 | +## Best Practices | |
325 | + | |
326 | +### π‘ When to Use the Queue | |
327 | + | |
328 | +**β
Always use for:** | |
329 | +- Feature branches β main | |
330 | +- Release branches | |
331 | +- Any PR affecting production | |
332 | + | |
333 | +**β Don't use for:** | |
334 | +- Draft PRs | |
335 | +- WIP branches | |
336 | +- Experimental features | |
337 | + | |
338 | +### π‘ Optimizing Queue Performance | |
339 | + | |
340 | +**1. Fast CI:** | |
341 | +```yaml | |
342 | +# Run quick tests in queue | |
343 | +queue_ci: | |
344 | + - unit_tests # 2 min | |
345 | + | |
346 | +# Run slow tests post-merge | |
347 | +post_merge_ci: | |
348 | + - integration_tests # 10 min | |
349 | + - e2e_tests # 15 min | |
350 | +``` | |
351 | + | |
352 | +**2. Parallel CIs:** | |
353 | +```yaml | |
354 | +# Run multiple jobs in parallel | |
355 | +ci: | |
356 | + - test (3 min) | |
357 | + - lint (1 min) | |
358 | + - build (2 min) | |
359 | +# Total: 3 min (not 6 min!) | |
360 | +``` | |
361 | + | |
362 | +**3. Smart batching:** | |
363 | +```bash | |
364 | +# Group related PRs | |
365 | +och queue add 123 124 125 --batch-id feature-auth | |
366 | + | |
367 | +# Queue runs CI once for entire batch | |
368 | +``` | |
369 | + | |
370 | +### π‘ Handling Conflicts | |
371 | + | |
372 | +**Automatic resolution:** | |
373 | +```yaml | |
374 | +auto_rebase: true | |
375 | +rebase_strategy: merge # Creates merge commit | |
376 | + | |
377 | +# Or: | |
378 | +rebase_strategy: rebase # Cleaner history | |
379 | +``` | |
380 | + | |
381 | +**Manual resolution:** | |
382 | +```bash | |
383 | +# If auto-rebase fails, you'll get notification | |
384 | +# Fix locally: | |
385 | +git checkout feature-branch | |
386 | +git fetch origin | |
387 | +git rebase origin/main | |
388 | +# Resolve conflicts | |
389 | +git push --force-with-lease | |
390 | + | |
391 | +# PR automatically re-enters queue | |
392 | +``` | |
393 | + | |
394 | +### π‘ Queue Monitoring | |
395 | + | |
396 | +**Set up alerts:** | |
397 | +```yaml | |
398 | +# Slack notification if queue stalled | |
399 | +alerts: | |
400 | + - type: queue_stalled | |
401 | + threshold: 30m | |
402 | + channel: "#eng-deployments" | |
403 | + | |
404 | + - type: merge_failed | |
405 | + channel: "#eng-alerts" | |
406 | +``` | |
407 | + | |
408 | +**Dashboard example:** | |
409 | +```bash | |
410 | +# View queue health | |
411 | +och queue stats | |
412 | + | |
413 | +# Output: | |
414 | +# Queue Health: β
Good | |
415 | +# - Average wait time: 4.2 min | |
416 | +# - CI success rate: 94% | |
417 | +# - Merge rate: 15 PRs/hour | |
418 | +# - Current size: 8 PRs | |
419 | +``` | |
420 | + | |
421 | +--- | |
422 | + | |
423 | +## Troubleshooting | |
424 | + | |
425 | +### "PR stuck in queue" | |
426 | + | |
427 | +**Causes:** | |
428 | +1. CI failing | |
429 | +2. Waiting for approval | |
430 | +3. Merge conflict | |
431 | +4. Dependency not merged yet | |
432 | + | |
433 | +**Solutions:** | |
434 | +```bash | |
435 | +# Check status | |
436 | +och queue status 125 | |
437 | + | |
438 | +# View logs | |
439 | +och queue logs 125 | |
440 | + | |
441 | +# Remove and re-add | |
442 | +och queue remove 125 | |
443 | +# Fix issue | |
444 | +och queue add 125 | |
445 | +``` | |
446 | + | |
447 | +### "Auto-rebase failed" | |
448 | + | |
449 | +**Cause:** Complex merge conflicts. | |
450 | + | |
451 | +**Solution:** | |
452 | +```bash | |
453 | +# Remove from queue | |
454 | +och queue remove 125 | |
455 | + | |
456 | +# Manually rebase | |
457 | +git checkout feature-branch | |
458 | +git rebase origin/main | |
459 | +# Resolve conflicts | |
460 | +git push -f | |
461 | + | |
462 | +# Re-add to queue | |
463 | +och queue add 125 | |
464 | +``` | |
465 | + | |
466 | +### "Queue not processing" | |
467 | + | |
468 | +**Causes:** | |
469 | +1. Queue paused | |
470 | +2. CI service down | |
471 | +3. Rate limit reached | |
472 | + | |
473 | +**Solutions:** | |
474 | +```bash | |
475 | +# Check queue status | |
476 | +och queue info | |
477 | + | |
478 | +# Resume if paused | |
479 | +och queue resume | |
480 | + | |
481 | +# Check CI status | |
482 | +curl https://ci-service/status | |
483 | + | |
484 | +# View queue system logs (admin) | |
485 | +och admin logs queue | |
486 | +``` | |
487 | + | |
488 | +### "Wrong merge order" | |
489 | + | |
490 | +**Cause:** Dependencies not detected. | |
491 | + | |
492 | +**Solution:** | |
493 | +```bash | |
494 | +# Manually set dependency | |
495 | +och pr edit 125 --depends-on 124 | |
496 | + | |
497 | +# Queue will automatically reorder | |
498 | +``` | |
499 | + | |
500 | +--- | |
501 | + | |
502 | +## Advanced Features | |
503 | + | |
504 | +### Merge Train | |
505 | + | |
506 | +For high-velocity teams: | |
507 | + | |
508 | +```yaml | |
509 | +# Merge up to 5 PRs simultaneously | |
510 | +merge_train: | |
511 | + enabled: true | |
512 | + max_concurrent: 5 | |
513 | + | |
514 | +# Creates temporary integration branches | |
515 | +# Runs CI in parallel | |
516 | +# Merges in rapid succession | |
517 | +``` | |
518 | + | |
519 | +### Scheduled Merges | |
520 | + | |
521 | +```bash | |
522 | +# Merge during business hours only | |
523 | +och queue add 125 --schedule "Mon-Fri 9am-5pm EST" | |
524 | + | |
525 | +# Merge during maintenance window | |
526 | +och queue add 126 --schedule "Sat 2am-4am EST" | |
527 | +``` | |
528 | + | |
529 | +### Queue Hooks | |
530 | + | |
531 | +Custom automation: | |
532 | + | |
533 | +```javascript | |
534 | +// .opencodehub/hooks/queue.js | |
535 | +module.exports = { | |
536 | + beforeMerge(pr) { | |
537 | + // Send Slack notification | |
538 | + // Update JIRA ticket | |
539 | + // Trigger deployment | |
540 | + }, | |
541 | + | |
542 | + afterMerge(pr) { | |
543 | + // Create release notes | |
544 | + // Update documentation | |
545 | + } | |
546 | +}; | |
547 | +``` | |
548 | + | |
549 | +--- | |
550 | + | |
551 | +## See Also | |
552 | + | |
553 | +- [Stacked Pull Requests](stacked-prs.md) | |
554 | +- [AI Code Review](ai-review.md) | |
555 | +- [CI/CD Integration](ci-cd.md) | |
556 | +- [Webhooks](../guides/webhooks.md) | |
557 | + | |
558 | +--- | |
559 | + | |
560 | +## Need Help? | |
561 | + | |
562 | +- π¬ [Discord Community](https://discord.gg/opencodehub) | |
563 | +- π [Report Issues](https://github.com/swadhinbiswas/OpencodeHub/issues) | |
564 | +- π [Full Documentation](/) | |
0 | 565 | |
βΌdocs/features/stacked-prs.md
+459-0
| @@ -0,0 +1,459 @@ | ||
1 | +# Stacked Pull Requests | |
2 | + | |
3 | +> Break large features into small, reviewable changes that ship faster | |
4 | + | |
5 | +Stacked Pull Requests (Stacked PRs) is a development workflow that allows you to break large features into a series of small, dependent pull requests. Each PR in the stack builds on the previous one, enabling faster reviews and parallel development. | |
6 | + | |
7 | +## π Table of Contents | |
8 | + | |
9 | +- [Why Stacked PRs?](#why-stacked-prs) | |
10 | +- [How It Works](#how-it-works) | |
11 | +- [Getting Started](#getting-started) | |
12 | + - [Creating Your First Stack](#creating-your-first-stack-web-ui) | |
13 | + - [Using the CLI](#creating-a-stack-with-och-cli) | |
14 | +- [Managing Stacks](#managing-stacks) | |
15 | +- [Best Practices](#best-practices) | |
16 | +- [Troubleshooting](#troubleshooting) | |
17 | + | |
18 | +--- | |
19 | + | |
20 | +## Why Stacked PRs? | |
21 | + | |
22 | +### The Problem with Large PRs | |
23 | + | |
24 | +Large pull requests are: | |
25 | +- β° **Slow to review** - Reviewers get overwhelmed | |
26 | +- π **More bugs** - Hard to spot issues in 1000+ line diffs | |
27 | +- π **Conflict-prone** - More changes = more merge conflicts | |
28 | +- π¦ **Risky to deploy** - Big changes = big risk | |
29 | + | |
30 | +### The Stacked PR Solution | |
31 | + | |
32 | +Stacked PRs solve these problems by: | |
33 | + | |
34 | +β
**Faster Reviews** - Small, focused PRs get reviewed in minutes, not days | |
35 | +β
**Better Code Quality** - Reviewers can focus on specific changes | |
36 | +β
**Parallel Development** - Build on unmerged PRs while waiting for review | |
37 | +β
**Easier Rollback** - Deploy incrementally, rollback specific changes | |
38 | +β
**Clear History** - Each PR tells a story | |
39 | + | |
40 | +### Real-World Example | |
41 | + | |
42 | +**Without Stacks (Traditional):** | |
43 | +``` | |
44 | +PR #1: Add user authentication system (2,500 lines, 45 files) | |
45 | +ββ Review time: 3-5 days, high defect rate | |
46 | +``` | |
47 | + | |
48 | +**With Stacks:** | |
49 | +``` | |
50 | +PR #1: Add user database schema (100 lines, 2 files) | |
51 | +ββ Review time: 15 minutes β
| |
52 | + | |
53 | +PR #2: Add auth service layer (200 lines, 3 files) [depends on #1] | |
54 | +ββ Review time: 30 minutes β
| |
55 | + | |
56 | +PR #3: Add login/logout endpoints (150 lines, 4 files) [depends on #2] | |
57 | +ββ Review time: 20 minutes β
| |
58 | + | |
59 | +PR #4: Add login UI (180 lines, 5 files) [depends on #3] | |
60 | +ββ Review time: 25 minutes β
| |
61 | + | |
62 | +Total review time: ~1.5 hours (vs. 3-5 days) | |
63 | +``` | |
64 | + | |
65 | +--- | |
66 | + | |
67 | +## How It Works | |
68 | + | |
69 | +### Stack Visualization | |
70 | + | |
71 | +``` | |
72 | +main branch | |
73 | + βββ PR #123: Add database schema β
MERGED | |
74 | + βββ PR #124: Add auth service π IN REVIEW | |
75 | + βββ PR #125: Add login UI β³ WAITING | |
76 | +``` | |
77 | + | |
78 | +### Auto-Rebasing | |
79 | + | |
80 | +When PR #123 merges into main: | |
81 | +1. OpenCodeHub automatically rebases PR #124 onto the updated main | |
82 | +2. PR #125 is rebased onto the updated #124 | |
83 | +3. No manual work required! π | |
84 | + | |
85 | +### Dependency Tracking | |
86 | + | |
87 | +OpenCodeHub tracks dependencies automatically: | |
88 | +- Visual stack graph in PR view | |
89 | +- Auto-merge in correct order | |
90 | +- Conflict detection | |
91 | +- Status propagation | |
92 | + | |
93 | +--- | |
94 | + | |
95 | +## Getting Started | |
96 | + | |
97 | +### Prerequisites | |
98 | + | |
99 | +- OpenCodeHub account | |
100 | +- Repository with write access | |
101 | +- Git installed locally (or use web UI) | |
102 | + | |
103 | +### Creating Your First Stack (Web UI) | |
104 | + | |
105 | +**Step 1: Create Base PR** | |
106 | + | |
107 | +1. Create a feature branch: `git checkout -b feature/user-auth` | |
108 | +2. Make your first change (e.g., database schema) | |
109 | +3. Commit: `git commit -m "Add user table schema"` | |
110 | +4. Push: `git push origin feature/user-auth` | |
111 | +5. Create PR via web UI: `http://your-instance/owner/repo/compare` | |
112 | + | |
113 | +**Step 2: Stack Second PR** | |
114 | + | |
115 | +1. Create second branch from first: `git checkout -b feature/auth-service` | |
116 | +2. Make your changes | |
117 | +3. Commit: `git commit -m "Add authentication service"` | |
118 | +4. Push: `git push origin feature/auth-service` | |
119 | +5. When creating PR, select **"Stack on PR #123"** in the PR creation form | |
120 | + | |
121 | +**Step 3: Continue Stacking** | |
122 | + | |
123 | +Repeat the process for each layer of your feature! | |
124 | + | |
125 | +### Creating a Stack with OCH CLI | |
126 | + | |
127 | +The CLI makes stacking even easier: | |
128 | + | |
129 | +```bash | |
130 | +# 1. Start from main | |
131 | +git checkout main | |
132 | +git pull | |
133 | + | |
134 | +# 2. Create first branch | |
135 | +och stack create user-schema | |
136 | +# ... make changes ... | |
137 | +git add . | |
138 | +git commit -m "Add user table schema" | |
139 | + | |
140 | +# 3. Create second branch (automatically stacks) | |
141 | +och stack create auth-service | |
142 | +# ... make changes ... | |
143 | +git add . | |
144 | +git commit -m "Add authentication service" | |
145 | + | |
146 | +# 4. Create third branch | |
147 | +och stack create login-ui | |
148 | +# ... make changes ... | |
149 | +git add . | |
150 | +git commit -m "Add login UI" | |
151 | + | |
152 | +# 5. Submit entire stack | |
153 | +och stack submit | |
154 | + | |
155 | +# Result: 3 PRs created, automatically linked! | |
156 | +``` | |
157 | + | |
158 | +Install OCH CLI: | |
159 | +```bash | |
160 | +npm install -g opencodehub-cli | |
161 | +och auth login | |
162 | +``` | |
163 | + | |
164 | +--- | |
165 | + | |
166 | +## Managing Stacks | |
167 | + | |
168 | +### Viewing Your Stack | |
169 | + | |
170 | +**Web UI:** | |
171 | +- Navigate to any PR in the stack | |
172 | +- See **"Stack View"** panel showing all related PRs | |
173 | +- Visual dependency graph | |
174 | + | |
175 | +**CLI:** | |
176 | +```bash | |
177 | +och stack view | |
178 | + | |
179 | +# Output: | |
180 | +# π Current Stack | |
181 | +# ββ main (base) | |
182 | +# ββ #123: user-schema β
Merged | |
183 | +# ββ #124: auth-service π In Review | |
184 | +# ββ #125: login-ui β³ Draft | |
185 | +``` | |
186 | + | |
187 | +### Updating a PR in the Stack | |
188 | + | |
189 | +When you need to make changes: | |
190 | + | |
191 | +```bash | |
192 | +# Checkout the branch | |
193 | +git checkout feature/auth-service | |
194 | + | |
195 | +# Make changes | |
196 | +# ... edit files ... | |
197 | + | |
198 | +# Commit and push | |
199 | +git add . | |
200 | +git commit -m "Address review feedback" | |
201 | +git push | |
202 | + | |
203 | +# OpenCodeHub automatically updates dependent PRs if needed | |
204 | +``` | |
205 | + | |
206 | +### Syncing the Stack | |
207 | + | |
208 | +If main branch advances while your stack is in review: | |
209 | + | |
210 | +```bash | |
211 | +# Automatically rebase entire stack | |
212 | +och stack sync | |
213 | + | |
214 | +# Or manually: | |
215 | +git checkout feature/user-schema | |
216 | +git rebase main | |
217 | +git push --force-with-lease | |
218 | + | |
219 | +# Repeat for each branch in stack | |
220 | +``` | |
221 | + | |
222 | +### Merging the Stack | |
223 | + | |
224 | +**Option 1: Merge One at a Time** | |
225 | +1. Get PR #123 approved β Merge | |
226 | +2. OpenCodeHub auto-rebases #124 onto main | |
227 | +3. Get #124 approved β Merge | |
228 | +4. #125 auto-rebases | |
229 | +5. Get #125 approved β Merge | |
230 | + | |
231 | +**Option 2: Use Merge Queue** | |
232 | +```bash | |
233 | +# Add all PRs to merge queue | |
234 | +och queue add 123 | |
235 | +och queue add 124 | |
236 | +och queue add 125 | |
237 | + | |
238 | +# Queue handles: | |
239 | +# - Waiting for approvals | |
240 | +# - Running CI for each | |
241 | +# - Merging in correct order | |
242 | +# - Auto-rebasing | |
243 | +``` | |
244 | + | |
245 | +--- | |
246 | + | |
247 | +## Best Practices | |
248 | + | |
249 | +### π‘ Stack Size | |
250 | + | |
251 | +**Ideal stack:** | |
252 | +- 3-5 PRs per stack | |
253 | +- Each PR: 100-300 lines | |
254 | +- Each PR: Single, focused change | |
255 | + | |
256 | +**Too deep?** Consider splitting into multiple independent stacks. | |
257 | + | |
258 | +### π‘ PR Descriptions | |
259 | + | |
260 | +Make each PR standalone: | |
261 | + | |
262 | +```markdown | |
263 | +# Bad | |
264 | +"Part 2 of user auth" | |
265 | + | |
266 | +# Good | |
267 | +"Add authentication service layer | |
268 | + | |
269 | +Depends on: #123 (database schema) | |
270 | + | |
271 | +This PR adds: | |
272 | +- JWT token generation | |
273 | +- Password hashing with bcrypt | |
274 | +- Session management | |
275 | + | |
276 | +The next PR will add the API endpoints." | |
277 | +``` | |
278 | + | |
279 | +### π‘ Commit Messages | |
280 | + | |
281 | +Use conventional commits: | |
282 | + | |
283 | +```bash | |
284 | +feat: add user authentication schema | |
285 | +feat: implement JWT token service | |
286 | +feat: add login/logout endpoints | |
287 | +feat: create login UI component | |
288 | +``` | |
289 | + | |
290 | +### π‘ Testing | |
291 | + | |
292 | +Test each layer: | |
293 | + | |
294 | +```bash | |
295 | +# In PR #123 | |
296 | +npm test -- auth/schema.test.ts | |
297 | + | |
298 | +# In PR #124 | |
299 | +npm test -- auth/service.test.ts | |
300 | + | |
301 | +# In PR #125 | |
302 | +npm test -- components/Login.test.tsx | |
303 | +``` | |
304 | + | |
305 | +### π‘ Review Strategy | |
306 | + | |
307 | +**For Reviewers:** | |
308 | +- Review PRs in order (bottom β top) | |
309 | +- Approve quickly to unblock the stack | |
310 | +- Focus on the specific change in each PR | |
311 | + | |
312 | +**For Authors:** | |
313 | +- Mark early PRs as "Ready for Review" immediately | |
314 | +- Keep later PRs as "Draft" until earlier ones are approved | |
315 | +- Be responsive to feedback | |
316 | + | |
317 | +### π‘ Deployment | |
318 | + | |
319 | +Deploy incrementally: | |
320 | + | |
321 | +```bash | |
322 | +# Deploy PR #123 | |
323 | +β
Database migration - SAFE | |
324 | + | |
325 | +# Deploy PR #124 | |
326 | +β
Service layer - SAFE (no API changes yet) | |
327 | + | |
328 | +# Deploy PR #125 | |
329 | +β
Login UI - COMPLETE FEATURE | |
330 | + | |
331 | +# Each deployment is low-risk! | |
332 | +``` | |
333 | + | |
334 | +--- | |
335 | + | |
336 | +## Troubleshooting | |
337 | + | |
338 | +### "My stack has merge conflicts" | |
339 | + | |
340 | +**Cause:** Base branch (main) changed while your stack was in review. | |
341 | + | |
342 | +**Solution:** | |
343 | +```bash | |
344 | +# Sync the entire stack | |
345 | +och stack sync | |
346 | + | |
347 | +# Or manually rebase each branch | |
348 | +git checkout feature/user-schema | |
349 | +git fetch origin | |
350 | +git rebase origin/main | |
351 | +git push --force-with-lease | |
352 | + | |
353 | +# Then update dependent branches | |
354 | +git checkout feature/auth-service | |
355 | +git rebase feature/user-schema | |
356 | +git push --force-with-lease | |
357 | + | |
358 | +# Repeat for each branch | |
359 | +``` | |
360 | + | |
361 | +### "Auto-rebase failed" | |
362 | + | |
363 | +**Cause:** Conflicting changes in dependent PRs. | |
364 | + | |
365 | +**Solution:** | |
366 | +1. Check PR comments for conflict details | |
367 | +2. Manually resolve conflicts: | |
368 | + ```bash | |
369 | + git checkout feature/auth-service | |
370 | + git rebase feature/user-schema | |
371 | + # Resolve conflicts | |
372 | + git add . | |
373 | + git rebase --continue | |
374 | + git push --force-with-lease | |
375 | + ``` | |
376 | + | |
377 | +### "Can't create stack - PR not found" | |
378 | + | |
379 | +**Cause:** Base PR might not exist or is already merged. | |
380 | + | |
381 | +**Solution:** | |
382 | +```bash | |
383 | +# Check PR status | |
384 | +och pr status 123 | |
385 | + | |
386 | +# Verify branch exists | |
387 | +git branch -a | grep feature/user-schema | |
388 | + | |
389 | +# Recreate if needed | |
390 | +git checkout -b feature/new-base main | |
391 | +``` | |
392 | + | |
393 | +### "Stack order is wrong" | |
394 | + | |
395 | +**Cause:** PRs created in wrong order or dependencies changed. | |
396 | + | |
397 | +**Solution:** | |
398 | +```bash | |
399 | +# Reorder stack | |
400 | +och stack reorder | |
401 | + | |
402 | +# Or manually update dependencies in web UI: | |
403 | +# PR Settings β Dependencies β Change base PR | |
404 | +``` | |
405 | + | |
406 | +### "Too many PRs in my stack" | |
407 | + | |
408 | +**Cause:** Stack grew too large (>5 PRs). | |
409 | + | |
410 | +**Solution:** | |
411 | +1. Merge bottom PRs first | |
412 | +2. Start a new stack for remaining changes | |
413 | +3. Or split into parallel stacks | |
414 | + | |
415 | +--- | |
416 | + | |
417 | +## Advanced: Parallel Stacks | |
418 | + | |
419 | +You can work on multiple independent stacks simultaneously: | |
420 | + | |
421 | +``` | |
422 | +main | |
423 | + βββ Stack A: User Authentication | |
424 | + β βββ PR #123: DB schema | |
425 | + β βββ PR #124: Service layer | |
426 | + β βββ PR #125: Login UI | |
427 | + β | |
428 | + βββ Stack B: Payment Integration | |
429 | + βββ PR #126: Payment models | |
430 | + βββ PR #127: Stripe integration | |
431 | + βββ PR #128: Checkout page | |
432 | +``` | |
433 | + | |
434 | +**CLI:** | |
435 | +```bash | |
436 | +# View all stacks | |
437 | +och stack list | |
438 | + | |
439 | +# Switch between stacks | |
440 | +git checkout feature/auth-stack | |
441 | +git checkout feature/payment-stack | |
442 | +``` | |
443 | + | |
444 | +--- | |
445 | + | |
446 | +## See Also | |
447 | + | |
448 | +- [OCH CLI Reference](../../cli/README.md) | |
449 | +- [Smart Merge Queue](merge-queue.md) | |
450 | +- [CI/CD Integration](ci-cd.md) | |
451 | +- [Tutorial: Your First Stack](../tutorials/your-first-stack.md) | |
452 | + | |
453 | +--- | |
454 | + | |
455 | +## Need Help? | |
456 | + | |
457 | +- π¬ [Discord Community](https://discord.gg/opencodehub) | |
458 | +- π [Report Issues](https://github.com/swadhinbiswas/OpencodeHub/issues) | |
459 | +- π [Full Documentation](/) | |
0 | 460 | |
βΌdocs/index.md
+290-0
| @@ -0,0 +1,290 @@ | ||
1 | +# OpenCodeHub Documentation | |
2 | + | |
3 | +> Everything you need to know about OpenCodeHub - from getting started to advanced features | |
4 | + | |
5 | +Welcome to OpenCodeHub's comprehensive documentation. Whether you're new to the platform or looking to master advanced features, you'll find everything you need here. | |
6 | + | |
7 | +--- | |
8 | + | |
9 | +## π Quick Links | |
10 | + | |
11 | +<div class="quick-links"> | |
12 | + <a href="#getting-started"> | |
13 | + <h3>π― Getting Started</h3> | |
14 | + <p>Install and set up OpenCodeHub in 5 minutes</p> | |
15 | + </a> | |
16 | + | |
17 | + <a href="#features"> | |
18 | + <h3>β¨ Features</h3> | |
19 | + <p>Learn about stacked PRs, AI review, and more</p> | |
20 | + </a> | |
21 | + | |
22 | + <a href="#tutorials"> | |
23 | + <h3>π Tutorials</h3> | |
24 | + <p>Hands-on guides to master OpenCodeHub</p> | |
25 | + </a> | |
26 | + | |
27 | + <a href="#api"> | |
28 | + <h3>π API Reference</h3> | |
29 | + <p>Integrate with OpenCodeHub programmatically</p> | |
30 | + </a> | |
31 | +</div> | |
32 | + | |
33 | +--- | |
34 | + | |
35 | +## π Documentation Sections | |
36 | + | |
37 | +### Getting Started | |
38 | + | |
39 | +New to OpenCodeHub? Start here! | |
40 | + | |
41 | +- **[Installation Guide](getting-started/installation.md)** - Set up OpenCodeHub (Docker or manual) | |
42 | +- **[Quick Start](getting-started/quick-start.md)** - Get up and running in 5 minutes | |
43 | +- **[Create Your First Repository](getting-started/first-repository.md)** - Push your first code | |
44 | + | |
45 | +### Core Features | |
46 | + | |
47 | +Learn about OpenCodeHub's powerful features: | |
48 | + | |
49 | +- **[Stacked Pull Requests](features/stacked-prs.md)** β | |
50 | + Break large changes into reviewable stacks. Ship code faster. | |
51 | + | |
52 | +- **[AI Code Review](features/ai-review.md)** π€ | |
53 | + Get instant feedback from GPT-4 or Claude. Catch bugs automatically. | |
54 | + | |
55 | +- **[Smart Merge Queue](features/merge-queue.md)** π | |
56 | + Automate merging with stack-aware CI optimization. | |
57 | + | |
58 | +- **[Developer Metrics](features/developer-metrics.md)** π | |
59 | + Track velocity, review efficiency, and team performance. | |
60 | + | |
61 | +- **[Slack Integration](features/slack-integration.md)** π¬ | |
62 | + Get actionable notifications in Slack. Review without leaving chat. | |
63 | + | |
64 | +- **[CI/CD Pipelines](features/ci-cd.md)** βοΈ | |
65 | + GitHub Actions-compatible workflows. Build, test, deploy. | |
66 | + | |
67 | +### Hands-On Tutorials | |
68 | + | |
69 | +Step-by-step guides to master OpenCodeHub: | |
70 | + | |
71 | +- **[Your First Stacked PR](tutorials/your-first-stack.md)** π | |
72 | + 15-minute tutorial: Create your first stack of PRs | |
73 | + | |
74 | +- **[AI-Powered Code Reviews](tutorials/ai-powered-reviews.md)** | |
75 | + Set up and use AI review for your projects | |
76 | + | |
77 | +- **[Automated Deployment](tutorials/automated-deployment.md)** | |
78 | + Build a complete CI/CD pipeline from scratch | |
79 | + | |
80 | +### Guides | |
81 | + | |
82 | +Best practices and workflows: | |
83 | + | |
84 | +- **[Team Workflows](guides/team-workflows.md)** - Collaborate effectively | |
85 | +- **[Branch Protection](guides/branch-protection.md)** - Secure your branches | |
86 | +- **[Webhooks](guides/webhooks.md)** - Integrate with external services | |
87 | +- **[Storage Adapters](guides/storage-adapters.md)** - Configure S3, GCS, or local storage | |
88 | + | |
89 | +### API Reference | |
90 | + | |
91 | +Integrate OpenCodeHub into your tools: | |
92 | + | |
93 | +- **[REST API](api/rest-api.md)** - Complete API reference | |
94 | +- **[Webhooks](api/webhooks-api.md)** - Event-driven integrations | |
95 | +- **[Authentication](api/authentication.md)** - API tokens and OAuth | |
96 | + | |
97 | +### Administration | |
98 | + | |
99 | +For system administrators: | |
100 | + | |
101 | +- **[Deployment Guide](administration/deployment.md)** - Production deployment | |
102 | +- **[Configuration Reference](administration/configuration.md)** - All config options | |
103 | +- **[Security Best Practices](administration/security.md)** - Secure your instance | |
104 | +- **[Monitoring](administration/monitoring.md)** - Monitor health and performance | |
105 | + | |
106 | +### Development | |
107 | + | |
108 | +Contributing to OpenCodeHub: | |
109 | + | |
110 | +- **[Architecture](development/architecture.md)** - System design and components | |
111 | +- **[Database Schema](development/database-schema.md)** - Database structure | |
112 | +- **[Contributing Guide](development/contributing.md)** - How to contribute | |
113 | +- **[Testing Guide](development/testing.md)** - Write and run tests | |
114 | + | |
115 | +--- | |
116 | + | |
117 | +## π― Popular Topics | |
118 | + | |
119 | +### Stacked Pull Requests | |
120 | + | |
121 | +The #1 feature that makes OpenCodeHub unique: | |
122 | + | |
123 | +``` | |
124 | +main branch | |
125 | + βββ PR #123: Add database schema β
| |
126 | + βββ PR #124: Add auth service π | |
127 | + βββ PR #125: Add login UI β³ | |
128 | +``` | |
129 | + | |
130 | +- [What are Stacked PRs?](features/stacked-prs.md#why-stacked-prs) | |
131 | +- [Create your first stack](tutorials/your-first-stack.md) | |
132 | +- [CLI reference](../cli/README.md) | |
133 | + | |
134 | +### AI Code Review | |
135 | + | |
136 | +Catch bugs before they reach production: | |
137 | + | |
138 | +```python | |
139 | +# AI Review finds: | |
140 | +# π΄ CRITICAL: SQL injection on line 45 | |
141 | +# π‘ WARNING: N+1 query on line 128 | |
142 | +# π΅ INFO: Consider error handling on line 87 | |
143 | +``` | |
144 | + | |
145 | +- [Setup AI review](features/ai-review.md#setup) | |
146 | +- [Understanding output](features/ai-review.md#understanding-review-output) | |
147 | +- [Cost optimization](features/ai-review.md#cost-considerations) | |
148 | + | |
149 | +### Merge Queue | |
150 | + | |
151 | +Merge automatically, in the right order: | |
152 | + | |
153 | +```bash | |
154 | +och queue add 123 124 125 | |
155 | +# β Queue handles everything: | |
156 | +# - CI runs | |
157 | +# - Rebasing | |
158 | +# - Merging in order | |
159 | +# - Zero manual work | |
160 | +``` | |
161 | + | |
162 | +- [How it works](features/merge-queue.md#how-it-works) | |
163 | +- [Queue configuration](features/merge-queue.md#configuration) | |
164 | +- [Best practices](features/merge-queue.md#best-practices) | |
165 | + | |
166 | +--- | |
167 | + | |
168 | +## π§ Installation | |
169 | + | |
170 | +### Quick Start (Docker) | |
171 | + | |
172 | +```bash | |
173 | +# 1. Download docker-compose.yml | |
174 | +curl -O https://raw.githubusercontent.com/swadhinbiswas/OpencodeHub/main/docker-compose.yml | |
175 | + | |
176 | +# 2. Configure | |
177 | +cp .env.example .env | |
178 | +# Edit .env with your settings | |
179 | + | |
180 | +# 3. Start | |
181 | +docker-compose up -d | |
182 | + | |
183 | +# 4. Create admin user | |
184 | +docker-compose exec app bun run scripts/seed-admin.ts | |
185 | + | |
186 | +# 5. Open | |
187 | +open http://localhost:3000 | |
188 | +``` | |
189 | + | |
190 | +[Full installation guide β](getting-started/installation.md) | |
191 | + | |
192 | +### Requirements | |
193 | + | |
194 | +- **Docker**: 20.10+ (recommended) | |
195 | +- **OR Node.js**: 18+ with Bun or npm | |
196 | +- **Database**: PostgreSQL 14+ or SQLite | |
197 | +- **Storage**: Local, S3, Google Cloud Storage, or Azure Blob | |
198 | + | |
199 | +--- | |
200 | + | |
201 | +## π Learning Paths | |
202 | + | |
203 | +### For Developers | |
204 | + | |
205 | +1. [Quick Start](getting-started/quick-start.md) - Get familiar with the platform | |
206 | +2. [Your First Stack](tutorials/your-first-stack.md) - Learn stacked PRs | |
207 | +3. [Team Workflows](guides/team-workflows.md) - Collaborate effectively | |
208 | + | |
209 | +### For Teams | |
210 | + | |
211 | +1. [Installation](getting-started/installation.md) - Set up your instance | |
212 | +2. [Team Workflows](guides/team-workflows.md) - Define your process | |
213 | +3. [Branch Protection](guides/branch-protection.md) - Secure your code | |
214 | +4. [CI/CD Setup](features/ci-cd.md) - Automate everything | |
215 | + | |
216 | +### For Administrators | |
217 | + | |
218 | +1. [Deployment Guide](administration/deployment.md) - Production setup | |
219 | +2. [Security Best Practices](administration/security.md) - Secure your instance | |
220 | +3. [Monitoring](administration/monitoring.md) - Track health | |
221 | +4. [Configuration](administration/configuration.md) - Tune performance | |
222 | + | |
223 | +--- | |
224 | + | |
225 | +## π¬ Community & Support | |
226 | + | |
227 | +### Get Help | |
228 | + | |
229 | +- **π Documentation** - You're here! | |
230 | +- **π¬ [Discord](https://discord.gg/opencodehub)** - Real-time chat, community support | |
231 | +- **π [GitHub Issues](https://github.com/swadhinbiswas/OpencodeHub/issues)** - Bug reports, feature requests | |
232 | +- **π§ [Email Support](mailto:[email protected])** - Direct support | |
233 | + | |
234 | +### Contributing | |
235 | + | |
236 | +We welcome contributions! | |
237 | + | |
238 | +- **[Contributing Guide](development/contributing.md)** - How to contribute | |
239 | +- **[Good First Issues](https://github.com/swadhinbiswas/OpencodeHub/labels/good-first-issue)** - Easy issues for new contributors | |
240 | +- **[Roadmap](https://github.com/swadhinbiswas/OpencodeHub/projects)** - What's coming next | |
241 | + | |
242 | +--- | |
243 | + | |
244 | +## π Recent Updates | |
245 | + | |
246 | +### December 2024 | |
247 | + | |
248 | +- β¨ Added AI code review with GPT-4 and Claude support | |
249 | +- π Improved merge queue performance (60% faster) | |
250 | +- π New developer metrics dashboard | |
251 | +- π§ 100+ bug fixes and improvements | |
252 | + | |
253 | +[Full changelog β](../CHANGELOG.md) | |
254 | + | |
255 | +--- | |
256 | + | |
257 | +## π Why OpenCodeHub? | |
258 | + | |
259 | +| Feature | GitHub | GitLab | OpenCodeHub | | |
260 | +|---------|--------|--------|-------------| | |
261 | +| Stacked PRs | β | β | β
Native support | | |
262 | +| AI Code Review | β οΈ Copilot only | β | β
GPT-4 & Claude | | |
263 | +| Smart Merge Queue | β οΈ Basic | β οΈ Basic | β
Stack-aware | | |
264 | +| Self-Hosted | β | β
Complex | β
Simple | | |
265 | +| Cost | $$$ | $$$ | Free | | |
266 | + | |
267 | +--- | |
268 | + | |
269 | +## π Search Documentation | |
270 | + | |
271 | +Can't find what you're looking for? | |
272 | + | |
273 | +- Use the search bar above | |
274 | +- Check the [FAQ](#faq) | |
275 | +- Ask in [Discord](https://discord.gg/opencodehub) | |
276 | +- Browse [GitHub Discussions](https://github.com/swadhinbiswas/OpencodeHub/discussions) | |
277 | + | |
278 | +--- | |
279 | + | |
280 | +## π License | |
281 | + | |
282 | +OpenCodeHub is open source under the MIT License. | |
283 | + | |
284 | +[View license β](../LICENSE) | |
285 | + | |
286 | +--- | |
287 | + | |
288 | +**Ready to get started?** | |
289 | + | |
290 | +[Install OpenCodeHub β](getting-started/installation.md) | [Quick Start β](getting-started/quick-start.md) | [Your First Stack β](tutorials/your-first-stack.md) | |
0 | 291 | |
βΌdocs/tutorials/your-first-stack.md
+561-0
| @@ -0,0 +1,561 @@ | ||
1 | +# Tutorial: Your First Stacked PR | |
2 | + | |
3 | +> Learn how to split a large feature into reviewable chunks in 15 minutes | |
4 | + | |
5 | +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. | |
6 | + | |
7 | +## What You'll Learn | |
8 | + | |
9 | +- Create a stack of 3 dependent PRs | |
10 | +- Use the OCH CLI for efficient workflow | |
11 | +- Navigate the review process | |
12 | +- Merge the stack successfully | |
13 | + | |
14 | +**Time:** 15-20 minutes | |
15 | +**Difficulty:** Beginner | |
16 | + | |
17 | +--- | |
18 | + | |
19 | +## Prerequisites | |
20 | + | |
21 | +Before starting, make sure you have: | |
22 | + | |
23 | +- [ ] OpenCodeHub account | |
24 | +- [ ] A repository where you have write access | |
25 | +- [ ] Git installed locally | |
26 | +- [ ] OCH CLI installed (`npm install -g opencodehub-cli`) | |
27 | +- [ ] Basic Git knowledge (branch, commit, push) | |
28 | + | |
29 | +**First time with OCH CLI?** | |
30 | +```bash | |
31 | +# Install | |
32 | +npm install -g opencodehub-cli | |
33 | + | |
34 | +# Login | |
35 | +och auth login | |
36 | +# Enter your email and password when prompted | |
37 | +``` | |
38 | + | |
39 | +--- | |
40 | + | |
41 | +## The Scenario | |
42 | + | |
43 | +You're building a **user authentication system**. Instead of creating one massive PR, you'll split it into 3 logical layers: | |
44 | + | |
45 | +1. **Database Layer** - User table schema | |
46 | +2. **Service Layer** - Authentication logic | |
47 | +3. **API Layer** - Login/logout endpoints | |
48 | + | |
49 | +Each PR builds on the previous one, but can be reviewed independently. | |
50 | + | |
51 | +--- | |
52 | + | |
53 | +## Step 1: Set Up Your Workspace (2 min) | |
54 | + | |
55 | +**1.1. Clone your repository:** | |
56 | + | |
57 | +```bash | |
58 | +cd ~/projects | |
59 | +git clone https://github.com/yourorg/your-repo.git | |
60 | +cd your-repo | |
61 | +``` | |
62 | + | |
63 | +**1.2. Ensure main is up to date:** | |
64 | + | |
65 | +```bash | |
66 | +git checkout main | |
67 | +git pull origin main | |
68 | +``` | |
69 | + | |
70 | +**1.3. Verify OCH CLI is connected:** | |
71 | + | |
72 | +```bash | |
73 | +och auth whoami | |
74 | +# Should show: "Logged in as: [email protected]" | |
75 | +``` | |
76 | + | |
77 | +--- | |
78 | + | |
79 | +## Step 2: Create the Database Layer PR (4 min) | |
80 | + | |
81 | +This is the foundation of your stack. | |
82 | + | |
83 | +**2.1. Create the first branch:** | |
84 | + | |
85 | +```bash | |
86 | +och stack create auth-database | |
87 | +``` | |
88 | + | |
89 | +This creates a new branch `stack/auth-database` from `main`. | |
90 | + | |
91 | +**2.2. Create the migration file:** | |
92 | + | |
93 | +```bash | |
94 | +mkdir -p db/migrations | |
95 | +cat > db/migrations/001_create_users_table.sql << 'EOF' | |
96 | +CREATE TABLE users ( | |
97 | + id SERIAL PRIMARY KEY, | |
98 | + email VARCHAR(255) UNIQUE NOT NULL, | |
99 | + password_hash VARCHAR(255) NOT NULL, | |
100 | + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
101 | + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
102 | +); | |
103 | + | |
104 | +CREATE INDEX idx_users_email ON users(email); | |
105 | +EOF | |
106 | +``` | |
107 | + | |
108 | +**2.3. Commit your changes:** | |
109 | + | |
110 | +```bash | |
111 | +git add db/migrations/ | |
112 | +git commit -m "feat: add users table migration | |
113 | + | |
114 | +- Create users table with email and password_hash | |
115 | +- Add email index for fast lookups | |
116 | +- Set up timestamps" | |
117 | +``` | |
118 | + | |
119 | +**2.4. Submit to create the PR:** | |
120 | + | |
121 | +```bash | |
122 | +och stack submit | |
123 | + | |
124 | +# Output: | |
125 | +# β Pushing stack/auth-database | |
126 | +# β Creating pull request | |
127 | +# | |
128 | +# PR #123 created: feat: add users table migration | |
129 | +# https://git.yourcompany.com/yourorg/your-repo/pulls/123 | |
130 | +``` | |
131 | + | |
132 | +**2.5. Verify in web UI:** | |
133 | + | |
134 | +Open the PR link. You should see: | |
135 | +- Title: "feat: add users table migration" | |
136 | +- Files changed: `db/migrations/001_create_users_table.sql` | |
137 | +- Status: Draft or Ready for Review | |
138 | + | |
139 | +--- | |
140 | + | |
141 | +## Step 3: Create the Service Layer PR (4 min) | |
142 | + | |
143 | +Now, build on top of your first PR. | |
144 | + | |
145 | +**3.1. Create the second stacked branch:** | |
146 | + | |
147 | +```bash | |
148 | +och stack create auth-service | |
149 | +``` | |
150 | + | |
151 | +This creates `stack/auth-service` from your current branch automatically. | |
152 | + | |
153 | +**3.2. Create the auth service:** | |
154 | + | |
155 | +```bash | |
156 | +mkdir -p src/services | |
157 | +cat > src/services/auth.ts << 'EOF' | |
158 | +import bcrypt from 'bcrypt'; | |
159 | +import jwt from 'jsonwebtoken'; | |
160 | +import { db } from '../db'; | |
161 | + | |
162 | +export class AuthService { | |
163 | + async register(email: string, password: string) { | |
164 | + const hash = await bcrypt.hash(password, 10); | |
165 | + | |
166 | + const user = await db.users.create({ | |
167 | + email, | |
168 | + password_hash: hash | |
169 | + }); | |
170 | + | |
171 | + return { id: user.id, email: user.email }; | |
172 | + } | |
173 | + | |
174 | + async login(email: string, password: string) { | |
175 | + const user = await db.users.findByEmail(email); | |
176 | + | |
177 | + if (!user) { | |
178 | + throw new Error('Invalid credentials'); | |
179 | + } | |
180 | + | |
181 | + const valid = await bcrypt.compare(password, user.password_hash); | |
182 | + | |
183 | + if (!valid) { | |
184 | + throw new Error('Invalid credentials'); | |
185 | + } | |
186 | + | |
187 | + const token = jwt.sign( | |
188 | + { userId: user.id, email: user.email }, | |
189 | + process.env.JWT_SECRET!, | |
190 | + { expiresIn: '7d' } | |
191 | + ); | |
192 | + | |
193 | + return { token, user: { id: user.id, email: user.email } }; | |
194 | + } | |
195 | +} | |
196 | +EOF | |
197 | +``` | |
198 | + | |
199 | +**3.3. Commit:** | |
200 | + | |
201 | +```bash | |
202 | +git add src/services/ | |
203 | +git commit -m "feat: implement authentication service | |
204 | + | |
205 | +- Add user registration with bcrypt hashing | |
206 | +- Add login with JWT token generation | |
207 | +- Implement password verification | |
208 | + | |
209 | +Depends on: PR #123 (users table)" | |
210 | +``` | |
211 | + | |
212 | +**3.4. Submit:** | |
213 | + | |
214 | +```bash | |
215 | +och stack submit | |
216 | + | |
217 | +# Output: | |
218 | +# β Pushing stack/auth-service | |
219 | +# β Creating pull request | |
220 | +# β Linking to base PR #123 | |
221 | +# | |
222 | +# PR #124 created: feat: implement authentication service | |
223 | +# Stack: #123 β #124 | |
224 | +# https://git.yourcompany.com/yourorg/your-repo/pulls/124 | |
225 | +``` | |
226 | + | |
227 | +**3.5. Check the stack:** | |
228 | + | |
229 | +```bash | |
230 | +och stack view | |
231 | + | |
232 | +# Output: | |
233 | +# π Current Stack | |
234 | +# ββ main (base) | |
235 | +# ββ #123: auth-database β³ Draft | |
236 | +# ββ #124: auth-service β³ Draft | |
237 | +``` | |
238 | + | |
239 | +Perfect! You now have a stack of 2 PRs. | |
240 | + | |
241 | +--- | |
242 | + | |
243 | +## Step 4: Create the API Layer PR (4 min) | |
244 | + | |
245 | +Final layer: the HTTP endpoints. | |
246 | + | |
247 | +**4.1. Create third branch:** | |
248 | + | |
249 | +```bash | |
250 | +och stack create auth-api | |
251 | +``` | |
252 | + | |
253 | +**4.2. Create API endpoints:** | |
254 | + | |
255 | +```bash | |
256 | +mkdir -p src/api | |
257 | +cat > src/api/auth.ts << 'EOF' | |
258 | +import { Router } from 'express'; | |
259 | +import { AuthService } from '../services/auth'; | |
260 | + | |
261 | +const router = Router(); | |
262 | +const authService = new AuthService(); | |
263 | + | |
264 | +router.post('/register', async (req, res) => { | |
265 | + try { | |
266 | + const { email, password } = req.body; | |
267 | + | |
268 | + // Validation | |
269 | + if (!email || !password) { | |
270 | + return res.status(400).json({ error: 'Email and password required' }); | |
271 | + } | |
272 | + | |
273 | + const user = await authService.register(email, password); | |
274 | + | |
275 | + res.status(201).json({ user }); | |
276 | + } catch (error) { | |
277 | + res.status(500).json({ error: error.message }); | |
278 | + } | |
279 | +}); | |
280 | + | |
281 | +router.post('/login', async (req, res) => { | |
282 | + try { | |
283 | + const { email, password } = req.body; | |
284 | + | |
285 | + const result = await authService.login(email, password); | |
286 | + | |
287 | + res.json(result); | |
288 | + } catch (error) { | |
289 | + res.status(401).json({ error: error.message }); | |
290 | + } | |
291 | +}); | |
292 | + | |
293 | +export default router; | |
294 | +EOF | |
295 | +``` | |
296 | + | |
297 | +**4.3. Commit and submit:** | |
298 | + | |
299 | +```bash | |
300 | +git add src/api/ | |
301 | +git commit -m "feat: add authentication API endpoints | |
302 | + | |
303 | +- POST /register - create new user | |
304 | +- POST /login - authenticate and get token | |
305 | +- Input validation | |
306 | +- Error handling | |
307 | + | |
308 | +Depends on: PR #124 (auth service)" | |
309 | + | |
310 | +och stack submit | |
311 | + | |
312 | +# Output: | |
313 | +# β Pushing stack/auth-api | |
314 | +# β Creating pull request | |
315 | +# β Linking to base PR #124 | |
316 | +# | |
317 | +# PR #125 created: feat: add authentication API endpoints | |
318 | +# Stack: #123 β #124 β #125 | |
319 | +``` | |
320 | + | |
321 | +**4.4. View complete stack:** | |
322 | + | |
323 | +```bash | |
324 | +och stack view | |
325 | + | |
326 | +# Output: | |
327 | +# π Current Stack | |
328 | +# ββ main (base) | |
329 | +# ββ #123: auth-database β³ Draft | |
330 | +# ββ #124: auth-service β³ Draft | |
331 | +# ββ #125: auth-api β³ Draft (current) | |
332 | +``` | |
333 | + | |
334 | +π **Congratulations!** You've created your first stack of 3 PRs! | |
335 | + | |
336 | +--- | |
337 | + | |
338 | +## Step 5: Navigate the Review Process (3 min) | |
339 | + | |
340 | +Now let's get these PRs reviewed and merged. | |
341 | + | |
342 | +**5.1. Mark PRs as ready:** | |
343 | + | |
344 | +```bash | |
345 | +# Mark first PR ready for review | |
346 | +curl -X PATCH https://git.yourcompany.com/api/prs/123 \ | |
347 | + -H "Authorization: Bearer $TOKEN" \ | |
348 | + -d '{"draft": false}' | |
349 | + | |
350 | +# Or via web UI: Click "Ready for Review" on PR #123 | |
351 | +``` | |
352 | + | |
353 | +**5.2. Request reviews:** | |
354 | + | |
355 | +In the web UI for PR #123: | |
356 | +1. Click "Reviewers" β Select team members | |
357 | +2. Add comment: "This is the first PR in a stack of 3. Reviewing these in order would be helpful!" | |
358 | + | |
359 | +**5.3. While waiting, add tests:** | |
360 | + | |
361 | +```bash | |
362 | +# Switch back to first branch | |
363 | +git checkout stack/auth-database | |
364 | + | |
365 | +# Add test file | |
366 | +cat > db/migrations/001_create_users_table.test.ts << 'EOF' | |
367 | +import { test } from 'vitest'; | |
368 | +import { db } from '../db'; | |
369 | + | |
370 | +test('creates users table', async () => { | |
371 | + // Run migration | |
372 | + await db.migrate(); | |
373 | + | |
374 | + // Verify table exists | |
375 | + const tables = await db.raw(` | |
376 | + SELECT table_name | |
377 | + FROM information_schema.tables | |
378 | + WHERE table_name = 'users' | |
379 | + `); | |
380 | + | |
381 | + expect(tables.length).toBe(1); | |
382 | +}); | |
383 | +EOF | |
384 | + | |
385 | +git add . | |
386 | +git commit -m "test: add migration test" | |
387 | +git push | |
388 | + | |
389 | +# PR #123 automatically updates! | |
390 | +``` | |
391 | + | |
392 | +**5.4. Monitor review progress:** | |
393 | + | |
394 | +```bash | |
395 | +# Check PR status | |
396 | +och pr status 123 | |
397 | + | |
398 | +# Output: | |
399 | +# PR #123: feat: add users table migration | |
400 | +# Status: π In Review | |
401 | +# Approvals: 1/1 required | |
402 | +# CI: β
Passing | |
403 | +# Comments: 2 | |
404 | +``` | |
405 | + | |
406 | +--- | |
407 | + | |
408 | +## Step 6: Merge the Stack (2 min) | |
409 | + | |
410 | +Once PR #123 is approved, the magic happens! | |
411 | + | |
412 | +**6.1. Use the merge queue:** | |
413 | + | |
414 | +```bash | |
415 | +# Add all PRs to queue | |
416 | +och queue add 123 124 125 | |
417 | + | |
418 | +# Or add individually as they get approved: | |
419 | +och queue add 123 # Merges first | |
420 | +# PR #124 auto-rebases onto new main | |
421 | +och queue add 124 # Merges second | |
422 | +# PR #125 auto-rebases onto new main | |
423 | +och queue add 125 # Merges third | |
424 | +``` | |
425 | + | |
426 | +**6.2. Watch it merge automatically:** | |
427 | + | |
428 | +```bash | |
429 | +# Monitor queue | |
430 | +och queue watch | |
431 | + | |
432 | +# Output (live updates): | |
433 | +# Position PR Status ETA | |
434 | +# 1 #123 π Running CI 1 min | |
435 | +# 2 #124 β³ Waiting 3 min | |
436 | +# 3 #125 β³ Waiting 5 min | |
437 | +# | |
438 | +# [1 min later] | |
439 | +# β
PR #123 merged! | |
440 | +# π Rebasing PR #124... | |
441 | +# π Running CI for #124... | |
442 | +# | |
443 | +# [2 min later] | |
444 | +# β
PR #124 merged! | |
445 | +# π Rebasing PR #125... | |
446 | +``` | |
447 | + | |
448 | +**6.3. Celebrate! π** | |
449 | + | |
450 | +```bash | |
451 | +# Check final status | |
452 | +git checkout main | |
453 | +git pull | |
454 | + | |
455 | +git log --oneline -3 | |
456 | + | |
457 | +# Output: | |
458 | +# abc123 feat: add authentication API endpoints | |
459 | +# def456 feat: implement authentication service | |
460 | +# ghi789 feat: add users table migration | |
461 | +``` | |
462 | + | |
463 | +All 3 PRs merged! Your auth system is live! | |
464 | + | |
465 | +--- | |
466 | + | |
467 | +## What You've Learned | |
468 | + | |
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 | |
474 | +β
How auto-rebasing works | |
475 | + | |
476 | +## Key Takeaways | |
477 | + | |
478 | +**Stacked PRs are better because:** | |
479 | +- Each PR was ~50-100 lines (easy to review) | |
480 | +- Reviews happened in parallel (faster) | |
481 | +- Each merge was low-risk (incremental) | |
482 | +- Clear history (logical progression) | |
483 | + | |
484 | +**vs. One Large PR:** | |
485 | +- Would be ~200-300 lines | |
486 | +- Single review bottleneck | |
487 | +- High-risk merge | |
488 | +- Messy history | |
489 | + | |
490 | +--- | |
491 | + | |
492 | +## Next Steps | |
493 | + | |
494 | +### Practice More | |
495 | + | |
496 | +Try stacking your next feature: | |
497 | +- π¨ Frontend feature β Split UI, logic, styles | |
498 | +- π§ Backend feature β Split models, services, controllers | |
499 | +- π Data pipeline β Split ingestion, processing, output | |
500 | + | |
501 | +### Advanced Techniques | |
502 | + | |
503 | +Learn more about: | |
504 | +- [Parallel stacks](../features/stacked-prs.md#parallel-stacks) | |
505 | +- [Stack reordering](../features/stacked-prs.md#reordering) | |
506 | +- [AI code review](../features/ai-review.md) on stacks | |
507 | +- [Advanced CLI commands](../../cli/README.md) | |
508 | + | |
509 | +### Join the Community | |
510 | + | |
511 | +- π¬ [Discord](https://discord.gg/opencodehub) - Ask questions | |
512 | +- π [Documentation](/) - Read more guides | |
513 | +- π [GitHub](https://github.com/swadhinbiswas/OpencodeHub) - Report issues | |
514 | + | |
515 | +--- | |
516 | + | |
517 | +## Troubleshooting | |
518 | + | |
519 | +**"och command not found"** | |
520 | + | |
521 | +```bash | |
522 | +# Install CLI | |
523 | +npm install -g opencodehub-cli | |
524 | + | |
525 | +# Or use npx | |
526 | +npx opencodehub-cli stack create auth-database | |
527 | +``` | |
528 | + | |
529 | +**"Permission denied"** | |
530 | + | |
531 | +```bash | |
532 | +# Login again | |
533 | +och auth login | |
534 | + | |
535 | +# Verify permissions | |
536 | +och auth whoami | |
537 | +``` | |
538 | + | |
539 | +**"Can't create stack - conflicts"** | |
540 | + | |
541 | +```bash | |
542 | +# Make sure main is up to date | |
543 | +git checkout main | |
544 | +git pull | |
545 | + | |
546 | +# Start fresh | |
547 | +git checkout -b stack/auth-database main | |
548 | +``` | |
549 | + | |
550 | +**"PR not linking to stack"** | |
551 | + | |
552 | +```bash | |
553 | +# Manually link via web UI: | |
554 | +# Go to PR β Settings β Dependencies β Select base PR | |
555 | +``` | |
556 | + | |
557 | +--- | |
558 | + | |
559 | +**Congratulations on completing your first stacked PR workflow!** π | |
560 | + | |
561 | +You're now ready to ship code faster with OpenCodeHub. | |
0 | 562 | |
βΌdrizzle.config.ts
+6-1
| @@ -5,10 +5,12 @@ const driver = process.env.DATABASE_DRIVER || "sqlite"; | ||
5 | 5 | const url = process.env.DATABASE_URL || "./data/opencodehub.db"; |
6 | 6 | |
7 | 7 | // Map driver to Drizzle dialect |
8 | -const dialectMap: Record<string, "sqlite" | "postgresql" | "mysql"> = { | |
8 | +const dialectMap: Record<string, "sqlite" | "postgresql" | "mysql" | "turso"> = { | |
9 | 9 | sqlite: "sqlite", |
10 | 10 | postgres: "postgresql", |
11 | 11 | mysql: "mysql", |
12 | + libsql: "turso", | |
13 | + turso: "turso", | |
12 | 14 | }; |
13 | 15 | |
14 | 16 | const dialect = dialectMap[driver] || "sqlite"; |
| @@ -19,6 +21,9 @@ function getCredentials() { | ||
19 | 21 | case "postgres": |
20 | 22 | case "mysql": |
21 | 23 | return { url }; |
24 | + case "libsql": | |
25 | + case "turso": | |
26 | + return { url, authToken: process.env.DATABASE_AUTH_TOKEN }; | |
22 | 27 | case "sqlite": |
23 | 28 | default: |
24 | 29 | return { url }; |
25 | 30 | |
βΌpackage.json
+11-1
| @@ -3,6 +3,14 @@ | ||
3 | 3 | "version": "1.0.0", |
4 | 4 | "description": "A fully self-hosted, open-source GitHub/GitLab alternative with SSH git hosting, pipelines/actions, and advanced UI", |
5 | 5 | "type": "module", |
6 | + "repository": { | |
7 | + "type": "git", | |
8 | + "url": "https://github.com/swadhinbiswas/OpencodeHub.git" | |
9 | + }, | |
10 | + "homepage": "https://github.com/swadhinbiswas/OpencodeHub#readme", | |
11 | + "bugs": { | |
12 | + "url": "https://github.com/swadhinbiswas/OpencodeHub/issues" | |
13 | + }, | |
6 | 14 | "scripts": { |
7 | 15 | "dev": "astro dev", |
8 | 16 | "start": "astro dev", |
| @@ -23,6 +31,7 @@ | ||
23 | 31 | "@astrojs/tailwind": "^5.1.0", |
24 | 32 | "@aws-sdk/client-s3": "^3.948.0", |
25 | 33 | "@aws-sdk/s3-request-presigner": "^3.948.0", |
34 | + "@libsql/client": "^0.15.15", | |
26 | 35 | "@monaco-editor/react": "^4.6.0", |
27 | 36 | "@opentelemetry/auto-instrumentations-node": "^0.67.2", |
28 | 37 | "@opentelemetry/exporter-trace-otlp-http": "^0.208.0", |
| @@ -53,6 +62,7 @@ | ||
53 | 62 | "@radix-ui/react-toggle-group": "^1.1.0", |
54 | 63 | "@radix-ui/react-tooltip": "^1.1.2", |
55 | 64 | "@types/nodemailer": "^7.0.4", |
65 | + "@upstash/redis": "^1.36.0", | |
56 | 66 | "astro": "^4.11.0", |
57 | 67 | "bcryptjs": "^2.4.3", |
58 | 68 | "better-sqlite3": "^11.1.2", |
| @@ -125,4 +135,4 @@ | ||
125 | 135 | "engines": { |
126 | 136 | "node": ">=18.0.0" |
127 | 137 | } |
128 | -} | |
138 | +} | |
129 | 139 | |
βΌsrc/components/home/FeatureDemosSection.tsx
+427-0
| @@ -0,0 +1,427 @@ | ||
1 | +"use client"; | |
2 | +import { useState } from "react"; | |
3 | +import { motion, AnimatePresence } from "framer-motion"; | |
4 | +import { Layers, Bot, GitMerge, BarChart3, MessageSquare, Terminal as TerminalIcon, Check, X, Clock, ArrowRight } from "lucide-react"; | |
5 | + | |
6 | +const demos = [ | |
7 | + { | |
8 | + id: "stacked-prs", | |
9 | + title: "Stacked Pull Requests", | |
10 | + icon: Layers, | |
11 | + color: "from-blue-500 to-cyan-500", | |
12 | + description: "Break large features into reviewable chunks", | |
13 | + demo: { | |
14 | + type: "stack-visualization", | |
15 | + items: [ | |
16 | + { id: 1, title: "Add database schema", status: "merged", pr: "#123" }, | |
17 | + { id: 2, title: "Add authentication service", status: "approved", pr: "#124", dependsOn: 1 }, | |
18 | + { id: 3, title: "Add login UI", status: "review", pr: "#125", dependsOn: 2 }, | |
19 | + ], | |
20 | + }, | |
21 | + }, | |
22 | + { | |
23 | + id: "ai-review", | |
24 | + title: "AI Code Review", | |
25 | + icon: Bot, | |
26 | + color: "from-purple-500 to-pink-500", | |
27 | + description: "Get instant feedback from AI reviewers", | |
28 | + demo: { | |
29 | + type: "ai-feedback", | |
30 | + issues: [ | |
31 | + { severity: "critical", line: 45, message: "SQL injection vulnerability detected", fix: "Use parameterized queries" }, | |
32 | + { severity: "warning", line: 128, message: "N+1 query in loop", fix: "Use batch loading" }, | |
33 | + { severity: "info", line: 87, message: "Consider adding error handling", fix: "Wrap in try-catch" }, | |
34 | + ], | |
35 | + }, | |
36 | + }, | |
37 | + { | |
38 | + id: "merge-queue", | |
39 | + title: "Smart Merge Queue", | |
40 | + icon: GitMerge, | |
41 | + color: "from-green-500 to-emerald-500", | |
42 | + description: "Stack-aware merging with CI optimization", | |
43 | + demo: { | |
44 | + type: "queue-status", | |
45 | + queue: [ | |
46 | + { pr: "#125", title: "Login UI", status: "running", ci: 75 }, | |
47 | + { pr: "#124", title: "Auth service", status: "waiting", dependsOn: "#123" }, | |
48 | + { pr: "#123", title: "Database schema", status: "ready" }, | |
49 | + ], | |
50 | + }, | |
51 | + }, | |
52 | + { | |
53 | + id: "metrics", | |
54 | + title: "Developer Metrics", | |
55 | + icon: BarChart3, | |
56 | + color: "from-orange-500 to-red-500", | |
57 | + description: "Track velocity and review efficiency", | |
58 | + demo: { | |
59 | + type: "metrics-dashboard", | |
60 | + data: { | |
61 | + prsMerged: { value: 24, change: "+12%" }, | |
62 | + avgTimeToMerge: { value: "8.2h", change: "-3h" }, | |
63 | + stackUsage: { value: "65%", change: "+15%" }, | |
64 | + reviewTime: { value: "22min", change: "-5min" }, | |
65 | + }, | |
66 | + }, | |
67 | + }, | |
68 | + { | |
69 | + id: "slack", | |
70 | + title: "Slack Notifications", | |
71 | + icon: MessageSquare, | |
72 | + color: "from-indigo-500 to-blue-500", | |
73 | + description: "Actionable notifications in Slack", | |
74 | + demo: { | |
75 | + type: "slack-message", | |
76 | + message: { | |
77 | + author: "swadhin", | |
78 | + action: "opened", | |
79 | + pr: "#125", | |
80 | + title: "Add login UI", | |
81 | + stack: ["#123", "#124", "#125"], | |
82 | + stats: { files: 32, additions: 420, deletions: 18 }, | |
83 | + }, | |
84 | + }, | |
85 | + }, | |
86 | + { | |
87 | + id: "cli", | |
88 | + title: "OCH CLI", | |
89 | + icon: TerminalIcon, | |
90 | + color: "from-cyan-500 to-teal-500", | |
91 | + description: "Powerful terminal workflow", | |
92 | + demo: { | |
93 | + type: "terminal", | |
94 | + commands: [ | |
95 | + { cmd: "och stack create feature-name", output: "β Created branch stack/feature-name" }, | |
96 | + { cmd: "och stack submit", output: "β Pushed and created PR #125" }, | |
97 | + { cmd: "och queue add 125", output: "β Added to merge queue (position 1)" }, | |
98 | + ], | |
99 | + }, | |
100 | + }, | |
101 | +]; | |
102 | + | |
103 | +function StackVisualization({ items }: any) { | |
104 | + return ( | |
105 | + <div className="space-y-2"> | |
106 | + <div className="text-xs text-muted-foreground pl-4">main (base)</div> | |
107 | + {items.map((item: any, idx: number) => ( | |
108 | + <motion.div | |
109 | + key={item.id} | |
110 | + initial={{ opacity: 0, x: -20 }} | |
111 | + animate={{ opacity: 1, x: 0 }} | |
112 | + transition={{ delay: idx * 0.1 }} | |
113 | + className="flex items-center gap-3"> | |
114 | + <div className="w-8 flex flex-col items-center"> | |
115 | + <div className="h-4 border-l-2 border-muted" /> | |
116 | + <div className="w-2 h-2 rounded-full bg-primary" /> | |
117 | + {idx < items.length - 1 && <div className="h-12 border-l-2 border-dashed border-muted" />} | |
118 | + </div> | |
119 | + <div className="flex-1 p-3 rounded-lg border border-white/5 bg-card/50"> | |
120 | + <div className="flex items-center justify-between mb-1"> | |
121 | + <span className="text-sm font-medium">{item.pr}: {item.title}</span> | |
122 | + <span | |
123 | + className={`text-xs px-2 py-0.5 rounded-full ${item.status === "merged" | |
124 | + ? "bg-green-500/20 text-green-400" | |
125 | + : item.status === "approved" | |
126 | + ? "bg-blue-500/20 text-blue-400" | |
127 | + : "bg-yellow-500/20 text-yellow-400" | |
128 | + }`}> | |
129 | + {item.status} | |
130 | + </span> | |
131 | + </div> | |
132 | + {item.dependsOn && ( | |
133 | + <div className="text-xs text-muted-foreground"> | |
134 | + Depends on PR #{items.find((i: any) => i.id === item.dependsOn)?.pr.slice(1)} | |
135 | + </div> | |
136 | + )} | |
137 | + </div> | |
138 | + </motion.div> | |
139 | + ))} | |
140 | + </div> | |
141 | + ); | |
142 | +} | |
143 | + | |
144 | +function AIFeedback({ issues }: any) { | |
145 | + const severityConfig: Record<string, { bg: string; text: string; icon: any }> = { | |
146 | + critical: { bg: "bg-red-500/20", text: "text-red-400", icon: X }, | |
147 | + warning: { bg: "bg-yellow-500/20", text: "text-yellow-400", icon: Clock }, | |
148 | + info: { bg: "bg-blue-500/20", text: "text-blue-400", icon: Check }, | |
149 | + }; | |
150 | + | |
151 | + return ( | |
152 | + <div className="space-y-3"> | |
153 | + {issues.map((issue: any, idx: number) => { | |
154 | + const config = severityConfig[issue.severity]; | |
155 | + const Icon = config.icon; | |
156 | + return ( | |
157 | + <motion.div | |
158 | + key={idx} | |
159 | + initial={{ opacity: 0, y: 20 }} | |
160 | + animate={{ opacity: 1, y: 0 }} | |
161 | + transition={{ delay: idx * 0.15 }} | |
162 | + className="p-3 rounded-lg border border-white/5 bg-card/50 space-y-2"> | |
163 | + <div className="flex items-start gap-3"> | |
164 | + <div className={`p-1.5 rounded ${config.bg}`}> | |
165 | + <Icon className={`h-4 w-4 ${config.text}`} /> | |
166 | + </div> | |
167 | + <div className="flex-1"> | |
168 | + <div className="flex items-center gap-2 mb-1"> | |
169 | + <span className="text-xs font-mono text-muted-foreground">Line {issue.line}</span> | |
170 | + <span className={`text-xs uppercase font-semibold ${config.text}`}> | |
171 | + {issue.severity} | |
172 | + </span> | |
173 | + </div> | |
174 | + <p className="text-sm">{issue.message}</p> | |
175 | + <p className="text-xs text-muted-foreground mt-1">π‘ {issue.fix}</p> | |
176 | + </div> | |
177 | + </div> | |
178 | + </motion.div> | |
179 | + ); | |
180 | + })} | |
181 | + </div> | |
182 | + ); | |
183 | +} | |
184 | + | |
185 | +function QueueStatus({ queue }: any) { | |
186 | + return ( | |
187 | + <div className="space-y-2"> | |
188 | + {queue.map((item: any, idx: number) => ( | |
189 | + <motion.div | |
190 | + key={idx} | |
191 | + initial={{ opacity: 0, x: -20 }} | |
192 | + animate={{ opacity: 1, x: 0 }} | |
193 | + transition={{ delay: idx * 0.1 }} | |
194 | + className="p-3 rounded-lg border border-white/5 bg-card/50"> | |
195 | + <div className="flex items-center justify-between mb-2"> | |
196 | + <div> | |
197 | + <span className="text-sm font-medium"> | |
198 | + {item.pr}: {item.title} | |
199 | + </span> | |
200 | + </div> | |
201 | + <span className="text-xs text-muted-foreground">Position {idx + 1}</span> | |
202 | + </div> | |
203 | + {item.status === "running" && item.ci !== undefined && ( | |
204 | + <div> | |
205 | + <div className="flex items-center justify-between text-xs mb-1"> | |
206 | + <span className="text-muted-foreground">β³ Running CI</span> | |
207 | + <span className="text-primary">{item.ci}%</span> | |
208 | + </div> | |
209 | + <div className="h-1.5 bg-background rounded-full overflow-hidden"> | |
210 | + <motion.div | |
211 | + initial={{ width: 0 }} | |
212 | + animate={{ width: `${item.ci}%` }} | |
213 | + transition={{ duration: 1.5 }} | |
214 | + className="h-full bg-primary" | |
215 | + /> | |
216 | + </div> | |
217 | + </div> | |
218 | + )} | |
219 | + {item.dependsOn && ( | |
220 | + <div className="text-xs text-muted-foreground mt-1">βΈοΈ Waiting for {item.dependsOn}</div> | |
221 | + )} | |
222 | + {item.status === "ready" && <div className="text-xs text-green-400 mt-1">β
Ready to merge</div>} | |
223 | + </motion.div> | |
224 | + ))} | |
225 | + </div> | |
226 | + ); | |
227 | +} | |
228 | + | |
229 | +function MetricsDashboard({ data }: any) { | |
230 | + const metrics = [ | |
231 | + { label: "PRs Merged", key: "prsMerged" }, | |
232 | + { label: "Avg Time to Merge", key: "avgTimeToMerge" }, | |
233 | + { label: "Stack Usage", key: "stackUsage" }, | |
234 | + { label: "Review Time", key: "reviewTime" }, | |
235 | + ]; | |
236 | + | |
237 | + return ( | |
238 | + <div className="grid grid-cols-2 gap-3"> | |
239 | + {metrics.map((metric, idx) => ( | |
240 | + <motion.div | |
241 | + key={metric.key} | |
242 | + initial={{ opacity: 0, scale: 0.9 }} | |
243 | + animate={{ opacity: 1, scale: 1 }} | |
244 | + transition={{ delay: idx * 0.1 }} | |
245 | + className="p-4 rounded-lg border border-white/5 bg-card/50"> | |
246 | + <div className="text-xs text-muted-foreground mb-2">{metric.label}</div> | |
247 | + <div className="text-2xl font-bold">{data[metric.key].value}</div> | |
248 | + <div className="text-xs text-green-400 mt-1">{data[metric.key].change}</div> | |
249 | + </motion.div> | |
250 | + ))} | |
251 | + </div> | |
252 | + ); | |
253 | +} | |
254 | + | |
255 | +function SlackMessage({ message }: any) { | |
256 | + return ( | |
257 | + <motion.div | |
258 | + initial={{ opacity: 0, y: 20 }} | |
259 | + animate={{ opacity: 1, y: 0 }} | |
260 | + className="p-4 rounded-lg border border-white/5 bg-card/50 space-y-3"> | |
261 | + <div className="flex items-start gap-3"> | |
262 | + <div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center text-xs font-medium"> | |
263 | + {message.author[0].toUpperCase()} | |
264 | + </div> | |
265 | + <div className="flex-1"> | |
266 | + <div className="text-sm"> | |
267 | + <span className="font-medium">{message.author}</span> <span className="text-muted-foreground">{message.action}</span>{" "} | |
268 | + <span className="font-medium"> | |
269 | + {message.pr}: {message.title} | |
270 | + </span> | |
271 | + </div> | |
272 | + <div className="text-xs text-muted-foreground mt-1"> | |
273 | + Stack: {message.stack.join(" β ")} β’ {message.stats.files} files β’ +{message.stats.additions}/- | |
274 | + {message.stats.deletions} | |
275 | + </div> | |
276 | + </div> | |
277 | + </div> | |
278 | + <div className="flex gap-2"> | |
279 | + {["Approve", "Request Changes", "View PR", "Add to Queue"].map((action) => ( | |
280 | + <button | |
281 | + key={action} | |
282 | + className="px-3 py-1.5 text-xs rounded border border-white/10 bg-white/5 hover:bg-white/10 transition-colors"> | |
283 | + {action} | |
284 | + </button> | |
285 | + ))} | |
286 | + </div> | |
287 | + </motion.div> | |
288 | + ); | |
289 | +} | |
290 | + | |
291 | +function Terminal({ commands }: any) { | |
292 | + const [currentIndex, setCurrentIndex] = useState(0); | |
293 | + | |
294 | + if (!commands || commands.length === 0) { | |
295 | + return null; | |
296 | + } | |
297 | + | |
298 | + return ( | |
299 | + <div className="p-4 rounded-lg bg-black/80 border border-white/10 font-mono text-sm"> | |
300 | + <div className="flex items-center gap-2 mb-3"> | |
301 | + <div className="flex gap-1.5"> | |
302 | + <div className="w-3 h-3 rounded-full bg-red-500" /> | |
303 | + <div className="w-3 h-3 rounded-full bg-yellow-500" /> | |
304 | + <div className="w-3 h-3 rounded-full bg-green-500" /> | |
305 | + </div> | |
306 | + <span className="text-xs text-muted-foreground">zsh</span> | |
307 | + </div> | |
308 | + <AnimatePresence mode="wait"> | |
309 | + {commands.slice(0, currentIndex + 1).map((cmd: any, idx: number) => ( | |
310 | + <motion.div | |
311 | + key={idx} | |
312 | + initial={{ opacity: 0 }} | |
313 | + animate={{ opacity: 1 }} | |
314 | + className="space-y-1 mb-2"> | |
315 | + <div className="text-primary"> | |
316 | + <span className="text-green-400">$</span> {cmd.cmd} | |
317 | + </div> | |
318 | + <div className="text-muted-foreground text-xs">{cmd.output}</div> | |
319 | + </motion.div> | |
320 | + ))} | |
321 | + </AnimatePresence> | |
322 | + {currentIndex < commands.length - 1 && ( | |
323 | + <button | |
324 | + onClick={() => setCurrentIndex((i) => i + 1)} | |
325 | + className="text-xs text-primary hover:text-primary/80 mt-2"> | |
326 | + β Next command | |
327 | + </button> | |
328 | + )} | |
329 | + </div> | |
330 | + ); | |
331 | +} | |
332 | + | |
333 | +export default function FeatureDemosSection() { | |
334 | + const [activeDemo, setActiveDemo] = useState(demos[0].id); | |
335 | + | |
336 | + const currentDemo = demos.find((d) => d.id === activeDemo)!; | |
337 | + | |
338 | + return ( | |
339 | + <section className="container py-20"> | |
340 | + <motion.div | |
341 | + initial={{ opacity: 0, y: 20 }} | |
342 | + whileInView={{ opacity: 1, y: 0 }} | |
343 | + viewport={{ once: true }} | |
344 | + className="text-center mb-12"> | |
345 | + <h2 className="text-3xl md:text-4xl font-bold mb-4"> | |
346 | + See It in <span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-purple-400">Action</span> | |
347 | + </h2> | |
348 | + <p className="text-muted-foreground max-w-2xl mx-auto"> | |
349 | + Interactive demonstrations of our advanced features. Click each feature to see how it works. | |
350 | + </p> | |
351 | + </motion.div> | |
352 | + | |
353 | + {/* Feature Tabs */} | |
354 | + <div className="flex flex-wrap justify-center gap-3 mb-8"> | |
355 | + {demos.map((demo) => { | |
356 | + const Icon = demo.icon; | |
357 | + const isActive = activeDemo === demo.id; | |
358 | + return ( | |
359 | + <button | |
360 | + key={demo.id} | |
361 | + onClick={() => setActiveDemo(demo.id)} | |
362 | + className={`group relative px-4 py-3 rounded-lg border transition-all ${isActive | |
363 | + ? "border-primary/50 bg-primary/10" | |
364 | + : "border-white/5 bg-card/30 hover:border-white/20" | |
365 | + }`}> | |
366 | + <div className="flex items-center gap-2"> | |
367 | + <Icon className={`h-4 w-4 ${isActive ? "text-primary" : "text-muted-foreground"}`} /> | |
368 | + <span className={`text-sm font-medium ${isActive ? "text-foreground" : "text-muted-foreground"}`}> | |
369 | + {demo.title} | |
370 | + </span> | |
371 | + </div> | |
372 | + {isActive && ( | |
373 | + <motion.div | |
374 | + layoutId="activeTab" | |
375 | + className="absolute inset-0 rounded-lg border border-primary bg-primary/5 -z-10" | |
376 | + transition={{ type: "spring", duration: 0.5 }} | |
377 | + /> | |
378 | + )} | |
379 | + </button> | |
380 | + ); | |
381 | + })} | |
382 | + </div> | |
383 | + | |
384 | + {/* Demo Display */} | |
385 | + <AnimatePresence mode="wait"> | |
386 | + <motion.div | |
387 | + key={activeDemo} | |
388 | + initial={{ opacity: 0, y: 20 }} | |
389 | + animate={{ opacity: 1, y: 0 }} | |
390 | + exit={{ opacity: 0, y: -20 }} | |
391 | + transition={{ duration: 0.3 }} | |
392 | + className="max-w-4xl mx-auto"> | |
393 | + <div className="grid md:grid-cols-2 gap-8 items-center"> | |
394 | + {/* Description */} | |
395 | + <div> | |
396 | + <div className={`inline-flex p-3 rounded-xl bg-gradient-to-br ${currentDemo.color} mb-4`}> | |
397 | + <currentDemo.icon className="h-8 w-8 text-white" /> | |
398 | + </div> | |
399 | + <h3 className="text-2xl font-bold mb-2">{currentDemo.title}</h3> | |
400 | + <p className="text-muted-foreground mb-6">{currentDemo.description}</p> | |
401 | + <a | |
402 | + href="/docs" | |
403 | + className="inline-flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"> | |
404 | + Learn more | |
405 | + <ArrowRight className="h-4 w-4" /> | |
406 | + </a> | |
407 | + </div> | |
408 | + | |
409 | + {/* Demo Component */} | |
410 | + <div className="rounded-xl border border-white/10 bg-card/50 backdrop-blur-sm p-6"> | |
411 | + {currentDemo.demo.type === "stack-visualization" && ( | |
412 | + <StackVisualization items={currentDemo.demo.items} /> | |
413 | + )} | |
414 | + {currentDemo.demo.type === "ai-feedback" && <AIFeedback issues={currentDemo.demo.issues} />} | |
415 | + {currentDemo.demo.type === "queue-status" && <QueueStatus queue={currentDemo.demo.queue} />} | |
416 | + {currentDemo.demo.type === "metrics-dashboard" && ( | |
417 | + <MetricsDashboard data={currentDemo.demo.data} /> | |
418 | + )} | |
419 | + {currentDemo.demo.type === "slack-message" && <SlackMessage message={currentDemo.demo.message} />} | |
420 | + {currentDemo.demo.type === "terminal" && <Terminal commands={currentDemo.demo.commands} />} | |
421 | + </div> | |
422 | + </div> | |
423 | + </motion.div> | |
424 | + </AnimatePresence> | |
425 | + </section> | |
426 | + ); | |
427 | +} | |
0 | 428 | |
βΌsrc/db/index.ts
+22-4
| @@ -5,15 +5,18 @@ | ||
5 | 5 | |
6 | 6 | import Database from "better-sqlite3"; |
7 | 7 | import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; |
8 | +import { drizzle as drizzleLibSQL, LibSQLDatabase } from "drizzle-orm/libsql"; | |
9 | +import { createClient } from "@libsql/client"; | |
8 | 10 | import { existsSync, mkdirSync } from "fs"; |
9 | 11 | import { dirname } from "path"; |
10 | 12 | import { logger } from "@/lib/logger"; |
11 | 13 | import * as schema from "./schema"; |
12 | 14 | |
13 | 15 | // Database driver types |
14 | -export type DatabaseDriver = "sqlite" | "postgres" | "mysql"; | |
16 | +export type DatabaseDriver = "sqlite" | "postgres" | "mysql" | "libsql" | "turso"; | |
15 | 17 | |
16 | -let db: BetterSQLite3Database<typeof schema> | null = null; | |
18 | +let db: BetterSQLite3Database<typeof schema> | LibSQLDatabase<typeof schema> | null = | |
19 | + null; | |
17 | 20 | |
18 | 21 | /** |
19 | 22 | * Get database configuration from environment |
| @@ -27,11 +30,24 @@ function getDbConfig(): { driver: DatabaseDriver; url: string } { | ||
27 | 30 | /** |
28 | 31 | * Get or create database connection |
29 | 32 | */ |
30 | -export function getDatabase(): BetterSQLite3Database<typeof schema> { | |
33 | +export function getDatabase(): | |
34 | + | BetterSQLite3Database<typeof schema> | |
35 | + | LibSQLDatabase<typeof schema> { | |
31 | 36 | if (db) return db; |
32 | 37 | |
33 | 38 | const { driver, url } = getDbConfig(); |
34 | 39 | |
40 | + // Support for Turso/LibSQL | |
41 | + if (driver === "libsql" || driver === "turso") { | |
42 | + const client = createClient({ | |
43 | + url, | |
44 | + authToken: process.env.DATABASE_AUTH_TOKEN, | |
45 | + }); | |
46 | + db = drizzleLibSQL(client, { schema }); | |
47 | + logger.info({ driver: "libsql", url }, "Database connected (LibSQL)"); | |
48 | + return db; | |
49 | + } | |
50 | + | |
35 | 51 | // Currently only SQLite is fully supported |
36 | 52 | // PostgreSQL and MySQL schemas need separate implementations |
37 | 53 | if (driver !== "sqlite") { |
| @@ -86,4 +102,6 @@ export function closeDatabase(): void { | ||
86 | 102 | |
87 | 103 | // Export schema and types |
88 | 104 | export { schema }; |
89 | -export type Database = BetterSQLite3Database<typeof schema>; | |
105 | +export type Database = | |
106 | + | BetterSQLite3Database<typeof schema> | |
107 | + | LibSQLDatabase<typeof schema>; | |
90 | 108 | |
βΌsrc/db/schema/activity.ts
+1-11
| @@ -5,7 +5,7 @@ | ||
5 | 5 | |
6 | 6 | import { relations } from "drizzle-orm"; |
7 | 7 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; |
8 | -import { organizations } from "./organizations"; | |
8 | +// import { organizations } from "./organizations"; // Removed to prevent circular dependency | |
9 | 9 | import { repositories } from "./repositories"; |
10 | 10 | import { users } from "./users"; |
11 | 11 | |
| @@ -17,7 +17,6 @@ export const activities = sqliteTable("activities", { | ||
17 | 17 | repositoryId: text("repository_id").references(() => repositories.id, { |
18 | 18 | onDelete: "cascade", |
19 | 19 | }), |
20 | - organizationId: text("organization_id").references(() => organizations.id), | |
21 | 20 | type: text("type").notNull(), // push, create_repo, create_branch, open_issue, open_pr, comment, star, fork, etc. |
22 | 21 | action: text("action").notNull(), // created, updated, deleted, opened, closed, merged, etc. |
23 | 22 | refType: text("ref_type"), // branch, tag |
| @@ -54,7 +53,6 @@ export const notifications = sqliteTable("notifications", { | ||
54 | 53 | export const auditLogs = sqliteTable("audit_logs", { |
55 | 54 | id: text("id").primaryKey(), |
56 | 55 | userId: text("user_id").references(() => users.id), |
57 | - organizationId: text("organization_id").references(() => organizations.id), | |
58 | 56 | repositoryId: text("repository_id").references(() => repositories.id), |
59 | 57 | action: text("action").notNull(), |
60 | 58 | actorType: text("actor_type").notNull().default("user"), // user, system, oauth_app |
| @@ -78,10 +76,6 @@ export const activitiesRelations = relations(activities, ({ one }) => ({ | ||
78 | 76 | fields: [activities.repositoryId], |
79 | 77 | references: [repositories.id], |
80 | 78 | }), |
81 | - organization: one(organizations, { | |
82 | - fields: [activities.organizationId], | |
83 | - references: [organizations.id], | |
84 | - }), | |
85 | 79 | })); |
86 | 80 | |
87 | 81 | export const notificationsRelations = relations(notifications, ({ one }) => ({ |
| @@ -104,10 +98,6 @@ export const auditLogsRelations = relations(auditLogs, ({ one }) => ({ | ||
104 | 98 | fields: [auditLogs.userId], |
105 | 99 | references: [users.id], |
106 | 100 | }), |
107 | - organization: one(organizations, { | |
108 | - fields: [auditLogs.organizationId], | |
109 | - references: [organizations.id], | |
110 | - }), | |
111 | 101 | repository: one(repositories, { |
112 | 102 | fields: [auditLogs.repositoryId], |
113 | 103 | references: [repositories.id], |
114 | 104 | |
βΌsrc/db/schema/developer-metrics.ts
+4-5
| @@ -1,14 +1,14 @@ | ||
1 | 1 | /** |
2 | - * Developer Metrics Schema | |
3 | - * Track PR velocity, review efficiency, and team productivity | |
2 | + * Developer Metrics Schema - Drizzle ORM | |
3 | + * Tracks PR metrics, review efficiency, and stack analytics | |
4 | 4 | */ |
5 | 5 | |
6 | 6 | import { relations } from "drizzle-orm"; |
7 | -import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; | |
7 | +import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core"; | |
8 | 8 | import { pullRequests } from "./pull-requests"; |
9 | 9 | import { repositories } from "./repositories"; |
10 | 10 | import { users } from "./users"; |
11 | -import { organizations } from "./organizations"; | |
11 | + | |
12 | 12 | |
13 | 13 | // Per-PR metrics |
14 | 14 | export const prMetrics = sqliteTable("pr_metrics", { |
| @@ -62,7 +62,6 @@ export const reviewMetrics = sqliteTable("review_metrics", { | ||
62 | 62 | .notNull() |
63 | 63 | .references(() => users.id, { onDelete: "cascade" }), |
64 | 64 | repositoryId: text("repository_id").references(() => repositories.id, { onDelete: "cascade" }), |
65 | - organizationId: text("organization_id").references(() => organizations.id), | |
66 | 65 | |
67 | 66 | // Time period |
68 | 67 | weekOf: text("week_of").notNull(), // ISO week: "2024-W52" |
69 | 68 | |
βΌsrc/db/schema/index.ts
+2-2
| @@ -21,8 +21,8 @@ export * from "./workflows"; | ||
21 | 21 | // Wiki |
22 | 22 | export * from "./wiki"; |
23 | 23 | |
24 | -// Organizations and teams | |
25 | -export * from "./organizations"; | |
24 | +// Organizations and teams - TEMPORARILY DISABLED DUE TO CIRCULAR DEPENDENCY | |
25 | +// export * from "./organizations"; | |
26 | 26 | |
27 | 27 | // Activity and notifications |
28 | 28 | export * from "./activity"; |
29 | 29 | |
βΌsrc/db/schema/issues.ts
+1-5
| @@ -21,7 +21,7 @@ export const issues = sqliteTable("issues", { | ||
21 | 21 | .notNull() |
22 | 22 | .references(() => users.id), |
23 | 23 | assigneeId: text("assignee_id").references(() => users.id), |
24 | - milestoneId: text("milestone_id").references(() => milestones.id), | |
24 | + milestoneId: text("milestone_id"), // Reference added later in relations | |
25 | 25 | isPinned: integer("is_pinned", { mode: "boolean" }).default(false), |
26 | 26 | isLocked: integer("is_locked", { mode: "boolean" }).default(false), |
27 | 27 | lockReason: text("lock_reason"), |
| @@ -122,10 +122,6 @@ export const issuesRelations = relations(issues, ({ one, many }) => ({ | ||
122 | 122 | fields: [issues.assigneeId], |
123 | 123 | references: [users.id], |
124 | 124 | }), |
125 | - milestone: one(milestones, { | |
126 | - fields: [issues.milestoneId], | |
127 | - references: [milestones.id], | |
128 | - }), | |
129 | 125 | comments: many(issueComments), |
130 | 126 | labels: many(issueLabels), |
131 | 127 | assignees: many(issueAssignees), |
132 | 128 | |
βΌsrc/db/schema/organizations.ts
+0-145
| @@ -1,145 +0,0 @@ | ||
1 | -/** | |
2 | - * Organizations Schema - Drizzle ORM | |
3 | - * Defines organizations, teams, and memberships | |
4 | - */ | |
5 | - | |
6 | -import { relations } from "drizzle-orm"; | |
7 | -import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; | |
8 | -import { repositories } from "./repositories"; | |
9 | -import { users } from "./users"; | |
10 | - | |
11 | -export const organizations = sqliteTable("organizations", { | |
12 | - id: text("id").primaryKey(), | |
13 | - name: text("name").notNull().unique(), | |
14 | - displayName: text("display_name"), | |
15 | - description: text("description"), | |
16 | - avatarUrl: text("avatar_url"), | |
17 | - website: text("website"), | |
18 | - location: text("location"), | |
19 | - email: text("email"), | |
20 | - isVerified: integer("is_verified", { mode: "boolean" }).default(false), | |
21 | - visibility: text("visibility").notNull().default("public"), // public, private | |
22 | - defaultRepoPermission: text("default_repo_permission").default("read"), // none, read, write, admin | |
23 | - membersCanCreateRepos: integer("members_can_create_repos", { | |
24 | - mode: "boolean", | |
25 | - }).default(true), | |
26 | - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), | |
27 | - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), | |
28 | -}); | |
29 | - | |
30 | -export const organizationMembers = sqliteTable("organization_members", { | |
31 | - id: text("id").primaryKey(), | |
32 | - organizationId: text("organization_id") | |
33 | - .notNull() | |
34 | - .references(() => organizations.id, { onDelete: "cascade" }), | |
35 | - userId: text("user_id") | |
36 | - .notNull() | |
37 | - .references(() => users.id, { onDelete: "cascade" }), | |
38 | - role: text("role").notNull().default("member"), // owner, admin, member | |
39 | - joinedAt: text("joined_at").notNull().default("CURRENT_TIMESTAMP"), | |
40 | -}); | |
41 | - | |
42 | -export const teams = sqliteTable("teams", { | |
43 | - id: text("id").primaryKey(), | |
44 | - organizationId: text("organization_id") | |
45 | - .notNull() | |
46 | - .references(() => organizations.id, { onDelete: "cascade" }), | |
47 | - name: text("name").notNull(), | |
48 | - slug: text("slug").notNull(), | |
49 | - description: text("description"), | |
50 | - privacy: text("privacy").notNull().default("visible"), // visible, secret | |
51 | - parentId: text("parent_id").references(() => teams.id), | |
52 | - permission: text("permission").notNull().default("read"), // none, read, write, maintain, admin | |
53 | - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), | |
54 | - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), | |
55 | -}); | |
56 | - | |
57 | -export const teamMembers = sqliteTable("team_members", { | |
58 | - id: text("id").primaryKey(), | |
59 | - teamId: text("team_id") | |
60 | - .notNull() | |
61 | - .references(() => teams.id, { onDelete: "cascade" }), | |
62 | - userId: text("user_id") | |
63 | - .notNull() | |
64 | - .references(() => users.id, { onDelete: "cascade" }), | |
65 | - role: text("role").notNull().default("member"), // maintainer, member | |
66 | - joinedAt: text("joined_at").notNull().default("CURRENT_TIMESTAMP"), | |
67 | -}); | |
68 | - | |
69 | -export const teamRepositories = sqliteTable("team_repositories", { | |
70 | - id: text("id").primaryKey(), | |
71 | - teamId: text("team_id") | |
72 | - .notNull() | |
73 | - .references(() => teams.id, { onDelete: "cascade" }), | |
74 | - repositoryId: text("repository_id") | |
75 | - .notNull() | |
76 | - .references(() => repositories.id, { onDelete: "cascade" }), | |
77 | - permission: text("permission").notNull().default("read"), // read, write, maintain, admin | |
78 | - addedAt: text("added_at").notNull().default("CURRENT_TIMESTAMP"), | |
79 | -}); | |
80 | - | |
81 | -// Relations | |
82 | -export const organizationsRelations = relations(organizations, ({ many }) => ({ | |
83 | - members: many(organizationMembers), | |
84 | - teams: many(teams), | |
85 | -})); | |
86 | - | |
87 | -export const organizationMembersRelations = relations( | |
88 | - organizationMembers, | |
89 | - ({ one }) => ({ | |
90 | - organization: one(organizations, { | |
91 | - fields: [organizationMembers.organizationId], | |
92 | - references: [organizations.id], | |
93 | - }), | |
94 | - user: one(users, { | |
95 | - fields: [organizationMembers.userId], | |
96 | - references: [users.id], | |
97 | - }), | |
98 | - }) | |
99 | -); | |
100 | - | |
101 | -export const teamsRelations = relations(teams, ({ one, many }) => ({ | |
102 | - organization: one(organizations, { | |
103 | - fields: [teams.organizationId], | |
104 | - references: [organizations.id], | |
105 | - }), | |
106 | - parent: one(teams, { | |
107 | - fields: [teams.parentId], | |
108 | - references: [teams.id], | |
109 | - }), | |
110 | - children: many(teams), | |
111 | - members: many(teamMembers), | |
112 | - repositories: many(teamRepositories), | |
113 | -})); | |
114 | - | |
115 | -export const teamMembersRelations = relations(teamMembers, ({ one }) => ({ | |
116 | - team: one(teams, { | |
117 | - fields: [teamMembers.teamId], | |
118 | - references: [teams.id], | |
119 | - }), | |
120 | - user: one(users, { | |
121 | - fields: [teamMembers.userId], | |
122 | - references: [users.id], | |
123 | - }), | |
124 | -})); | |
125 | - | |
126 | -export const teamRepositoriesRelations = relations( | |
127 | - teamRepositories, | |
128 | - ({ one }) => ({ | |
129 | - team: one(teams, { | |
130 | - fields: [teamRepositories.teamId], | |
131 | - references: [teams.id], | |
132 | - }), | |
133 | - repository: one(repositories, { | |
134 | - fields: [teamRepositories.repositoryId], | |
135 | - references: [repositories.id], | |
136 | - }), | |
137 | - }) | |
138 | -); | |
139 | - | |
140 | -// Types | |
141 | -export type Organization = typeof organizations.$inferSelect; | |
142 | -export type NewOrganization = typeof organizations.$inferInsert; | |
143 | -export type Team = typeof teams.$inferSelect; | |
144 | -export type OrganizationMember = typeof organizationMembers.$inferSelect; | |
145 | -export type TeamMember = typeof teamMembers.$inferSelect; | |
146 | 0 | |
βΌsrc/db/schema/pull-requests.ts
+36-39
| @@ -22,7 +22,7 @@ export const pullRequests = sqliteTable("pull_requests", { | ||
22 | 22 | .notNull() |
23 | 23 | .references(() => users.id), |
24 | 24 | assigneeId: text("assignee_id").references(() => users.id), |
25 | - milestoneId: text("milestone_id").references(() => milestones.id), | |
25 | + milestoneId: text("milestone_id"), // Reference handled in relations | |
26 | 26 | |
27 | 27 | // Source and target |
28 | 28 | headBranch: text("head_branch").notNull(), |
| @@ -163,47 +163,44 @@ export const pullRequestChecks = sqliteTable("pull_request_checks", { | ||
163 | 163 | detailsUrl: text("details_url"), |
164 | 164 | output: text("output"), // JSON { title, summary, text } |
165 | 165 | startedAt: text("started_at"), |
166 | - completedAt: text("completed_at"), | |
167 | - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), | |
168 | - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), | |
169 | 166 | }); |
170 | 167 | |
171 | 168 | // Relations |
172 | -export const pullRequestsRelations = relations( | |
173 | - pullRequests, | |
174 | - ({ one, many }) => ({ | |
175 | - repository: one(repositories, { | |
176 | - fields: [pullRequests.repositoryId], | |
177 | - references: [repositories.id], | |
178 | - }), | |
179 | - headRepository: one(repositories, { | |
180 | - fields: [pullRequests.headRepositoryId], | |
181 | - references: [repositories.id], | |
182 | - }), | |
183 | - author: one(users, { | |
184 | - fields: [pullRequests.authorId], | |
185 | - references: [users.id], | |
186 | - }), | |
187 | - assignee: one(users, { | |
188 | - fields: [pullRequests.assigneeId], | |
189 | - references: [users.id], | |
190 | - }), | |
191 | - mergedBy: one(users, { | |
192 | - fields: [pullRequests.mergedById], | |
193 | - references: [users.id], | |
194 | - }), | |
195 | - milestone: one(milestones, { | |
196 | - fields: [pullRequests.milestoneId], | |
197 | - references: [milestones.id], | |
198 | - }), | |
199 | - reviews: many(pullRequestReviews), | |
200 | - comments: many(pullRequestComments), | |
201 | - labels: many(pullRequestLabels), | |
202 | - assignees: many(pullRequestAssignees), | |
203 | - reviewers: many(pullRequestReviewers), | |
204 | - checks: many(pullRequestChecks), | |
205 | - }) | |
206 | -); | |
169 | +// export const pullRequestsRelations = relations( | |
170 | +// pullRequests, | |
171 | +// ({ one, many }) => ({ | |
172 | +// repository: one(repositories, { | |
173 | +// fields: [pullRequests.repositoryId], | |
174 | +// references: [repositories.id], | |
175 | +// }), | |
176 | +// headRepository: one(repositories, { | |
177 | +// fields: [pullRequests.headRepositoryId], | |
178 | +// references: [repositories.id], | |
179 | +// }), | |
180 | +// author: one(users, { | |
181 | +// fields: [pullRequests.authorId], | |
182 | +// references: [users.id], | |
183 | +// }), | |
184 | +// assignee: one(users, { | |
185 | +// fields: [pullRequests.assigneeId], | |
186 | +// references: [users.id], | |
187 | +// }), | |
188 | +// // milestone: one(milestones, { // Removed because milestoneId has no foreign key | |
189 | +// // fields: [pullRequests.milestoneId], | |
190 | +// // references: [milestones.id], | |
191 | +// // }), | |
192 | +// mergedBy: one(users, { | |
193 | +// fields: [pullRequests.mergedById], | |
194 | +// references: [users.id], | |
195 | +// }), | |
196 | +// reviews: many(pullRequestReviews), | |
197 | +// comments: many(pullRequestComments), | |
198 | +// labels: many(pullRequestLabels), | |
199 | +// assignees: many(pullRequestAssignees), | |
200 | +// reviewers: many(pullRequestReviewers), | |
201 | +// checks: many(pullRequestChecks), | |
202 | +// }) | |
203 | +// ); | |
207 | 204 | |
208 | 205 | export const pullRequestReviewsRelations = relations( |
209 | 206 | pullRequestReviews, |
210 | 207 | |
βΌsrc/db/schema/slack-integration.ts
+2-6
| @@ -5,14 +5,14 @@ | ||
5 | 5 | |
6 | 6 | import { relations } from "drizzle-orm"; |
7 | 7 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; |
8 | -import { organizations } from "./organizations"; | |
8 | +// import { organizations } from "./organizations"; | |
9 | 9 | import { repositories } from "./repositories"; |
10 | 10 | import { users } from "./users"; |
11 | 11 | |
12 | 12 | // Slack workspace connections |
13 | 13 | export const slackWorkspaces = sqliteTable("slack_workspaces", { |
14 | 14 | id: text("id").primaryKey(), |
15 | - organizationId: text("organization_id").references(() => organizations.id, { onDelete: "cascade" }), | |
15 | + // organizationId: text("organization_id").references(() => organizations.id, { onDelete: "cascade" }), // Removed as per instruction | |
16 | 16 | |
17 | 17 | // Slack info |
18 | 18 | teamId: text("team_id").notNull(), // Slack team/workspace ID |
| @@ -95,10 +95,6 @@ export const slackUserMappings = sqliteTable("slack_user_mappings", { | ||
95 | 95 | |
96 | 96 | // Relations |
97 | 97 | export const slackWorkspacesRelations = relations(slackWorkspaces, ({ one, many }) => ({ |
98 | - organization: one(organizations, { | |
99 | - fields: [slackWorkspaces.organizationId], | |
100 | - references: [organizations.id], | |
101 | - }), | |
102 | 98 | installedBy: one(users, { |
103 | 99 | fields: [slackWorkspaces.installedById], |
104 | 100 | references: [users.id], |
105 | 101 | |
βΌsrc/pages/[owner]/[repo]/index.astro
+2-2
| @@ -312,11 +312,11 @@ const repo = { | ||
312 | 312 | <div class="space-y-4"> |
313 | 313 | <!-- Latest Commit --> |
314 | 314 | { |
315 | - latestCommit ? ( | |
315 | + latestCommit && latestCommit.authorName ? ( | |
316 | 316 | <div class="flex items-center gap-4 rounded-lg border bg-muted/30 p-4"> |
317 | 317 | <div class="h-8 w-8 rounded-full bg-muted flex items-center justify-center"> |
318 | 318 | <span class="text-sm font-medium"> |
319 | - {latestCommit.authorName[0]} | |
319 | + {latestCommit.authorName[0].toUpperCase()} | |
320 | 320 | </span> |
321 | 321 | </div> |
322 | 322 | <div class="flex-1 min-w-0"> |
323 | 323 | |
βΌsrc/pages/admin/metrics-disabled.txt β src/pages/admin/_metrics-disabled.txt
+0-0
0 | 0 |
βΌsrc/pages/admin/storage-disabled.txt β src/pages/admin/_storage-disabled.txt
+0-0
0 | 0 |
βΌsrc/pages/api.astro
+328-0
| @@ -0,0 +1,328 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "@/layouts/BaseLayout.astro"; | |
3 | +import { Code2, Webhook, Key, BookOpen } from "lucide-react"; | |
4 | + | |
5 | +const apiSections = [ | |
6 | + { | |
7 | + icon: Code2, | |
8 | + title: "REST API", | |
9 | + description: "Complete REST API reference for repositories, pull requests, and more", | |
10 | + href: "/docs/api/rest-api", | |
11 | + status: "Available", | |
12 | + statusColor: "text-green-400" | |
13 | + }, | |
14 | + { | |
15 | + icon: Webhook, | |
16 | + title: "Webhooks", | |
17 | + description: "Event-driven integrations with webhook payloads", | |
18 | + href: "/docs/api/webhooks", | |
19 | + status: "Available", | |
20 | + statusColor: "text-green-400" | |
21 | + }, | |
22 | + { | |
23 | + icon: Key, | |
24 | + title: "Authentication", | |
25 | + description: "API tokens, OAuth, and authentication flows", | |
26 | + href: "/docs/api/authentication", | |
27 | + status: "Available", | |
28 | + statusColor: "text-green-400" | |
29 | + }, | |
30 | + { | |
31 | + icon: BookOpen, | |
32 | + title: "GraphQL API", | |
33 | + description: "Flexible data querying with GraphQL", | |
34 | + href: "/docs/api/graphql", | |
35 | + status: "Coming Soon", | |
36 | + statusColor: "text-yellow-400" | |
37 | + } | |
38 | +]; | |
39 | + | |
40 | +const quickExamples = [ | |
41 | + { | |
42 | + title: "Create Repository", | |
43 | + method: "POST", | |
44 | + endpoint: "/api/repos", | |
45 | + code: `curl -X POST https://git.yourcompany.com/api/repos \\ | |
46 | + -H "Authorization: Bearer YOUR_TOKEN" \\ | |
47 | + -H "Content-Type: application/json" \\ | |
48 | + -d '{ | |
49 | + "name": "my-project", | |
50 | + "description": "My awesome project", | |
51 | + "visibility": "private" | |
52 | + }'` | |
53 | + }, | |
54 | + { | |
55 | + title: "List Pull Requests", | |
56 | + method: "GET", | |
57 | + endpoint: "/api/repos/:owner/:repo/pulls", | |
58 | + code: `curl https://git.yourcompany.com/api/repos/owner/repo/pulls \\ | |
59 | + -H "Authorization: Bearer YOUR_TOKEN"` | |
60 | + }, | |
61 | + { | |
62 | + title: "Create Pull Request", | |
63 | + method: "POST", | |
64 | + endpoint: "/api/repos/:owner/:repo/pulls", | |
65 | + code: `curl -X POST https://git.yourcompany.com/api/repos/owner/repo/pulls \\ | |
66 | + -H "Authorization: Bearer YOUR_TOKEN" \\ | |
67 | + -H "Content-Type: application/json" \\ | |
68 | + -d '{ | |
69 | + "title": "Add new feature", | |
70 | + "head": "feature-branch", | |
71 | + "base": "main", | |
72 | + "body": "This PR adds..." | |
73 | + }'` | |
74 | + } | |
75 | +]; | |
76 | +--- | |
77 | + | |
78 | +<BaseLayout title="API Documentation - OpenCodeHub"> | |
79 | + <div class="container mx-auto py-12 px-4"> | |
80 | + <!-- Hero --> | |
81 | + <div class="text-center mb-16"> | |
82 | + <h1 class="text-5xl font-bold mb-4"> | |
83 | + <span class="bg-clip-text text-transparent bg-gradient-to-r from-primary to-purple-400"> | |
84 | + API Documentation | |
85 | + </span> | |
86 | + </h1> | |
87 | + <p class="text-xl text-muted-foreground max-w-2xl mx-auto"> | |
88 | + Integrate OpenCodeHub into your workflow with our comprehensive REST API | |
89 | + </p> | |
90 | + </div> | |
91 | + | |
92 | + <!-- API Overview --> | |
93 | + <section class="mb-16"> | |
94 | + <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6"> | |
95 | + {apiSections.map((section) => { | |
96 | + const Icon = section.icon; | |
97 | + return ( | |
98 | + <a | |
99 | + href={section.href} | |
100 | + class="group relative p-6 rounded-xl border border-white/10 bg-card/50 backdrop-blur-sm hover:border-primary/50 transition-all hover:scale-105" | |
101 | + > | |
102 | + <div class="flex items-center justify-between mb-4"> | |
103 | + <Icon class="h-8 w-8 text-primary" /> | |
104 | + <span class={`text-xs font-medium ${section.statusColor}`}> | |
105 | + {section.status} | |
106 | + </span> | |
107 | + </div> | |
108 | + <h3 class="text-lg font-semibold mb-2">{section.title}</h3> | |
109 | + <p class="text-sm text-muted-foreground">{section.description}</p> | |
110 | + </a> | |
111 | + ); | |
112 | + })} | |
113 | + </div> | |
114 | + </section> | |
115 | + | |
116 | + <!-- Quick Start --> | |
117 | + <section class="mb-16"> | |
118 | + <h2 class="text-3xl font-bold mb-8">Quick Start</h2> | |
119 | + <div class="grid md:grid-cols-2 gap-6"> | |
120 | + <!-- Authentication --> | |
121 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
122 | + <h3 class="text-xl font-semibold mb-4">1. Get API Token</h3> | |
123 | + <div class="space-y-3"> | |
124 | + <p class="text-sm text-muted-foreground"> | |
125 | + Generate a personal access token from your settings: | |
126 | + </p> | |
127 | + <ol class="list-decimal list-inside space-y-2 text-sm"> | |
128 | + <li>Go to <a href="/settings/tokens" class="text-primary hover:underline">Settings β Tokens</a></li> | |
129 | + <li>Click "Generate New Token"</li> | |
130 | + <li>Select scopes: <code class="bg-muted px-2 py-1 rounded">repo</code>, <code class="bg-muted px-2 py-1 rounded">pr</code></li> | |
131 | + <li>Copy and save your token securely</li> | |
132 | + </ol> | |
133 | + </div> | |
134 | + </div> | |
135 | + | |
136 | + <!-- Base URL --> | |
137 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
138 | + <h3 class="text-xl font-semibold mb-4">2. API Base URL</h3> | |
139 | + <div class="space-y-3"> | |
140 | + <p class="text-sm text-muted-foreground"> | |
141 | + All API requests should be made to: | |
142 | + </p> | |
143 | + <pre class="text-sm bg-black/50 p-3 rounded overflow-x-auto"><code>https://git.yourcompany.com/api</code></pre> | |
144 | + <p class="text-xs text-muted-foreground"> | |
145 | + Replace <code class="bg-muted px-1 rounded">git.yourcompany.com</code> with your instance URL | |
146 | + </p> | |
147 | + </div> | |
148 | + </div> | |
149 | + </div> | |
150 | + </section> | |
151 | + | |
152 | + <!-- Quick Examples --> | |
153 | + <section class="mb-16"> | |
154 | + <h2 class="text-3xl font-bold mb-8">Quick Examples</h2> | |
155 | + <div class="space-y-6"> | |
156 | + {quickExamples.map((example) => ( | |
157 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
158 | + <div class="flex items-center justify-between mb-4"> | |
159 | + <h3 class="text-lg font-semibold">{example.title}</h3> | |
160 | + <div class="flex items-center gap-2"> | |
161 | + <span class={`px-3 py-1 rounded-full text-xs font-medium ${ | |
162 | + example.method === 'GET' | |
163 | + ? 'bg-blue-500/20 text-blue-400' | |
164 | + : example.method === 'POST' | |
165 | + ? 'bg-green-500/20 text-green-400' | |
166 | + : 'bg-yellow-500/20 text-yellow-400' | |
167 | + }`}> | |
168 | + {example.method} | |
169 | + </span> | |
170 | + <code class="text-xs text-muted-foreground">{example.endpoint}</code> | |
171 | + </div> | |
172 | + </div> | |
173 | + <pre class="text-sm bg-black/80 p-4 rounded overflow-x-auto border border-white/5"><code>{example.code}</code></pre> | |
174 | + </div> | |
175 | + ))} | |
176 | + </div> | |
177 | + </section> | |
178 | + | |
179 | + <!-- Rate Limiting --> | |
180 | + <section class="mb-16"> | |
181 | + <h2 class="text-3xl font-bold mb-8">Rate Limiting</h2> | |
182 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
183 | + <div class="grid md:grid-cols-3 gap-6"> | |
184 | + <div> | |
185 | + <h4 class="font-semibold mb-2">Authenticated Requests</h4> | |
186 | + <p class="text-3xl font-bold text-primary">5,000</p> | |
187 | + <p class="text-sm text-muted-foreground">requests per hour</p> | |
188 | + </div> | |
189 | + <div> | |
190 | + <h4 class="font-semibold mb-2">Unauthenticated Requests</h4> | |
191 | + <p class="text-3xl font-bold text-yellow-400">60</p> | |
192 | + <p class="text-sm text-muted-foreground">requests per hour</p> | |
193 | + </div> | |
194 | + <div> | |
195 | + <h4 class="font-semibold mb-2">Check Limits</h4> | |
196 | + <p class="text-sm text-muted-foreground mb-2">Response headers:</p> | |
197 | + <code class="text-xs bg-muted px-2 py-1 rounded block">X-RateLimit-Remaining</code> | |
198 | + </div> | |
199 | + </div> | |
200 | + </div> | |
201 | + </section> | |
202 | + | |
203 | + <!-- Response Format --> | |
204 | + <section class="mb-16"> | |
205 | + <h2 class="text-3xl font-bold mb-8">Response Format</h2> | |
206 | + <div class="grid md:grid-cols-2 gap-6"> | |
207 | + <!-- Success --> | |
208 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
209 | + <h3 class="text-lg font-semibold mb-4 text-green-400">Success Response</h3> | |
210 | + <pre class="text-sm bg-black/80 p-4 rounded overflow-x-auto"><code>{`{ | |
211 | + "data": { | |
212 | + "id": "repo-123", | |
213 | + "name": "my-project", | |
214 | + "owner": { | |
215 | + "username": "swadhin", | |
216 | + "id": "user-456" | |
217 | + }, | |
218 | + "visibility": "private", | |
219 | + "created_at": "2024-01-01T12:00:00Z" | |
220 | + }, | |
221 | + "meta": { | |
222 | + "timestamp": "2024-01-01T12:00:01Z" | |
223 | + } | |
224 | +}`}</code></pre> | |
225 | + </div> | |
226 | + | |
227 | + <!-- Error --> | |
228 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
229 | + <h3 class="text-lg font-semibold mb-4 text-red-400">Error Response</h3> | |
230 | + <pre class="text-sm bg-black/80 p-4 rounded overflow-x-auto"><code>{`{ | |
231 | + "error": { | |
232 | + "code": "VALIDATION_ERROR", | |
233 | + "message": "Repository name is required", | |
234 | + "details": { | |
235 | + "field": "name", | |
236 | + "constraint": "required" | |
237 | + } | |
238 | + }, | |
239 | + "meta": { | |
240 | + "timestamp": "2024-01-01T12:00:01Z", | |
241 | + "request_id": "req-abc123" | |
242 | + } | |
243 | +}`}</code></pre> | |
244 | + </div> | |
245 | + </div> | |
246 | + </section> | |
247 | + | |
248 | + <!-- SDKs & Libraries --> | |
249 | + <section class="mb-16"> | |
250 | + <h2 class="text-3xl font-bold mb-8">SDKs & Libraries</h2> | |
251 | + <div class="grid md:grid-cols-3 gap-6"> | |
252 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
253 | + <h3 class="text-lg font-semibold mb-2">JavaScript/TypeScript</h3> | |
254 | + <pre class="text-sm bg-black/50 p-3 rounded mb-3"><code>npm install @opencodehub/sdk</code></pre> | |
255 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/tree/main/sdk/js" class="text-sm text-primary hover:underline"> | |
256 | + View docs β | |
257 | + </a> | |
258 | + </div> | |
259 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
260 | + <h3 class="text-lg font-semibold mb-2">Python</h3> | |
261 | + <pre class="text-sm bg-black/50 p-3 rounded mb-3"><code>pip install opencodehub</code></pre> | |
262 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/tree/main/sdk/python" class="text-sm text-primary hover:underline"> | |
263 | + View docs β | |
264 | + </a> | |
265 | + </div> | |
266 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
267 | + <h3 class="text-lg font-semibold mb-2">Go</h3> | |
268 | + <pre class="text-sm bg-black/50 p-3 rounded mb-3"><code>go get github.com/opencodehub/sdk-go</code></pre> | |
269 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/tree/main/sdk/go" class="text-sm text-primary hover:underline"> | |
270 | + View docs β | |
271 | + </a> | |
272 | + </div> | |
273 | + </div> | |
274 | + </section> | |
275 | + | |
276 | + <!-- Resources --> | |
277 | + <section> | |
278 | + <h2 class="text-3xl font-bold mb-8">Additional Resources</h2> | |
279 | + <div class="grid md:grid-cols-2 gap-6"> | |
280 | + <a | |
281 | + href="/docs" | |
282 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
283 | + > | |
284 | + <h3 class="text-lg font-semibold mb-2">π Full Documentation</h3> | |
285 | + <p class="text-sm text-muted-foreground"> | |
286 | + Complete guides, tutorials, and feature documentation | |
287 | + </p> | |
288 | + </a> | |
289 | + <a | |
290 | + href="https://github.com/swadhinbiswas/OpencodeHub/tree/main/examples" | |
291 | + target="_blank" | |
292 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
293 | + > | |
294 | + <h3 class="text-lg font-semibold mb-2">π‘ Code Examples</h3> | |
295 | + <p class="text-sm text-muted-foreground"> | |
296 | + Real-world integration examples and sample code | |
297 | + </p> | |
298 | + </a> | |
299 | + <a | |
300 | + href="https://github.com/swadhinbiswas/OpencodeHub/discussions" | |
301 | + target="_blank" | |
302 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
303 | + > | |
304 | + <h3 class="text-lg font-semibold mb-2">π¬ Community</h3> | |
305 | + <p class="text-sm text-muted-foreground"> | |
306 | + Ask questions and discuss API usage | |
307 | + </p> | |
308 | + </a> | |
309 | + <a | |
310 | + href="https://github.com/swadhinbiswas/OpencodeHub/blob/main/CHANGELOG.md" | |
311 | + target="_blank" | |
312 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
313 | + > | |
314 | + <h3 class="text-lg font-semibold mb-2">π Changelog</h3> | |
315 | + <p class="text-sm text-muted-foreground"> | |
316 | + API changes and version history | |
317 | + </p> | |
318 | + </a> | |
319 | + </div> | |
320 | + </section> | |
321 | + </div> | |
322 | +</BaseLayout> | |
323 | + | |
324 | +<style> | |
325 | + .container { | |
326 | + max-width: 1200px; | |
327 | + } | |
328 | +</style> | |
0 | 329 | |
βΌsrc/pages/docs/features/stacked-prs.astro
+99-0
| @@ -0,0 +1,99 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "@/layouts/BaseLayout.astro"; | |
3 | +import { readFileSync } from "fs"; | |
4 | +import { marked } from "marked"; | |
5 | + | |
6 | +const markdown = readFileSync("docs/features/stacked-prs.md", "utf-8"); | |
7 | +const html = marked(markdown); | |
8 | +--- | |
9 | + | |
10 | +<BaseLayout title="Stacked Pull Requests - OpenCodeHub Docs"> | |
11 | + <div class="container mx-auto py-12 px-4"> | |
12 | + <a href="/docs" class="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-primary mb-8"> | |
13 | + β Back to Documentation | |
14 | + </a> | |
15 | + | |
16 | + <article class="prose prose-invert max-w-4xl mx-auto"> | |
17 | + <div set:html={html} /> | |
18 | + </article> | |
19 | + </div> | |
20 | +</BaseLayout> | |
21 | + | |
22 | +<style is:global> | |
23 | + .prose { | |
24 | + color: hsl(var(--foreground)); | |
25 | + } | |
26 | + .prose h1 { | |
27 | + font-size: 2.5rem; | |
28 | + font-weight: bold; | |
29 | + margin-bottom: 1rem; | |
30 | + color: hsl(var(--primary)); | |
31 | + } | |
32 | + .prose h2 { | |
33 | + font-size: 2rem; | |
34 | + font-weight: bold; | |
35 | + margin-top: 2rem; | |
36 | + margin-bottom: 1rem; | |
37 | + border-bottom: 1px solid hsl(var(--border)); | |
38 | + padding-bottom: 0.5rem; | |
39 | + } | |
40 | + .prose h3 { | |
41 | + font-size: 1.5rem; | |
42 | + font-weight: 600; | |
43 | + margin-top: 1.5rem; | |
44 | + margin-bottom: 0.75rem; | |
45 | + } | |
46 | + .prose pre { | |
47 | + background: hsl(var(--card)); | |
48 | + border: 1px solid hsl(var(--border)); | |
49 | + border-radius: 0.5rem; | |
50 | + padding: 1rem; | |
51 | + overflow-x: auto; | |
52 | + } | |
53 | + .prose code { | |
54 | + background: hsl(var(--muted)); | |
55 | + padding: 0.2rem 0.4rem; | |
56 | + border-radius: 0.25rem; | |
57 | + font-size: 0.875rem; | |
58 | + } | |
59 | + .prose pre code { | |
60 | + background: transparent; | |
61 | + padding: 0; | |
62 | + } | |
63 | + .prose a { | |
64 | + color: hsl(var(--primary)); | |
65 | + text-decoration: none; | |
66 | + } | |
67 | + .prose a:hover { | |
68 | + text-decoration: underline; | |
69 | + } | |
70 | + .prose table { | |
71 | + width: 100%; | |
72 | + border-collapse: collapse; | |
73 | + margin: 1.5rem 0; | |
74 | + } | |
75 | + .prose th, | |
76 | + .prose td { | |
77 | + border: 1px solid hsl(var(--border)); | |
78 | + padding: 0.75rem; | |
79 | + text-align: left; | |
80 | + } | |
81 | + .prose th { | |
82 | + background: hsl(var(--muted)); | |
83 | + font-weight: 600; | |
84 | + } | |
85 | + .prose blockquote { | |
86 | + border-left: 4px solid hsl(var(--primary)); | |
87 | + padding-left: 1rem; | |
88 | + margin: 1.5rem 0; | |
89 | + font-style: italic; | |
90 | + color: hsl(var(--muted-foreground)); | |
91 | + } | |
92 | + .prose ul, .prose ol { | |
93 | + margin: 1rem 0; | |
94 | + padding-left: 2rem; | |
95 | + } | |
96 | + .prose li { | |
97 | + margin: 0.5rem 0; | |
98 | + } | |
99 | +</style> | |
0 | 100 | |
βΌsrc/pages/docs/index.astro
+272-0
| @@ -0,0 +1,272 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "@/layouts/BaseLayout.astro"; | |
3 | +import { BookOpen, Code, Zap, GitBranch, Bot, GitMerge, BarChart3, MessageSquare, Terminal } from "lucide-react"; | |
4 | + | |
5 | +const features = [ | |
6 | + { | |
7 | + icon: GitBranch, | |
8 | + title: "Stacked Pull Requests", | |
9 | + description: "Break large changes into reviewable stacks", | |
10 | + href: "/docs/features/stacked-prs", | |
11 | + color: "from-blue-500 to-cyan-500" | |
12 | + }, | |
13 | + { | |
14 | + icon: Bot, | |
15 | + title: "AI Code Review", | |
16 | + description: "Get instant feedback from GPT-4 or Claude", | |
17 | + href: "/docs/features/ai-review", | |
18 | + color: "from-purple-500 to-pink-500" | |
19 | + }, | |
20 | + { | |
21 | + icon: GitMerge, | |
22 | + title: "Smart Merge Queue", | |
23 | + description: "Automate merging with CI optimization", | |
24 | + href: "/docs/features/merge-queue", | |
25 | + color: "from-green-500 to-emerald-500" | |
26 | + }, | |
27 | + { | |
28 | + icon: BarChart3, | |
29 | + title: "Developer Metrics", | |
30 | + description: "Track velocity and review efficiency", | |
31 | + href: "/admin/metrics", | |
32 | + color: "from-orange-500 to-red-500" | |
33 | + }, | |
34 | + { | |
35 | + icon: MessageSquare, | |
36 | + title: "Slack Integration", | |
37 | + description: "Get notifications in Slack", | |
38 | + href: "/settings/integrations", | |
39 | + color: "from-indigo-500 to-blue-500" | |
40 | + }, | |
41 | + { | |
42 | + icon: Terminal, | |
43 | + title: "OCH CLI", | |
44 | + description: "Powerful command-line interface", | |
45 | + href: "https://github.com/swadhinbiswas/OpencodeHub/tree/main/cli", | |
46 | + color: "from-cyan-500 to-teal-500" | |
47 | + } | |
48 | +]; | |
49 | + | |
50 | +const quickLinks = [ | |
51 | + { title: "Getting Started", href: "#getting-started", icon: Zap }, | |
52 | + { title: "Features", href: "#features", icon: Code }, | |
53 | + { title: "Tutorials", href: "#tutorials", icon: BookOpen }, | |
54 | +]; | |
55 | +--- | |
56 | + | |
57 | +<BaseLayout title="Documentation - OpenCodeHub"> | |
58 | + <div class="container mx-auto py-12 px-4"> | |
59 | + <!-- Hero --> | |
60 | + <div class="text-center mb-16"> | |
61 | + <h1 class="text-5xl font-bold mb-4"> | |
62 | + <span class="bg-clip-text text-transparent bg-gradient-to-r from-primary to-purple-400"> | |
63 | + Documentation | |
64 | + </span> | |
65 | + </h1> | |
66 | + <p class="text-xl text-muted-foreground max-w-2xl mx-auto"> | |
67 | + Everything you need to know about OpenCodeHub - from getting started to advanced features | |
68 | + </p> | |
69 | + </div> | |
70 | + | |
71 | + <!-- Quick Links --> | |
72 | + <div class="grid md:grid-cols-3 gap-6 mb-16"> | |
73 | + {quickLinks.map((link) => { | |
74 | + const Icon = link.icon; | |
75 | + return ( | |
76 | + <a | |
77 | + href={link.href} | |
78 | + class="group relative p-6 rounded-xl border border-white/10 bg-card/50 backdrop-blur-sm hover:border-primary/50 transition-all hover:scale-105 hover:shadow-lg" | |
79 | + > | |
80 | + <div class="flex items-center gap-3 mb-2"> | |
81 | + <Icon class="h-6 w-6 text-primary" /> | |
82 | + <h3 class="text-lg font-semibold">{link.title}</h3> | |
83 | + </div> | |
84 | + <p class="text-sm text-muted-foreground">Quick access</p> | |
85 | + </a> | |
86 | + ); | |
87 | + })} | |
88 | + </div> | |
89 | + | |
90 | + <!-- Features Grid --> | |
91 | + <section id="features" class="mb-16"> | |
92 | + <h2 class="text-3xl font-bold mb-8">Core Features</h2> | |
93 | + <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
94 | + {features.map((feature) => { | |
95 | + const Icon = feature.icon; | |
96 | + return ( | |
97 | + <a | |
98 | + href={feature.href} | |
99 | + class="group relative p-6 rounded-xl border border-white/10 bg-card/50 backdrop-blur-sm hover:border-primary/50 transition-all hover:scale-105" | |
100 | + > | |
101 | + <div class={`inline-flex p-3 rounded-lg bg-gradient-to-br ${feature.color} mb-4`}> | |
102 | + <Icon class="h-6 w-6 text-white" /> | |
103 | + </div> | |
104 | + <h3 class="text-xl font-semibold mb-2">{feature.title}</h3> | |
105 | + <p class="text-sm text-muted-foreground mb-4">{feature.description}</p> | |
106 | + <span class="text-sm text-primary group-hover:underline"> | |
107 | + Learn more β | |
108 | + </span> | |
109 | + </a> | |
110 | + ); | |
111 | + })} | |
112 | + </div> | |
113 | + </section> | |
114 | + | |
115 | + <!-- Getting Started --> | |
116 | + <section id="getting-started" class="mb-16"> | |
117 | + <h2 class="text-3xl font-bold mb-8">Getting Started</h2> | |
118 | + <div class="grid md:grid-cols-2 gap-6"> | |
119 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
120 | + <h3 class="text-xl font-semibold mb-4">π Quick Start (5 minutes)</h3> | |
121 | + <div class="space-y-4"> | |
122 | + <div class="p-4 rounded-lg bg-background/50"> | |
123 | + <h4 class="font-medium mb-2">1. Install with Docker</h4> | |
124 | + <pre class="text-sm bg-black/50 p-3 rounded overflow-x-auto"><code>docker-compose up -d</code></pre> | |
125 | + </div> | |
126 | + <div class="p-4 rounded-lg bg-background/50"> | |
127 | + <h4 class="font-medium mb-2">2. Create admin user</h4> | |
128 | + <pre class="text-sm bg-black/50 p-3 rounded overflow-x-auto"><code>docker-compose exec app bun run scripts/seed-admin.ts</code></pre> | |
129 | + </div> | |
130 | + <div class="p-4 rounded-lg bg-background/50"> | |
131 | + <h4 class="font-medium mb-2">3. Open browser</h4> | |
132 | + <pre class="text-sm bg-black/50 p-3 rounded overflow-x-auto"><code>http://localhost:3000</code></pre> | |
133 | + </div> | |
134 | + </div> | |
135 | + <a href="/docs/installation" class="inline-flex items-center gap-2 mt-4 text-sm text-primary hover:underline"> | |
136 | + Full installation guide β | |
137 | + </a> | |
138 | + </div> | |
139 | + | |
140 | + <div class="p-6 rounded-xl border border-white/10 bg-card/50"> | |
141 | + <h3 class="text-xl font-semibold mb-4">π First Steps</h3> | |
142 | + <div class="space-y-3"> | |
143 | + <a href="/docs/tutorials/your-first-stack" class="block p-3 rounded-lg bg-background/50 hover:bg-primary/10 transition-colors"> | |
144 | + <div class="font-medium">Tutorial: Your First Stack</div> | |
145 | + <div class="text-sm text-muted-foreground">Create your first stacked PR in 15 minutes</div> | |
146 | + </a> | |
147 | + <a href="/docs/features/ai-review" class="block p-3 rounded-lg bg-background/50 hover:bg-primary/10 transition-colors"> | |
148 | + <div class="font-medium">Setup AI Code Review</div> | |
149 | + <div class="text-sm text-muted-foreground">Configure GPT-4 or Claude for automatic reviews</div> | |
150 | + </a> | |
151 | + <a href="/docs/features/merge-queue" class="block p-3 rounded-lg bg-background/50 hover:bg-primary/10 transition-colors"> | |
152 | + <div class="font-medium">Use Merge Queue</div> | |
153 | + <div class="text-sm text-muted-foreground">Automate merging with stack awareness</div> | |
154 | + </a> | |
155 | + </div> | |
156 | + </div> | |
157 | + </div> | |
158 | + </section> | |
159 | + | |
160 | + <!-- Tutorials --> | |
161 | + <section id="tutorials" class="mb-16"> | |
162 | + <h2 class="text-3xl font-bold mb-8">Hands-On Tutorials</h2> | |
163 | + <div class="space-y-4"> | |
164 | + <a | |
165 | + href="/docs/tutorials/your-first-stack" | |
166 | + class="block p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
167 | + > | |
168 | + <div class="flex items-center justify-between"> | |
169 | + <div> | |
170 | + <h3 class="text-xl font-semibold mb-2">π Your First Stacked PR</h3> | |
171 | + <p class="text-sm text-muted-foreground"> | |
172 | + Learn how to split a large feature into reviewable chunks in 15 minutes | |
173 | + </p> | |
174 | + <div class="flex items-center gap-4 mt-3 text-xs text-muted-foreground"> | |
175 | + <span>β±οΈ 15-20 minutes</span> | |
176 | + <span>π Beginner</span> | |
177 | + <span>π» Hands-on coding</span> | |
178 | + </div> | |
179 | + </div> | |
180 | + <span class="text-primary">β</span> | |
181 | + </div> | |
182 | + </a> | |
183 | + </div> | |
184 | + </section> | |
185 | + | |
186 | + <!-- Resources --> | |
187 | + <section class="mb-16"> | |
188 | + <h2 class="text-3xl font-bold mb-8">Additional Resources</h2> | |
189 | + <div class="grid md:grid-cols-3 gap-6"> | |
190 | + <a | |
191 | + href="https://github.com/swadhinbiswas/OpencodeHub" | |
192 | + target="_blank" | |
193 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
194 | + > | |
195 | + <h3 class="text-lg font-semibold mb-2">π GitHub Repository</h3> | |
196 | + <p class="text-sm text-muted-foreground">View source code and contribute</p> | |
197 | + </a> | |
198 | + <a | |
199 | + href="https://github.com/swadhinbiswas/OpencodeHub/issues" | |
200 | + target="_blank" | |
201 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
202 | + > | |
203 | + <h3 class="text-lg font-semibold mb-2">π Report Issues</h3> | |
204 | + <p class="text-sm text-muted-foreground">Found a bug? Let us know</p> | |
205 | + </a> | |
206 | + <a | |
207 | + href="https://github.com/swadhinbiswas/OpencodeHub/discussions" | |
208 | + target="_blank" | |
209 | + class="p-6 rounded-xl border border-white/10 bg-card/50 hover:border-primary/50 transition-all" | |
210 | + > | |
211 | + <h3 class="text-lg font-semibold mb-2">π¬ Discussions</h3> | |
212 | + <p class="text-sm text-muted-foreground">Ask questions and share ideas</p> | |
213 | + </a> | |
214 | + </div> | |
215 | + </section> | |
216 | + | |
217 | + <!-- Feature Comparison --> | |
218 | + <section> | |
219 | + <h2 class="text-3xl font-bold mb-8">Why OpenCodeHub?</h2> | |
220 | + <div class="overflow-x-auto"> | |
221 | + <table class="w-full border-collapse"> | |
222 | + <thead> | |
223 | + <tr class="border-b border-white/10"> | |
224 | + <th class="text-left p-4">Feature</th> | |
225 | + <th class="text-center p-4">GitHub</th> | |
226 | + <th class="text-center p-4">GitLab</th> | |
227 | + <th class="text-center p-4 text-primary">OpenCodeHub</th> | |
228 | + </tr> | |
229 | + </thead> | |
230 | + <tbody> | |
231 | + <tr class="border-b border-white/5"> | |
232 | + <td class="p-4">Stacked PRs</td> | |
233 | + <td class="text-center p-4">β</td> | |
234 | + <td class="text-center p-4">β</td> | |
235 | + <td class="text-center p-4 text-green-400">β
Native</td> | |
236 | + </tr> | |
237 | + <tr class="border-b border-white/5"> | |
238 | + <td class="p-4">AI Code Review</td> | |
239 | + <td class="text-center p-4">β οΈ Copilot</td> | |
240 | + <td class="text-center p-4">β</td> | |
241 | + <td class="text-center p-4 text-green-400">β
GPT-4 & Claude</td> | |
242 | + </tr> | |
243 | + <tr class="border-b border-white/5"> | |
244 | + <td class="p-4">Smart Merge Queue</td> | |
245 | + <td class="text-center p-4">β οΈ Basic</td> | |
246 | + <td class="text-center p-4">β οΈ Basic</td> | |
247 | + <td class="text-center p-4 text-green-400">β
Stack-aware</td> | |
248 | + </tr> | |
249 | + <tr class="border-b border-white/5"> | |
250 | + <td class="p-4">Self-Hosted</td> | |
251 | + <td class="text-center p-4">β</td> | |
252 | + <td class="text-center p-4">β
Complex</td> | |
253 | + <td class="text-center p-4 text-green-400">β
Simple</td> | |
254 | + </tr> | |
255 | + <tr> | |
256 | + <td class="p-4">Cost</td> | |
257 | + <td class="text-center p-4">$$$</td> | |
258 | + <td class="text-center p-4">$$$</td> | |
259 | + <td class="text-center p-4 text-green-400">Free</td> | |
260 | + </tr> | |
261 | + </tbody> | |
262 | + </table> | |
263 | + </div> | |
264 | + </section> | |
265 | + </div> | |
266 | +</BaseLayout> | |
267 | + | |
268 | +<style> | |
269 | + .container { | |
270 | + max-width: 1200px; | |
271 | + } | |
272 | +</style> | |
0 | 273 | |
βΌsrc/pages/index.astro
+4-0
| @@ -5,6 +5,7 @@ import { eq } from "drizzle-orm"; | ||
5 | 5 | import { BookOpen, Star } from "lucide-react"; |
6 | 6 | import HeroSection from "@/components/home/HeroSection"; |
7 | 7 | import FeaturesSection from "@/components/home/FeaturesSection"; |
8 | +import FeatureDemosSection from "@/components/home/FeatureDemosSection"; | |
8 | 9 | |
9 | 10 | // Fetch public repositories for the landing page |
10 | 11 | const db = getDatabase(); |
| @@ -92,6 +93,9 @@ try { | ||
92 | 93 | ) |
93 | 94 | } |
94 | 95 | |
96 | + <!-- Interactive Feature Demonstrations --> | |
97 | + <FeatureDemosSection client:load /> | |
98 | + | |
95 | 99 | <!-- Features Section with Aceternity UI Components --> |
96 | 100 | <FeaturesSection client:load /> |
97 | 101 | |
98 | 102 | |