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-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:
- Deploy to local folder
- Manually upload to hosting platform
- Configure hosting settings separately
- 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:
export async function deployToFolder({
project,
direntry,
environment,
baseUrl,
envVariables,
runtimeType = 'deploy'
}: DeployToFolderOptions)
From compilation.ts:
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
-
Platform Integrations
- Netlify (OAuth + API)
- Vercel (OAuth + API)
- GitHub Pages (via GitHub API)
- Cloudflare Pages (OAuth + API)
-
Deploy Flow
- One-click deploy from editor
- Platform selection dropdown
- Site/project selection or creation
- Environment variables configuration
- Deploy progress indication
-
Site Management
- List user's sites on each platform
- Create new site from editor
- Link project to existing site
- View deployment history
-
Configuration
- Environment variables per platform
- Custom domain display
- Build settings (if needed)
- Deploy hooks
-
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
// 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
// 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
// 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
// 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
// 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
packages/noodl-editor/src/editor/src/services/DeployService.tspackages/noodl-editor/src/editor/src/services/deploy/DeployProvider.tspackages/noodl-editor/src/editor/src/services/deploy/providers/NetlifyProvider.tspackages/noodl-editor/src/editor/src/services/deploy/providers/VercelProvider.tspackages/noodl-editor/src/editor/src/services/deploy/providers/GitHubPagesProvider.tspackages/noodl-editor/src/editor/src/services/deploy/providers/CloudflareProvider.tspackages/noodl-core-ui/src/components/deploy/DeployPanel/DeployPanel.tsxpackages/noodl-core-ui/src/components/deploy/DeployProgress/DeployProgress.tsxpackages/noodl-core-ui/src/components/deploy/SiteSelector/SiteSelector.tsxpackages/noodl-core-ui/src/components/deploy/PlatformSelector/PlatformSelector.tsx
Files to Modify
-
packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx- Add platform tabs
- Integrate new deploy flow
-
packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts- Add deploy to platform method
- Hook into build scripts
-
packages/noodl-editor/src/main/src/main.js- Add deep link handlers for OAuth callbacks
-
packages/noodl-editor/src/editor/src/models/projectmodel.ts- Store deploy target configuration
Implementation Steps
Phase 1: Service Architecture
- Create DeployService
- Define DeployProvider interface
- Implement provider registration
- Set up token storage
Phase 2: Netlify Integration
- Implement NetlifyProvider
- Add OAuth flow
- Implement site listing
- Implement deployment
Phase 3: Vercel Integration
- Implement VercelProvider
- Add OAuth flow
- Implement deployment
Phase 4: GitHub Pages Integration
- Implement GitHubPagesProvider
- Reuse GitHub OAuth
- Implement gh-pages deployment
Phase 5: UI Components
- Create DeployPanel
- Create platform/site selectors
- Create progress indicator
- Integrate with existing popup
Phase 6: Testing & Polish
- Test each provider
- Error handling
- Progress accuracy
- 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
- One-click deploy to Netlify works
- One-click deploy to Vercel works
- One-click deploy to GitHub Pages works
- Site creation from editor works
- Deploy progress visible
- 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