Started tasks to migrate runtime to React 19. Added phase 3 projects

This commit is contained in:
Richard Osborne
2025-12-13 22:37:44 +01:00
parent 8dd4f395c0
commit 1477a29ff7
55 changed files with 49205 additions and 281 deletions

View File

@@ -0,0 +1,579 @@
# DEPLOY-001: One-Click Deploy Integrations
## Overview
Add one-click deployment to popular hosting platforms (Netlify, Vercel, GitHub Pages). Users can deploy their frontend directly from the editor without manual file handling or CLI tools.
## Context
Currently, deployment requires:
1. Deploy to local folder
2. Manually upload to hosting platform
3. Configure hosting settings separately
4. Repeat for every deployment
This friction discourages frequent deployments and makes it harder for non-technical users to share their work.
### Existing Infrastructure
From `deployer.ts`:
```typescript
export async function deployToFolder({
project,
direntry,
environment,
baseUrl,
envVariables,
runtimeType = 'deploy'
}: DeployToFolderOptions)
```
From `compilation.ts`:
```typescript
class Compilation {
deployToFolder(direntry, options): Promise<void>;
// Build scripts for pre/post deploy
}
```
From `DeployToFolderTab.tsx`:
- Current UI for folder selection
- Environment selection dropdown
## Requirements
### Functional Requirements
1. **Platform Integrations**
- Netlify (OAuth + API)
- Vercel (OAuth + API)
- GitHub Pages (via GitHub API)
- Cloudflare Pages (OAuth + API)
2. **Deploy Flow**
- One-click deploy from editor
- Platform selection dropdown
- Site/project selection or creation
- Environment variables configuration
- Deploy progress indication
3. **Site Management**
- List user's sites on each platform
- Create new site from editor
- Link project to existing site
- View deployment history
4. **Configuration**
- Environment variables per platform
- Custom domain display
- Build settings (if needed)
- Deploy hooks
5. **Status & History**
- Deploy status in editor
- Link to live site
- Deployment history
- Rollback option (if supported)
### Non-Functional Requirements
- Deploy completes in < 2 minutes
- Works with existing deploy-to-folder logic
- Secure token storage
- Clear error messages
## Technical Approach
### 1. Deploy Service Architecture
```typescript
// packages/noodl-editor/src/editor/src/services/DeployService.ts
interface DeployTarget {
id: string;
name: string;
platform: DeployPlatform;
siteId: string;
siteName: string;
url: string;
customDomain?: string;
lastDeployedAt?: string;
envVariables?: Record<string, string>;
}
enum DeployPlatform {
NETLIFY = 'netlify',
VERCEL = 'vercel',
GITHUB_PAGES = 'github_pages',
CLOUDFLARE = 'cloudflare',
LOCAL_FOLDER = 'local_folder'
}
interface DeployResult {
success: boolean;
deployId: string;
url: string;
buildTime: number;
error?: string;
}
class DeployService {
private static instance: DeployService;
private providers: Map<DeployPlatform, DeployProvider> = new Map();
// Provider management
registerProvider(provider: DeployProvider): void;
getProvider(platform: DeployPlatform): DeployProvider;
// Authentication
async authenticate(platform: DeployPlatform): Promise<void>;
async disconnect(platform: DeployPlatform): Promise<void>;
isAuthenticated(platform: DeployPlatform): boolean;
// Site management
async listSites(platform: DeployPlatform): Promise<Site[]>;
async createSite(platform: DeployPlatform, name: string): Promise<Site>;
async linkSite(project: ProjectModel, target: DeployTarget): Promise<void>;
// Deployment
async deploy(project: ProjectModel, target: DeployTarget): Promise<DeployResult>;
async getDeployStatus(deployId: string): Promise<DeployStatus>;
async getDeployHistory(target: DeployTarget): Promise<Deployment[]>;
}
```
### 2. Deploy Provider Interface
```typescript
// packages/noodl-editor/src/editor/src/services/deploy/DeployProvider.ts
interface DeployProvider {
readonly platform: DeployPlatform;
readonly name: string;
readonly icon: string;
// Authentication
authenticate(): Promise<AuthResult>;
disconnect(): Promise<void>;
isAuthenticated(): boolean;
getUser(): Promise<User | null>;
// Sites
listSites(): Promise<Site[]>;
createSite(name: string, options?: CreateSiteOptions): Promise<Site>;
deleteSite(siteId: string): Promise<void>;
// Deployment
deploy(siteId: string, files: DeployFiles): Promise<DeployResult>;
getDeployStatus(deployId: string): Promise<DeployStatus>;
getDeployHistory(siteId: string): Promise<Deployment[]>;
cancelDeploy(deployId: string): Promise<void>;
// Configuration
getEnvVariables(siteId: string): Promise<Record<string, string>>;
setEnvVariables(siteId: string, vars: Record<string, string>): Promise<void>;
}
```
### 3. Netlify Provider
```typescript
// packages/noodl-editor/src/editor/src/services/deploy/providers/NetlifyProvider.ts
class NetlifyProvider implements DeployProvider {
platform = DeployPlatform.NETLIFY;
name = 'Netlify';
icon = 'netlify-icon.svg';
private clientId = 'YOUR_NETLIFY_CLIENT_ID';
private redirectUri = 'noodl://netlify-callback';
private token: string | null = null;
async authenticate(): Promise<AuthResult> {
// OAuth flow
const authUrl = `https://app.netlify.com/authorize?` +
`client_id=${this.clientId}&` +
`response_type=token&` +
`redirect_uri=${encodeURIComponent(this.redirectUri)}`;
// Open in browser, handle callback via deep link
const token = await this.handleOAuthCallback(authUrl);
this.token = token;
await this.storeToken(token);
return { success: true };
}
async listSites(): Promise<Site[]> {
const response = await fetch('https://api.netlify.com/api/v1/sites', {
headers: { Authorization: `Bearer ${this.token}` }
});
const sites = await response.json();
return sites.map(s => ({
id: s.id,
name: s.name,
url: s.ssl_url || s.url,
customDomain: s.custom_domain,
updatedAt: s.updated_at
}));
}
async createSite(name: string): Promise<Site> {
const response = await fetch('https://api.netlify.com/api/v1/sites', {
method: 'POST',
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name })
});
const site = await response.json();
return {
id: site.id,
name: site.name,
url: site.ssl_url || site.url
};
}
async deploy(siteId: string, files: DeployFiles): Promise<DeployResult> {
// Create deploy
const deploy = await this.createDeploy(siteId);
// Upload files using Netlify's digest-based upload
const fileHashes = await this.calculateHashes(files);
const required = await this.getRequiredFiles(deploy.id, fileHashes);
for (const file of required) {
await this.uploadFile(deploy.id, file);
}
// Finalize deploy
return await this.finalizeDeploy(deploy.id);
}
}
```
### 4. Vercel Provider
```typescript
// packages/noodl-editor/src/editor/src/services/deploy/providers/VercelProvider.ts
class VercelProvider implements DeployProvider {
platform = DeployPlatform.VERCEL;
name = 'Vercel';
icon = 'vercel-icon.svg';
private clientId = 'YOUR_VERCEL_CLIENT_ID';
private token: string | null = null;
async authenticate(): Promise<AuthResult> {
// Vercel uses OAuth 2.0
const state = this.generateState();
const authUrl = `https://vercel.com/integrations/noodl/new?` +
`state=${state}`;
const token = await this.handleOAuthCallback(authUrl);
this.token = token;
return { success: true };
}
async deploy(projectId: string, files: DeployFiles): Promise<DeployResult> {
// Vercel deployment API
const deployment = await fetch('https://api.vercel.com/v13/deployments', {
method: 'POST',
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: projectId,
files: await this.prepareFiles(files),
projectSettings: {
framework: null // Static site
}
})
});
const result = await deployment.json();
return {
success: true,
deployId: result.id,
url: `https://${result.url}`,
buildTime: 0
};
}
}
```
### 5. GitHub Pages Provider
```typescript
// packages/noodl-editor/src/editor/src/services/deploy/providers/GitHubPagesProvider.ts
class GitHubPagesProvider implements DeployProvider {
platform = DeployPlatform.GITHUB_PAGES;
name = 'GitHub Pages';
icon = 'github-icon.svg';
async authenticate(): Promise<AuthResult> {
// Reuse GitHub OAuth from GIT-001
const githubService = GitHubOAuthService.instance;
if (!githubService.isAuthenticated()) {
await githubService.authenticate();
}
return { success: true };
}
async listSites(): Promise<Site[]> {
// List repos with GitHub Pages enabled
const repos = await this.githubApi.listRepos();
const pagesRepos = repos.filter(r => r.has_pages);
return pagesRepos.map(r => ({
id: r.full_name,
name: r.name,
url: `https://${r.owner.login}.github.io/${r.name}`,
repo: r.full_name
}));
}
async deploy(repoFullName: string, files: DeployFiles): Promise<DeployResult> {
const [owner, repo] = repoFullName.split('/');
// Create/update gh-pages branch
const branch = 'gh-pages';
// Get current tree (if exists)
let baseTree: string | null = null;
try {
const ref = await this.githubApi.getRef(owner, repo, `heads/${branch}`);
const commit = await this.githubApi.getCommit(owner, repo, ref.object.sha);
baseTree = commit.tree.sha;
} catch {
// Branch doesn't exist yet
}
// Create blobs for all files
const tree = await this.createTree(owner, repo, files, baseTree);
// Create commit
const commit = await this.githubApi.createCommit(owner, repo, {
message: 'Deploy from Noodl',
tree: tree.sha,
parents: baseTree ? [baseTree] : []
});
// Update branch reference
await this.githubApi.updateRef(owner, repo, `heads/${branch}`, commit.sha);
return {
success: true,
deployId: commit.sha,
url: `https://${owner}.github.io/${repo}`,
buildTime: 0
};
}
}
```
### 6. UI Components
#### Deploy Panel
```
┌─────────────────────────────────────────────────────────────────────┐
│ Deploy [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ DEPLOY TARGET │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 🌐 Netlify [▾] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ SITE │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ my-noodl-app [▾] │ │
│ │ https://my-noodl-app.netlify.app │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ [+ Create New Site] │
│ │
│ ENVIRONMENT │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Production [▾] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Last deployed: 2 hours ago │ │
│ │ Deploy time: 45 seconds │ │
│ │ [View Site ↗] [View Deploy History] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [🚀 Deploy Now] │
└─────────────────────────────────────────────────────────────────────┘
```
#### Deploy Progress
```
┌─────────────────────────────────────────────────────────────────────┐
│ Deploying to Netlify... │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 35% │
│ │
│ ✓ Building project │
│ ✓ Exporting files (127 files) │
│ ◐ Uploading to Netlify... │
│ ○ Finalizing deploy │
│ │
│ [Cancel] │
└─────────────────────────────────────────────────────────────────────┘
```
## Files to Create
1. `packages/noodl-editor/src/editor/src/services/DeployService.ts`
2. `packages/noodl-editor/src/editor/src/services/deploy/DeployProvider.ts`
3. `packages/noodl-editor/src/editor/src/services/deploy/providers/NetlifyProvider.ts`
4. `packages/noodl-editor/src/editor/src/services/deploy/providers/VercelProvider.ts`
5. `packages/noodl-editor/src/editor/src/services/deploy/providers/GitHubPagesProvider.ts`
6. `packages/noodl-editor/src/editor/src/services/deploy/providers/CloudflareProvider.ts`
7. `packages/noodl-core-ui/src/components/deploy/DeployPanel/DeployPanel.tsx`
8. `packages/noodl-core-ui/src/components/deploy/DeployProgress/DeployProgress.tsx`
9. `packages/noodl-core-ui/src/components/deploy/SiteSelector/SiteSelector.tsx`
10. `packages/noodl-core-ui/src/components/deploy/PlatformSelector/PlatformSelector.tsx`
## Files to Modify
1. `packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx`
- Add platform tabs
- Integrate new deploy flow
2. `packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts`
- Add deploy to platform method
- Hook into build scripts
3. `packages/noodl-editor/src/main/src/main.js`
- Add deep link handlers for OAuth callbacks
4. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
- Store deploy target configuration
## Implementation Steps
### Phase 1: Service Architecture
1. Create DeployService
2. Define DeployProvider interface
3. Implement provider registration
4. Set up token storage
### Phase 2: Netlify Integration
1. Implement NetlifyProvider
2. Add OAuth flow
3. Implement site listing
4. Implement deployment
### Phase 3: Vercel Integration
1. Implement VercelProvider
2. Add OAuth flow
3. Implement deployment
### Phase 4: GitHub Pages Integration
1. Implement GitHubPagesProvider
2. Reuse GitHub OAuth
3. Implement gh-pages deployment
### Phase 5: UI Components
1. Create DeployPanel
2. Create platform/site selectors
3. Create progress indicator
4. Integrate with existing popup
### Phase 6: Testing & Polish
1. Test each provider
2. Error handling
3. Progress accuracy
4. Deploy history
## Testing Checklist
- [ ] Netlify OAuth works
- [ ] Netlify site listing works
- [ ] Netlify deployment succeeds
- [ ] Vercel OAuth works
- [ ] Vercel deployment succeeds
- [ ] GitHub Pages deployment works
- [ ] Progress indicator accurate
- [ ] Error messages helpful
- [ ] Deploy history shows
- [ ] Site links work
- [ ] Token storage secure
- [ ] Disconnect works
## Dependencies
- GIT-001 (GitHub OAuth) - for GitHub Pages
## Blocked By
- None (can start immediately)
## Blocks
- DEPLOY-002 (Preview Deployments)
- DEPLOY-003 (Deploy Settings)
## Estimated Effort
- Service architecture: 4-5 hours
- Netlify provider: 5-6 hours
- Vercel provider: 4-5 hours
- GitHub Pages provider: 4-5 hours
- Cloudflare provider: 4-5 hours
- UI components: 5-6 hours
- Testing & polish: 4-5 hours
- **Total: 30-37 hours**
## Success Criteria
1. One-click deploy to Netlify works
2. One-click deploy to Vercel works
3. One-click deploy to GitHub Pages works
4. Site creation from editor works
5. Deploy progress visible
6. Deploy history accessible
## Platform-Specific Notes
### Netlify
- Uses digest-based uploads (efficient)
- Supports deploy previews (branch deploys)
- Has good API documentation
- Free tier: 100GB bandwidth/month
### Vercel
- File-based deployment API
- Automatic HTTPS
- Edge functions support
- Free tier: 100GB bandwidth/month
### GitHub Pages
- No OAuth app needed (reuse GitHub)
- Limited to public repos on free tier
- Jekyll processing (can disable with .nojekyll)
- Free for public repos
### Cloudflare Pages
- Similar to Netlify/Vercel
- Global CDN
- Free tier generous
## Future Enhancements
- AWS S3 + CloudFront
- Firebase Hosting
- Surge.sh
- Custom server deployment (SFTP/SSH)
- Docker container deployment

View File

@@ -0,0 +1,510 @@
# 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:
1. Manual deploy to a staging site
2. Share URL with stakeholders
3. Remember which deploy corresponds to which version
4. 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
1. **Automatic Previews**
- Deploy preview on branch push
- Unique URL per branch
- Update preview on new commits
- Delete preview on branch delete
2. **Manual Previews**
- "Deploy Preview" button in editor
- Generate shareable URL
- Named previews (optional)
- Expiration settings
3. **Preview Management**
- List all active previews
- View preview URL
- Delete individual previews
- Set auto-cleanup rules
4. **Sharing**
- Copy preview URL
- QR code for mobile
- Optional password protection
- Expiration timer
5. **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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
1. `packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts`
2. `packages/noodl-core-ui/src/components/deploy/PreviewManager/PreviewManager.tsx`
3. `packages/noodl-core-ui/src/components/deploy/PreviewCard/PreviewCard.tsx`
4. `packages/noodl-core-ui/src/components/deploy/CreatePreviewModal/CreatePreviewModal.tsx`
5. `packages/noodl-core-ui/src/components/deploy/QRCodeModal/QRCodeModal.tsx`
6. `packages/noodl-core-ui/src/components/deploy/PreviewSettings/PreviewSettings.tsx`
## Files to Modify
1. `packages/noodl-editor/src/editor/src/services/DeployService.ts`
- Add preview deployment methods
- Integrate with deploy providers
2. `packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx`
- Add "Previews" tab
- Integrate PreviewManager
3. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
- Store preview configuration
- Track active previews
4. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts`
- Add PR comment methods
- Add PR lookup by branch
## Implementation Steps
### Phase 1: Preview Service
1. Create PreviewDeployService
2. Implement preview creation
3. Implement preview deletion
4. Add configuration storage
### Phase 2: Git Integration
1. Hook into push events
2. Hook into branch delete events
3. Implement auto-deployment
4. Test with branches
### Phase 3: PR Integration
1. Implement PR comment creation
2. Implement comment updating
3. Add status emoji handling
4. Test with GitHub PRs
### Phase 4: UI - Preview Manager
1. Create PreviewManager component
2. Create PreviewCard component
3. Add copy/share functionality
4. Implement delete action
### Phase 5: UI - Create Preview
1. Create CreatePreviewModal
2. Add expiration options
3. Add password protection
4. Add source selection
### Phase 6: UI - QR & Sharing
1. Create QRCodeModal
2. Add QR code generation
3. Add download option
4. 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
1. Auto-preview deploys on branch push
2. Preview URL unique per branch
3. PR comments posted automatically
4. Manual previews can be created
5. QR codes work for mobile testing
6. 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

View File

@@ -0,0 +1,533 @@
# DEPLOY-003: Deploy Settings & Environment Variables
## Overview
Provide comprehensive deployment configuration including environment variables, build settings, custom domains, and deployment rules. Users can manage different environments (development, staging, production) with different configurations.
## Context
Currently:
- Environment variables set per deploy manually
- No persistent environment configuration
- No distinction between environments
- Custom domain setup requires external configuration
This task adds:
- Persistent environment variable management
- Multiple environment profiles
- Custom domain configuration
- Build optimization settings
- Deploy rules and triggers
## Requirements
### Functional Requirements
1. **Environment Variables**
- Add/edit/delete variables
- Sensitive variable masking
- Import from .env file
- Export to .env file
- Variable validation
2. **Environment Profiles**
- Development, Staging, Production presets
- Custom profiles
- Variables per profile
- Easy switching
3. **Custom Domains**
- View current domains
- Add custom domain
- SSL certificate status
- DNS configuration help
4. **Build Settings**
- Output directory
- Base URL configuration
- Asset optimization
- Source maps (dev only)
5. **Deploy Rules**
- Auto-deploy on push
- Branch-based rules
- Deploy schedule
- Deploy hooks/webhooks
### Non-Functional Requirements
- Variables encrypted at rest
- Sensitive values never logged
- Sync with platform settings
- Works offline (cached)
## Technical Approach
### 1. Environment Configuration Service
```typescript
// packages/noodl-editor/src/editor/src/services/EnvironmentConfigService.ts
interface EnvironmentVariable {
key: string;
value: string;
sensitive: boolean; // Masked in UI
scope: VariableScope;
}
enum VariableScope {
BUILD = 'build', // Available during build
RUNTIME = 'runtime', // Injected into app
BOTH = 'both'
}
interface EnvironmentProfile {
id: string;
name: string;
description?: string;
variables: EnvironmentVariable[];
isDefault: boolean;
platform?: DeployPlatform; // If linked to a platform
}
interface DeploySettings {
outputDirectory: string;
baseUrl: string;
assetOptimization: boolean;
sourceMaps: boolean;
cleanUrls: boolean;
trailingSlash: boolean;
}
class EnvironmentConfigService {
private static instance: EnvironmentConfigService;
// Profiles
async getProfiles(projectId: string): Promise<EnvironmentProfile[]>;
async createProfile(projectId: string, profile: Omit<EnvironmentProfile, 'id'>): Promise<EnvironmentProfile>;
async updateProfile(projectId: string, profileId: string, updates: Partial<EnvironmentProfile>): Promise<void>;
async deleteProfile(projectId: string, profileId: string): Promise<void>;
// Variables
async getVariables(projectId: string, profileId: string): Promise<EnvironmentVariable[]>;
async setVariable(projectId: string, profileId: string, variable: EnvironmentVariable): Promise<void>;
async deleteVariable(projectId: string, profileId: string, key: string): Promise<void>;
async importFromEnvFile(projectId: string, profileId: string, content: string): Promise<void>;
async exportToEnvFile(projectId: string, profileId: string): Promise<string>;
// Build settings
async getDeploySettings(projectId: string): Promise<DeploySettings>;
async updateDeploySettings(projectId: string, settings: Partial<DeploySettings>): Promise<void>;
// Platform sync
async syncWithPlatform(projectId: string, profileId: string, platform: DeployPlatform): Promise<void>;
async pullFromPlatform(projectId: string, platform: DeployPlatform): Promise<EnvironmentVariable[]>;
}
```
### 2. Environment Storage
```typescript
// Store in project metadata, encrypted
interface ProjectDeployConfig {
profiles: EnvironmentProfile[];
activeProfileId: string;
deploySettings: DeploySettings;
domains: CustomDomain[];
deployRules: DeployRule[];
}
// Encryption for sensitive values
class SecureStorage {
async encrypt(value: string): Promise<string>;
async decrypt(value: string): Promise<string>;
}
// Store encrypted in project.json
{
"metadata": {
"deployConfig": {
"profiles": [
{
"id": "prod",
"name": "Production",
"variables": [
{
"key": "API_URL",
"value": "https://api.example.com", // Plain text
"sensitive": false
},
{
"key": "API_KEY",
"value": "encrypted:abc123...", // Encrypted
"sensitive": true
}
]
}
]
}
}
}
```
### 3. Domain Configuration
```typescript
interface CustomDomain {
domain: string;
platform: DeployPlatform;
siteId: string;
status: DomainStatus;
sslStatus: SSLStatus;
dnsRecords?: DNSRecord[];
}
enum DomainStatus {
PENDING = 'pending',
ACTIVE = 'active',
FAILED = 'failed'
}
enum SSLStatus {
PENDING = 'pending',
ACTIVE = 'active',
EXPIRED = 'expired',
FAILED = 'failed'
}
interface DNSRecord {
type: 'A' | 'CNAME' | 'TXT';
name: string;
value: string;
required: boolean;
}
class DomainService {
async addDomain(siteId: string, domain: string): Promise<CustomDomain>;
async verifyDomain(domainId: string): Promise<DomainStatus>;
async getDNSInstructions(domain: string): Promise<DNSRecord[]>;
async checkSSL(domainId: string): Promise<SSLStatus>;
}
```
### 4. Deploy Rules
```typescript
interface DeployRule {
id: string;
name: string;
enabled: boolean;
trigger: DeployTrigger;
conditions: DeployCondition[];
actions: DeployAction[];
}
interface DeployTrigger {
type: 'push' | 'schedule' | 'manual' | 'webhook';
config: PushConfig | ScheduleConfig | WebhookConfig;
}
interface PushConfig {
branches: string[]; // Glob patterns
paths?: string[]; // Only deploy if these paths changed
}
interface ScheduleConfig {
cron: string; // Cron expression
timezone: string;
}
interface DeployCondition {
type: 'branch' | 'tag' | 'path' | 'message';
operator: 'equals' | 'contains' | 'matches';
value: string;
}
interface DeployAction {
type: 'deploy' | 'notify' | 'webhook';
config: DeployActionConfig | NotifyConfig | WebhookConfig;
}
```
### 5. UI Components
#### Environment Variables Panel
```
┌─────────────────────────────────────────────────────────────────────┐
│ Environment Variables [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Profile: [Production ▾] [+ New Profile] │
│ │
│ VARIABLES [Import .env] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Key Value Scope [⋯] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ API_URL https://api.example.com Runtime [✎🗑] │ │
│ │ API_KEY •••••••••••••••••••••• Runtime [✎🗑] │ │
│ │ ANALYTICS_ID UA-12345678-1 Runtime [✎🗑] │ │
│ │ DEBUG false Build [✎🗑] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [+ Add Variable] [Export .env] │
│ │
│ ☑ Sync with Netlify │
│ Last synced: 5 minutes ago [Sync Now] │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
#### Build Settings Panel
```
┌─────────────────────────────────────────────────────────────────────┐
│ Build Settings [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ OUTPUT │
│ Output Directory: [dist ] │
│ Base URL: [/ ] │
│ │
│ OPTIMIZATION │
│ ☑ Optimize assets (minify JS/CSS) │
│ ☐ Generate source maps (increases build size) │
│ ☑ Clean URLs (remove .html extension) │
│ ☐ Trailing slash on URLs │
│ │
│ ADVANCED │
│ Build Command: [npm run build ] │
│ Publish Directory: [build ] │
│ │
│ NODE VERSION │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 18 (LTS) [▾] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [Save Settings] │
└─────────────────────────────────────────────────────────────────────┘
```
#### Custom Domains Panel
```
┌─────────────────────────────────────────────────────────────────────┐
│ Custom Domains [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CONNECTED DOMAINS │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 🌐 myapp.com │ │
│ │ ✓ DNS Configured ✓ SSL Active │ │
│ │ Primary domain [Remove] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 🌐 www.myapp.com │ │
│ │ ✓ DNS Configured ✓ SSL Active │ │
│ │ Redirects to myapp.com [Remove] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [+ Add Custom Domain] │
│ │
│ DEFAULT DOMAIN │
│ https://myapp.netlify.app [Copy] │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
#### Add Domain Modal
```
┌─────────────────────────────────────────────────────────────────────┐
│ Add Custom Domain [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Domain: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ app.example.com │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ DNS CONFIGURATION REQUIRED │
│ │
│ Add these records to your DNS provider: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Type Name Value [Copy] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ CNAME app myapp.netlify.app [Copy] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ DNS changes can take up to 48 hours to propagate │
│ │
│ Status: ⏳ Waiting for DNS verification... │
│ │
│ [Cancel] [Verify Domain] [Done] │
└─────────────────────────────────────────────────────────────────────┘
```
#### Deploy Rules Panel
```
┌─────────────────────────────────────────────────────────────────────┐
│ Deploy Rules [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ RULES [+ Add Rule] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ☑ Auto-deploy production │ │
│ │ When: Push to main │ │
│ │ Deploy to: Production [Edit 🗑] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ ☑ Preview branches │ │
│ │ When: Push to feature/* │ │
│ │ Deploy to: Preview [Edit 🗑] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ ☐ Scheduled deploy │ │
│ │ When: Daily at 2:00 AM UTC │ │
│ │ Deploy to: Production [Edit 🗑] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ WEBHOOKS [+ Add Webhook] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Build hook URL: │ │
│ │ https://api.netlify.com/build_hooks/abc123 [Copy] [🔄] │ │
│ │ Trigger: POST request to this URL │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
## Files to Create
1. `packages/noodl-editor/src/editor/src/services/EnvironmentConfigService.ts`
2. `packages/noodl-editor/src/editor/src/services/DomainService.ts`
3. `packages/noodl-editor/src/editor/src/services/DeployRulesService.ts`
4. `packages/noodl-core-ui/src/components/deploy/EnvironmentVariables/EnvironmentVariables.tsx`
5. `packages/noodl-core-ui/src/components/deploy/EnvironmentVariables/VariableRow.tsx`
6. `packages/noodl-core-ui/src/components/deploy/EnvironmentVariables/ProfileSelector.tsx`
7. `packages/noodl-core-ui/src/components/deploy/BuildSettings/BuildSettings.tsx`
8. `packages/noodl-core-ui/src/components/deploy/CustomDomains/CustomDomains.tsx`
9. `packages/noodl-core-ui/src/components/deploy/CustomDomains/AddDomainModal.tsx`
10. `packages/noodl-core-ui/src/components/deploy/DeployRules/DeployRules.tsx`
11. `packages/noodl-core-ui/src/components/deploy/DeployRules/RuleEditor.tsx`
## Files to Modify
1. `packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx`
- Add settings tabs
- Integrate new panels
2. `packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts`
- Use environment variables from config
- Apply build settings
3. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
- Store deploy configuration
- Load/save config
4. `packages/noodl-editor/src/editor/src/services/DeployService.ts`
- Apply environment variables to deploy
- Handle domain configuration
## Implementation Steps
### Phase 1: Environment Variables
1. Create EnvironmentConfigService
2. Implement variable storage
3. Implement encryption for sensitive values
4. Add import/export .env
### Phase 2: Environment Profiles
1. Add profile management
2. Implement profile switching
3. Add default profiles (dev/staging/prod)
4. UI for profile management
### Phase 3: UI - Variables Panel
1. Create EnvironmentVariables component
2. Create VariableRow component
3. Add add/edit/delete functionality
4. Add import/export buttons
### Phase 4: Build Settings
1. Create build settings storage
2. Create BuildSettings component
3. Integrate with compilation
4. Test with deployments
### Phase 5: Custom Domains
1. Create DomainService
2. Implement platform-specific domain APIs
3. Create CustomDomains component
4. Create AddDomainModal
### Phase 6: Deploy Rules
1. Create DeployRulesService
2. Implement rule evaluation
3. Create DeployRules component
4. Create RuleEditor
## Testing Checklist
- [ ] Variables saved correctly
- [ ] Sensitive values encrypted
- [ ] Variables applied to deploy
- [ ] Import .env works
- [ ] Export .env works
- [ ] Profile switching works
- [ ] Build settings applied
- [ ] Custom domain setup works
- [ ] DNS verification works
- [ ] Deploy rules trigger correctly
- [ ] Webhooks work
- [ ] Platform sync works
## Dependencies
- DEPLOY-001 (One-Click Deploy) - for platform integration
## Blocked By
- DEPLOY-001
## Blocks
- None (final DEPLOY task)
## Estimated Effort
- Environment config service: 4-5 hours
- Variable storage/encryption: 3-4 hours
- Environment profiles: 3-4 hours
- UI variables panel: 4-5 hours
- Build settings: 3-4 hours
- Custom domains: 4-5 hours
- Deploy rules: 4-5 hours
- Testing & polish: 3-4 hours
- **Total: 28-36 hours**
## Success Criteria
1. Environment variables persist across deploys
2. Sensitive values properly secured
3. Multiple profiles supported
4. Import/export .env works
5. Custom domains configurable
6. Deploy rules automate deployments
7. Settings sync with platforms
## Security Considerations
- Sensitive values encrypted at rest
- Never log sensitive values
- Use platform-native secret storage where available
- Clear memory after use
- Validate input to prevent injection
## Future Enhancements
- Environment variable inheritance
- Secret rotation reminders
- Integration with secret managers (Vault, AWS Secrets)
- A/B testing configuration
- Feature flags integration
- Monitoring/alerting integration

View File

@@ -0,0 +1,385 @@
# DEPLOY Series: Deployment Automation
## Overview
The DEPLOY series transforms Noodl's deployment from manual folder exports into a modern, automated deployment pipeline. Users can deploy to popular hosting platforms with one click, get automatic preview URLs for each branch, and manage environment variables and domains directly from the editor.
## Target Environment
- **Editor**: React 19 version only
- **Platforms**: Netlify, Vercel, GitHub Pages, Cloudflare Pages
- **Backwards Compatibility**: Existing deploy-to-folder continues to work
## Task Dependency Graph
```
DEPLOY-001 (One-Click Deploy)
├─────────────────────┐
↓ ↓
DEPLOY-002 (Previews) DEPLOY-003 (Settings)
```
## Task Summary
| Task ID | Name | Est. Hours | Priority |
|---------|------|------------|----------|
| DEPLOY-001 | One-Click Deploy Integrations | 30-37 | Critical |
| DEPLOY-002 | Preview Deployments | 24-31 | High |
| DEPLOY-003 | Deploy Settings & Environment Variables | 28-36 | High |
**Total Estimated: 82-104 hours**
## Implementation Order
### Phase 1: Core Deployment (Weeks 1-2)
1. **DEPLOY-001** - One-click deploy to platforms
- Establishes provider architecture
- OAuth flows for each platform
- Core deployment functionality
### Phase 2: Preview & Settings (Weeks 3-4)
2. **DEPLOY-002** - Preview deployments
- Branch-based previews
- PR integration
- Sharing features
3. **DEPLOY-003** - Deploy settings
- Environment variables
- Custom domains
- Deploy rules
## Existing Infrastructure
### Deployer
```typescript
// packages/noodl-editor/src/editor/src/utils/compilation/build/deployer.ts
export async function deployToFolder({
project,
direntry,
environment,
baseUrl,
envVariables,
runtimeType = 'deploy'
}: DeployToFolderOptions)
```
### Compilation
```typescript
// packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts
class Compilation {
deployToFolder(direntry, options): Promise<void>;
// Build scripts system for pre/post deploy
}
```
### Deploy UI
```typescript
// Current deploy popup with folder selection
DeployToFolderTab.tsx
DeployPopup.tsx
```
### Cloud Services
```typescript
// packages/noodl-editor/src/editor/src/models/CloudServices.ts
interface Environment {
id: string;
name: string;
url: string;
appId: string;
masterKey?: string;
}
```
## New Architecture
### Service Layer
```
packages/noodl-editor/src/editor/src/services/
├── DeployService.ts # Central deployment service
├── PreviewDeployService.ts # Preview management
├── EnvironmentConfigService.ts # Env vars & profiles
├── DomainService.ts # Custom domain management
├── DeployRulesService.ts # Automation rules
└── deploy/
├── DeployProvider.ts # Provider interface
└── providers/
├── NetlifyProvider.ts
├── VercelProvider.ts
├── GitHubPagesProvider.ts
└── CloudflareProvider.ts
```
### Provider Interface
```typescript
interface DeployProvider {
readonly platform: DeployPlatform;
readonly name: string;
// Authentication
authenticate(): Promise<AuthResult>;
isAuthenticated(): boolean;
// Sites
listSites(): Promise<Site[]>;
createSite(name: string): Promise<Site>;
// Deployment
deploy(siteId: string, files: DeployFiles): Promise<DeployResult>;
getDeployStatus(deployId: string): Promise<DeployStatus>;
// Configuration
getEnvVariables(siteId: string): Promise<Record<string, string>>;
setEnvVariables(siteId: string, vars: Record<string, string>): Promise<void>;
}
```
## Platform Comparison
| Feature | Netlify | Vercel | GitHub Pages | Cloudflare |
|---------|---------|--------|--------------|------------|
| OAuth | ✓ | ✓ | Via GitHub | ✓ |
| Preview Deploys | ✓ | ✓ | Manual | ✓ |
| Custom Domains | ✓ | ✓ | ✓ | ✓ |
| Env Variables | ✓ | ✓ | Secrets only | ✓ |
| Deploy Hooks | ✓ | ✓ | Actions | ✓ |
| Free Tier | 100GB/mo | 100GB/mo | Unlimited* | 100K/day |
*GitHub Pages: Free for public repos, requires Pro for private
## Key User Flows
### 1. First-Time Deploy
```
User clicks "Deploy"
Select platform (Netlify, Vercel, etc.)
Authenticate with platform (OAuth)
Create new site or select existing
Configure environment variables
Deploy → Get live URL
```
### 2. Subsequent Deploys
```
User clicks "Deploy"
Site already linked
One-click deploy
Progress indicator
Success → Link to site
```
### 3. Preview Workflow
```
User pushes feature branch
Auto-deploy preview
Comment on PR with preview URL
Stakeholder reviews
Merge → Production deploy
Preview auto-cleaned
```
## UI Components to Create
| Component | Package | Purpose |
|-----------|---------|---------|
| DeployPanel | noodl-core-ui | Main deploy interface |
| PlatformSelector | noodl-core-ui | Platform choice |
| SiteSelector | noodl-core-ui | Site choice |
| DeployProgress | noodl-core-ui | Progress indicator |
| PreviewManager | noodl-core-ui | Preview list |
| EnvironmentVariables | noodl-core-ui | Var management |
| CustomDomains | noodl-core-ui | Domain setup |
| DeployRules | noodl-core-ui | Automation rules |
## Data Models
### Deploy Target
```typescript
interface DeployTarget {
id: string;
name: string;
platform: DeployPlatform;
siteId: string;
siteName: string;
url: string;
customDomain?: string;
lastDeployedAt?: string;
}
```
### Preview Deployment
```typescript
interface PreviewDeployment {
id: string;
projectId: string;
branch: string;
commitSha: string;
url: string;
status: PreviewStatus;
createdAt: string;
expiresAt?: string;
}
```
### Environment Profile
```typescript
interface EnvironmentProfile {
id: string;
name: string;
variables: EnvironmentVariable[];
isDefault: boolean;
}
interface EnvironmentVariable {
key: string;
value: string;
sensitive: boolean;
}
```
## Security Considerations
### Token Storage
- OAuth tokens stored in electron safeStorage
- Never logged or displayed
- Cleared on disconnect
### Sensitive Variables
- Encrypted at rest
- Masked in UI (•••••)
- Never exported to .env without warning
### Platform Security
- Minimum OAuth scopes
- Token refresh handling
- Secure redirect handling
## Testing Strategy
### Unit Tests
- Provider method isolation
- Token handling
- File preparation
### Integration Tests
- OAuth flow mocking
- Deploy API mocking
- Full deploy cycle
### Manual Testing
- Real deployments to each platform
- Custom domain setup
- Preview workflow
## Cline Usage Notes
### Before Starting Each Task
1. Review existing deployment infrastructure:
- `deployer.ts`
- `compilation.ts`
- `DeployPopup/`
2. Understand build output:
- How project.json is exported
- How bundles are created
- How index.html is generated
### Key Integration Points
1. **Compilation**: Use existing `deployToFolder` for file preparation
2. **Cloud Services**: Existing environment model can inform design
3. **Git Integration**: Leverage GIT series for branch awareness
### Platform API Notes
- **Netlify**: Uses digest-based upload (SHA1 hashes)
- **Vercel**: File-based deployment API
- **GitHub Pages**: Git-based via GitHub API
- **Cloudflare**: Similar to Netlify/Vercel
## Success Criteria (Series Complete)
1. ✅ One-click deploy to 4 platforms
2. ✅ OAuth authentication flow works
3. ✅ Site creation from editor works
4. ✅ Preview deploys auto-generated
5. ✅ PR comments posted automatically
6. ✅ Environment variables managed
7. ✅ Custom domains configurable
8. ✅ Deploy rules automate workflow
## Future Work (Post-DEPLOY)
The DEPLOY series enables:
- **CI/CD Integration**: Connect to GitHub Actions, GitLab CI
- **Performance Monitoring**: Lighthouse scores per deploy
- **A/B Testing**: Deploy variants to subsets
- **Rollback**: One-click rollback to previous deploy
- **Multi-Region**: Deploy to multiple regions
## Files in This Series
- `DEPLOY-001-one-click-deploy.md`
- `DEPLOY-002-preview-deployments.md`
- `DEPLOY-003-deploy-settings.md`
- `DEPLOY-OVERVIEW.md` (this file)
## External Dependencies
### Platform OAuth
| Platform | OAuth Type | Client ID Required |
|----------|------------|-------------------|
| Netlify | OAuth 2.0 | Yes |
| Vercel | OAuth 2.0 | Yes |
| GitHub | OAuth 2.0 | From GIT-001 |
| Cloudflare | OAuth 2.0 | Yes |
### API Endpoints
- Netlify: `api.netlify.com/api/v1`
- Vercel: `api.vercel.com/v13`
- GitHub: `api.github.com`
- Cloudflare: `api.cloudflare.com/client/v4`
## Complete Roadmap Summary
With the DEPLOY series complete, the full OpenNoodl modernization roadmap includes:
| Series | Tasks | Hours | Focus |
|--------|-------|-------|-------|
| DASH | 4 tasks | 43-63 | Dashboard UX |
| GIT | 5 tasks | 68-96 | Git integration |
| COMP | 6 tasks | 117-155 | Shared components |
| AI | 4 tasks | 121-154 | AI assistance |
| DEPLOY | 3 tasks | 82-104 | Deployment |
**Grand Total: 22 tasks, 431-572 hours**