mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 23:02:56 +01:00
19 KiB
19 KiB
DEPLOY-002: Preview Deployments
Overview
Enable automatic preview deployments for each git branch or commit. When users push changes, a preview URL is automatically generated so stakeholders can review before merging to production.
Context
Currently, sharing work-in-progress requires:
- Manual deploy to a staging site
- Share URL with stakeholders
- Remember which deploy corresponds to which version
- Manually clean up old deploys
Preview deployments provide:
- Automatic URL per branch/PR
- Easy sharing with stakeholders
- Visual history of changes
- Automatic cleanup
This is especially valuable for:
- Design reviews
- QA testing
- Client approvals
- Team collaboration
Integration with GIT Series
From GIT-002:
- Git status tracking per project
- Branch awareness
- Commit detection
This task leverages that infrastructure to trigger preview deploys.
Requirements
Functional Requirements
-
Automatic Previews
- Deploy preview on branch push
- Unique URL per branch
- Update preview on new commits
- Delete preview on branch delete
-
Manual Previews
- "Deploy Preview" button in editor
- Generate shareable URL
- Named previews (optional)
- Expiration settings
-
Preview Management
- List all active previews
- View preview URL
- Delete individual previews
- Set auto-cleanup rules
-
Sharing
- Copy preview URL
- QR code for mobile
- Optional password protection
- Expiration timer
-
Integration with PRs
- Comment preview URL on PR
- Update comment on new commits
- Status check integration
Non-Functional Requirements
- Preview available within 2 minutes
- Support 10+ concurrent previews
- Auto-cleanup after configurable period
- Works with all deploy providers
Technical Approach
1. Preview Service
// packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts
interface PreviewDeployment {
id: string;
projectId: string;
branch: string;
commitSha: string;
url: string;
platform: DeployPlatform;
siteId: string;
status: PreviewStatus;
createdAt: string;
expiresAt?: string;
password?: string;
name?: string;
}
enum PreviewStatus {
PENDING = 'pending',
BUILDING = 'building',
READY = 'ready',
FAILED = 'failed',
EXPIRED = 'expired'
}
interface PreviewConfig {
enabled: boolean;
autoDeployBranches: boolean;
excludeBranches: string[]; // e.g., ['main', 'master']
expirationDays: number;
maxPreviews: number;
passwordProtect: boolean;
commentOnPR: boolean;
}
class PreviewDeployService {
private static instance: PreviewDeployService;
// Preview management
async createPreview(options: CreatePreviewOptions): Promise<PreviewDeployment>;
async updatePreview(previewId: string): Promise<PreviewDeployment>;
async deletePreview(previewId: string): Promise<void>;
async listPreviews(projectId: string): Promise<PreviewDeployment[]>;
// Auto-deployment
async onBranchPush(projectId: string, branch: string, commitSha: string): Promise<void>;
async onBranchDelete(projectId: string, branch: string): Promise<void>;
// PR integration
async commentOnPR(preview: PreviewDeployment): Promise<void>;
async updatePRComment(preview: PreviewDeployment): Promise<void>;
// Cleanup
async cleanupExpiredPreviews(): Promise<void>;
async enforceMaxPreviews(projectId: string): Promise<void>;
// Configuration
getConfig(projectId: string): PreviewConfig;
setConfig(projectId: string, config: Partial<PreviewConfig>): void;
}
2. Branch-Based Preview Naming
// Generate preview URLs based on branch
function generatePreviewUrl(platform: DeployPlatform, branch: string, projectName: string): string {
const sanitizedBranch = sanitizeBranchName(branch);
switch (platform) {
case DeployPlatform.NETLIFY:
// Netlify: branch--sitename.netlify.app
return `https://${sanitizedBranch}--${projectName}.netlify.app`;
case DeployPlatform.VERCEL:
// Vercel: project-branch-hash.vercel.app
return `https://${projectName}-${sanitizedBranch}.vercel.app`;
case DeployPlatform.GITHUB_PAGES:
// GitHub Pages: use subdirectory or separate branch
return `https://${owner}.github.io/${repo}/preview/${sanitizedBranch}`;
default:
return '';
}
}
function sanitizeBranchName(branch: string): string {
return branch
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-')
.substring(0, 50);
}
3. Git Integration Hook
// packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts
// Hook into git operations
class PreviewDeployService {
constructor() {
// Listen for git events
EventDispatcher.instance.on('git.push.success', this.handlePush.bind(this));
EventDispatcher.instance.on('git.branch.delete', this.handleBranchDelete.bind(this));
}
private async handlePush(event: GitPushEvent): Promise<void> {
const config = this.getConfig(event.projectId);
if (!config.enabled || !config.autoDeployBranches) {
return;
}
// Check if branch is excluded
if (config.excludeBranches.includes(event.branch)) {
return;
}
// Check if we already have a preview for this branch
const existing = await this.findPreviewByBranch(event.projectId, event.branch);
if (existing) {
// Update existing preview
await this.updatePreview(existing.id);
} else {
// Create new preview
await this.createPreview({
projectId: event.projectId,
branch: event.branch,
commitSha: event.commitSha
});
}
// Comment on PR if enabled
if (config.commentOnPR) {
const pr = await this.findPRForBranch(event.projectId, event.branch);
if (pr) {
await this.commentOnPR(existing || await this.getLatestPreview(event.projectId, event.branch));
}
}
}
private async handleBranchDelete(event: GitBranchDeleteEvent): Promise<void> {
const preview = await this.findPreviewByBranch(event.projectId, event.branch);
if (preview) {
await this.deletePreview(preview.id);
}
}
}
4. PR Comment Integration
// packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts
async commentOnPR(preview: PreviewDeployment): Promise<void> {
const github = GitHubApiClient.instance;
const project = ProjectModel.instance;
const remote = project.getRemoteUrl();
if (!remote || !remote.includes('github.com')) {
return; // Only GitHub PRs supported
}
const { owner, repo } = parseGitHubUrl(remote);
const pr = await github.findPRByBranch(owner, repo, preview.branch);
if (!pr) {
return;
}
const commentBody = this.generatePRComment(preview);
// Check for existing Noodl comment
const existingComment = await github.findComment(owner, repo, pr.number, '<!-- noodl-preview -->');
if (existingComment) {
await github.updateComment(owner, repo, existingComment.id, commentBody);
} else {
await github.createComment(owner, repo, pr.number, commentBody);
}
}
private generatePRComment(preview: PreviewDeployment): string {
return `<!-- noodl-preview -->
## 🚀 Noodl Preview Deployment
| Status | URL |
|--------|-----|
| ${this.getStatusEmoji(preview.status)} ${preview.status} | [${preview.url}](${preview.url}) |
**Branch:** \`${preview.branch}\`
**Commit:** \`${preview.commitSha.substring(0, 7)}\`
**Updated:** ${new Date(preview.createdAt).toLocaleString()}
---
<sub>Deployed automatically by Noodl</sub>`;
}
5. UI Components
Preview Manager Panel
┌─────────────────────────────────────────────────────────────────────┐
│ Preview Deployments [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ACTIVE PREVIEWS (3) │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 🌿 feature/new-dashboard [Copy URL] │ │
│ │ https://feature-new-dashboard--myapp.netlify.app │ │
│ │ Updated 10 minutes ago • Commit abc1234 │ │
│ │ [Open ↗] [QR Code] [Delete] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 🌿 feature/login-redesign [Copy URL] │ │
│ │ https://feature-login-redesign--myapp.netlify.app │ │
│ │ Updated 2 hours ago • Commit def5678 │ │
│ │ [Open ↗] [QR Code] [Delete] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 🌿 bugfix/form-validation [Copy URL] │ │
│ │ https://bugfix-form-validation--myapp.netlify.app │ │
│ │ Updated yesterday • Commit ghi9012 │ │
│ │ [Open ↗] [QR Code] [Delete] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ SETTINGS │
│ ☑ Auto-deploy branches │
│ ☑ Comment preview URL on PRs │
│ Exclude branches: main, master │
│ Auto-delete after: [7 days ▾] │
│ │
│ [+ Create Manual Preview] │
└─────────────────────────────────────────────────────────────────────┘
QR Code Modal
┌─────────────────────────────────────────────────────────────────────┐
│ Share Preview [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄│ │
│ │ █ █ █ █│ │
│ │ █ ███ █ █ ███ █│ Scan to open │
│ │ █ █ █ █│ on mobile │
│ │ ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀│ │
│ └─────────────────┘ │
│ │
│ feature/new-dashboard │
│ https://feature-new-dashboard--myapp.netlify.app │
│ │
│ [Copy URL] [Download QR] [Close] │
└─────────────────────────────────────────────────────────────────────┘
Create Manual Preview
┌─────────────────────────────────────────────────────────────────────┐
│ Create Preview [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Preview Name (optional): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ client-review-dec-15 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Expires in: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 7 days [▾] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ☐ Password protect │
│ Password: [•••••••• ] │
│ │
│ Deploy from: │
│ ○ Current state (uncommitted changes included) │
│ ● Current branch (feature/new-dashboard) │
│ ○ Specific commit: [abc1234 ▾] │
│ │
│ [Cancel] [Create Preview] │
└─────────────────────────────────────────────────────────────────────┘
Files to Create
packages/noodl-editor/src/editor/src/services/PreviewDeployService.tspackages/noodl-core-ui/src/components/deploy/PreviewManager/PreviewManager.tsxpackages/noodl-core-ui/src/components/deploy/PreviewCard/PreviewCard.tsxpackages/noodl-core-ui/src/components/deploy/CreatePreviewModal/CreatePreviewModal.tsxpackages/noodl-core-ui/src/components/deploy/QRCodeModal/QRCodeModal.tsxpackages/noodl-core-ui/src/components/deploy/PreviewSettings/PreviewSettings.tsx
Files to Modify
-
packages/noodl-editor/src/editor/src/services/DeployService.ts- Add preview deployment methods
- Integrate with deploy providers
-
packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx- Add "Previews" tab
- Integrate PreviewManager
-
packages/noodl-editor/src/editor/src/models/projectmodel.ts- Store preview configuration
- Track active previews
-
packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts- Add PR comment methods
- Add PR lookup by branch
Implementation Steps
Phase 1: Preview Service
- Create PreviewDeployService
- Implement preview creation
- Implement preview deletion
- Add configuration storage
Phase 2: Git Integration
- Hook into push events
- Hook into branch delete events
- Implement auto-deployment
- Test with branches
Phase 3: PR Integration
- Implement PR comment creation
- Implement comment updating
- Add status emoji handling
- Test with GitHub PRs
Phase 4: UI - Preview Manager
- Create PreviewManager component
- Create PreviewCard component
- Add copy/share functionality
- Implement delete action
Phase 5: UI - Create Preview
- Create CreatePreviewModal
- Add expiration options
- Add password protection
- Add source selection
Phase 6: UI - QR & Sharing
- Create QRCodeModal
- Add QR code generation
- Add download option
- Polish sharing UX
Testing Checklist
- Auto-preview on push works
- Preview URL is correct
- PR comment created
- PR comment updated on new commit
- Manual preview creation works
- Preview deletion works
- Auto-cleanup works
- QR code generates correctly
- Password protection works
- Expiration works
- Multiple previews supported
Dependencies
- DEPLOY-001 (One-Click Deploy) - for deploy providers
- GIT-001 (GitHub OAuth) - for PR comments
- GIT-002 (Git Status) - for branch awareness
Blocked By
- DEPLOY-001
Blocks
- None
Estimated Effort
- Preview service: 5-6 hours
- Git integration: 4-5 hours
- PR integration: 3-4 hours
- UI preview manager: 4-5 hours
- UI create preview: 3-4 hours
- UI QR/sharing: 2-3 hours
- Testing & polish: 3-4 hours
- Total: 24-31 hours
Success Criteria
- Auto-preview deploys on branch push
- Preview URL unique per branch
- PR comments posted automatically
- Manual previews can be created
- QR codes work for mobile testing
- Expired previews auto-cleaned
Platform-Specific Implementation
Netlify
- Branch deploys built-in
- URL pattern:
branch--site.netlify.app - Easy configuration
Vercel
- Preview deployments automatic
- URL pattern:
project-branch-hash.vercel.app - Good GitHub integration
GitHub Pages
- Need separate approach (subdirectory or deploy to different branch)
- Less native support for branch previews
Future Enhancements
- Visual regression testing
- Screenshot comparison
- Performance metrics per preview
- A/B testing setup
- Preview environments (staging, QA)
- Slack/Teams notifications