Add Gitea integration for auto-repo creation on project creation
- Add Gitea API client with repo, webhook, commit, and PR methods - Add TypeScript types for Gitea API responses - Update project creation to auto-create Gitea repo with webhook - Add step-by-step progress UI during project creation - Update database types for agent_runs approval workflow - Update database types for project automation config 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
91
src/app/(dashboard)/projects/new/actions.ts
Normal file
91
src/app/(dashboard)/projects/new/actions.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import type { GiteaRepository, GiteaWebhook } from '@/lib/gitea/types'
|
||||||
|
|
||||||
|
const GITEA_URL = process.env.GITEA_URL || 'https://gitea.mylder.io'
|
||||||
|
const GITEA_TOKEN = process.env.GITEA_TOKEN || ''
|
||||||
|
const GITEA_OWNER = 'admin'
|
||||||
|
const WWS_WEBHOOK_URL = 'https://wws.mylder.io/webhook/gitea'
|
||||||
|
|
||||||
|
interface CreateRepoResult {
|
||||||
|
success: boolean
|
||||||
|
repo?: GiteaRepository
|
||||||
|
webhook?: GiteaWebhook
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function giteaRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||||
|
const url = `${GITEA_URL}/api/v1${endpoint}`
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `token ${GITEA_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({
|
||||||
|
message: `HTTP ${response.status}: ${response.statusText}`,
|
||||||
|
}))
|
||||||
|
throw new Error(error.message || `Gitea API error: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
return {} as T
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGiteaRepo(slug: string, description?: string): Promise<CreateRepoResult> {
|
||||||
|
try {
|
||||||
|
// Check if repo already exists
|
||||||
|
const existingCheck = await fetch(`${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${slug}`, {
|
||||||
|
headers: { 'Authorization': `token ${GITEA_TOKEN}` },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existingCheck.ok) {
|
||||||
|
return { success: false, error: 'Repository already exists' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the repository
|
||||||
|
const repo = await giteaRequest<GiteaRepository>('/user/repos', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: slug,
|
||||||
|
description: description || `Mylder project: ${slug}`,
|
||||||
|
private: false,
|
||||||
|
auto_init: true,
|
||||||
|
default_branch: 'main',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create webhook pointing to WWS (which routes to n8n)
|
||||||
|
const webhook = await giteaRequest<GiteaWebhook>(`/repos/${GITEA_OWNER}/${slug}/hooks`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'gitea',
|
||||||
|
config: {
|
||||||
|
url: WWS_WEBHOOK_URL,
|
||||||
|
content_type: 'json',
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
events: ['push', 'pull_request', 'issues', 'create', 'delete'],
|
||||||
|
active: true,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return { success: true, repo, webhook }
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to create repository',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGiteaRepoUrl(slug: string): Promise<string> {
|
||||||
|
return `${GITEA_URL}/${GITEA_OWNER}/${slug}`
|
||||||
|
}
|
||||||
@@ -8,13 +8,17 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { ArrowLeft, Loader2 } from 'lucide-react'
|
import { ArrowLeft, Loader2, GitBranch, Check } from 'lucide-react'
|
||||||
|
import { createGiteaRepo } from './actions'
|
||||||
|
|
||||||
|
type CreationStep = 'idle' | 'creating_project' | 'creating_repo' | 'complete'
|
||||||
|
|
||||||
export default function NewProjectPage() {
|
export default function NewProjectPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [step, setStep] = useState<CreationStep>('idle')
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -37,6 +41,9 @@ export default function NewProjectPage() {
|
|||||||
.replace(/[^a-z0-9]+/g, '-')
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
.replace(/(^-|-$)/g, '')
|
.replace(/(^-|-$)/g, '')
|
||||||
|
|
||||||
|
// Step 1: Create Supabase project
|
||||||
|
setStep('creating_project')
|
||||||
|
|
||||||
// First, get or create the user's default team
|
// First, get or create the user's default team
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let { data: team } = await (supabase as any)
|
let { data: team } = await (supabase as any)
|
||||||
@@ -60,12 +67,13 @@ export default function NewProjectPage() {
|
|||||||
if (teamError) {
|
if (teamError) {
|
||||||
setError('Failed to create team')
|
setError('Failed to create team')
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
setStep('idle')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
team = newTeam
|
team = newTeam
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the project
|
// Create the project (without gitea_repo initially)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const { data: project, error: projectError } = await (supabase as any)
|
const { data: project, error: projectError } = await (supabase as any)
|
||||||
.from('projects')
|
.from('projects')
|
||||||
@@ -81,9 +89,34 @@ export default function NewProjectPage() {
|
|||||||
if (projectError) {
|
if (projectError) {
|
||||||
setError(projectError.message)
|
setError(projectError.message)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
setStep('idle')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 2: Create Gitea repository
|
||||||
|
setStep('creating_repo')
|
||||||
|
const repoResult = await createGiteaRepo(slug, description || name)
|
||||||
|
|
||||||
|
if (repoResult.success && repoResult.repo) {
|
||||||
|
// Update project with gitea_repo URL
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
await (supabase as any)
|
||||||
|
.from('projects')
|
||||||
|
.update({
|
||||||
|
gitea_repo: repoResult.repo.html_url,
|
||||||
|
gitea_webhook_id: repoResult.webhook?.id,
|
||||||
|
})
|
||||||
|
.eq('id', project.id)
|
||||||
|
} else {
|
||||||
|
// Log warning but don't fail - project still created
|
||||||
|
console.warn('Gitea repo creation failed:', repoResult.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
setStep('complete')
|
||||||
|
|
||||||
|
// Small delay to show completion state
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
|
|
||||||
router.push(`/projects/${project.id}`)
|
router.push(`/projects/${project.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +160,42 @@ export default function NewProjectPage() {
|
|||||||
{error && (
|
{error && (
|
||||||
<p className="text-sm text-destructive">{error}</p>
|
<p className="text-sm text-destructive">{error}</p>
|
||||||
)}
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="space-y-2 py-2">
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
{step === 'creating_project' ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin text-brand" />
|
||||||
|
) : step === 'creating_repo' || step === 'complete' ? (
|
||||||
|
<Check className="h-4 w-4 text-emerald-500" />
|
||||||
|
) : (
|
||||||
|
<div className="h-4 w-4 rounded-full border-2 border-muted" />
|
||||||
|
)}
|
||||||
|
<span className={step === 'creating_project' ? 'text-foreground' : 'text-muted-foreground'}>
|
||||||
|
Creating project
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
{step === 'creating_repo' ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin text-brand" />
|
||||||
|
) : step === 'complete' ? (
|
||||||
|
<Check className="h-4 w-4 text-emerald-500" />
|
||||||
|
) : (
|
||||||
|
<div className="h-4 w-4 rounded-full border-2 border-muted" />
|
||||||
|
)}
|
||||||
|
<span className={step === 'creating_repo' ? 'text-foreground' : 'text-muted-foreground'}>
|
||||||
|
<GitBranch className="inline h-3 w-3 mr-1" />
|
||||||
|
Creating Gitea repository
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Button type="submit" variant="brand" className="w-full" disabled={isLoading || !name}>
|
<Button type="submit" variant="brand" className="w-full" disabled={isLoading || !name}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
Creating...
|
{step === 'creating_project' && 'Creating project...'}
|
||||||
|
{step === 'creating_repo' && 'Setting up repository...'}
|
||||||
|
{step === 'complete' && 'Done!'}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Create Project'
|
'Create Project'
|
||||||
|
|||||||
197
src/lib/gitea/client.ts
Normal file
197
src/lib/gitea/client.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import type {
|
||||||
|
GiteaRepository,
|
||||||
|
GiteaWebhook,
|
||||||
|
GiteaPullRequest,
|
||||||
|
GiteaBranch,
|
||||||
|
CreateRepoOptions,
|
||||||
|
CreateWebhookOptions,
|
||||||
|
CreateCommitOptions,
|
||||||
|
CreatePROptions,
|
||||||
|
GiteaError,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
|
const GITEA_URL = process.env.NEXT_PUBLIC_GITEA_URL || 'https://gitea.mylder.io'
|
||||||
|
const GITEA_TOKEN = process.env.GITEA_TOKEN || ''
|
||||||
|
const GITEA_OWNER = 'admin'
|
||||||
|
|
||||||
|
class GiteaClient {
|
||||||
|
private baseUrl: string
|
||||||
|
private token: string
|
||||||
|
private owner: string
|
||||||
|
|
||||||
|
constructor(baseUrl: string = GITEA_URL, token: string = GITEA_TOKEN, owner: string = GITEA_OWNER) {
|
||||||
|
this.baseUrl = baseUrl
|
||||||
|
this.token = token
|
||||||
|
this.owner = owner
|
||||||
|
}
|
||||||
|
|
||||||
|
private async request<T>(
|
||||||
|
endpoint: string,
|
||||||
|
options: RequestInit = {}
|
||||||
|
): Promise<T> {
|
||||||
|
const url = `${this.baseUrl}/api/v1${endpoint}`
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `token ${this.token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error: GiteaError = await response.json().catch(() => ({
|
||||||
|
message: `HTTP ${response.status}: ${response.statusText}`,
|
||||||
|
}))
|
||||||
|
throw new Error(error.message || `Gitea API error: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
return {} as T
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRepo(options: CreateRepoOptions): Promise<GiteaRepository> {
|
||||||
|
return this.request<GiteaRepository>('/user/repos', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: options.name,
|
||||||
|
description: options.description || '',
|
||||||
|
private: options.private ?? false,
|
||||||
|
auto_init: options.auto_init ?? true,
|
||||||
|
default_branch: options.default_branch || 'main',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRepo(name: string): Promise<GiteaRepository | null> {
|
||||||
|
try {
|
||||||
|
return await this.request<GiteaRepository>(`/repos/${this.owner}/${name}`)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRepo(name: string): Promise<void> {
|
||||||
|
await this.request(`/repos/${this.owner}/${name}`, { method: 'DELETE' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async createWebhook(options: CreateWebhookOptions): Promise<GiteaWebhook> {
|
||||||
|
return this.request<GiteaWebhook>(`/repos/${this.owner}/${options.repo}/hooks`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'gitea',
|
||||||
|
config: {
|
||||||
|
url: options.url,
|
||||||
|
content_type: 'json',
|
||||||
|
secret: options.secret || '',
|
||||||
|
},
|
||||||
|
events: options.events,
|
||||||
|
active: options.active ?? true,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async listWebhooks(repo: string): Promise<GiteaWebhook[]> {
|
||||||
|
return this.request<GiteaWebhook[]>(`/repos/${this.owner}/${repo}/hooks`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteWebhook(repo: string, hookId: number): Promise<void> {
|
||||||
|
await this.request(`/repos/${this.owner}/${repo}/hooks/${hookId}`, { method: 'DELETE' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBranch(repo: string, branch: string): Promise<GiteaBranch | null> {
|
||||||
|
try {
|
||||||
|
return await this.request<GiteaBranch>(`/repos/${this.owner}/${repo}/branches/${branch}`)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBranch(repo: string, branchName: string, baseBranch: string = 'main'): Promise<GiteaBranch> {
|
||||||
|
const base = await this.getBranch(repo, baseBranch)
|
||||||
|
if (!base) {
|
||||||
|
throw new Error(`Base branch ${baseBranch} not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request<GiteaBranch>(`/repos/${this.owner}/${repo}/branches`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
new_branch_name: branchName,
|
||||||
|
old_ref_name: baseBranch,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCommit(options: CreateCommitOptions): Promise<{ commit: { sha: string } }> {
|
||||||
|
const results = await Promise.all(
|
||||||
|
options.files.map(async (file) => {
|
||||||
|
const existing = await this.getFileContent(options.repo, file.path, options.branch)
|
||||||
|
const method = existing ? 'PUT' : 'POST'
|
||||||
|
const endpoint = `/repos/${this.owner}/${options.repo}/contents/${file.path}`
|
||||||
|
|
||||||
|
return this.request(endpoint, {
|
||||||
|
method,
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: options.message,
|
||||||
|
content: Buffer.from(file.content).toString('base64'),
|
||||||
|
branch: options.branch,
|
||||||
|
sha: existing?.sha,
|
||||||
|
author: options.author,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return results[0] as { commit: { sha: string } }
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFileContent(repo: string, path: string, ref?: string): Promise<{ sha: string; content: string } | null> {
|
||||||
|
try {
|
||||||
|
const endpoint = `/repos/${this.owner}/${repo}/contents/${path}${ref ? `?ref=${ref}` : ''}`
|
||||||
|
return await this.request(endpoint)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPullRequest(options: CreatePROptions): Promise<GiteaPullRequest> {
|
||||||
|
return this.request<GiteaPullRequest>(`/repos/${this.owner}/${options.repo}/pulls`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: options.title,
|
||||||
|
body: options.body || '',
|
||||||
|
head: options.head,
|
||||||
|
base: options.base || 'main',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPullRequest(repo: string, number: number): Promise<GiteaPullRequest> {
|
||||||
|
return this.request<GiteaPullRequest>(`/repos/${this.owner}/${repo}/pulls/${number}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async mergePullRequest(repo: string, number: number, strategy: 'merge' | 'rebase' | 'squash' = 'squash'): Promise<void> {
|
||||||
|
await this.request(`/repos/${this.owner}/${repo}/pulls/${number}/merge`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ do: strategy }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async listPullRequests(repo: string, state: 'open' | 'closed' | 'all' = 'open'): Promise<GiteaPullRequest[]> {
|
||||||
|
return this.request<GiteaPullRequest[]>(`/repos/${this.owner}/${repo}/pulls?state=${state}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
getRepoUrl(name: string): string {
|
||||||
|
return `${this.baseUrl}/${this.owner}/${name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
getCloneUrl(name: string): string {
|
||||||
|
return `${this.baseUrl}/${this.owner}/${name}.git`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const gitea = new GiteaClient()
|
||||||
|
export { GiteaClient }
|
||||||
82
src/lib/gitea/types.ts
Normal file
82
src/lib/gitea/types.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
export interface GiteaRepository {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
full_name: string
|
||||||
|
description: string
|
||||||
|
html_url: string
|
||||||
|
clone_url: string
|
||||||
|
ssh_url: string
|
||||||
|
default_branch: string
|
||||||
|
empty: boolean
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaWebhook {
|
||||||
|
id: number
|
||||||
|
type: string
|
||||||
|
url: string
|
||||||
|
active: boolean
|
||||||
|
events: string[]
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaCommitFile {
|
||||||
|
path: string
|
||||||
|
content: string
|
||||||
|
operation?: 'create' | 'update' | 'delete'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaPullRequest {
|
||||||
|
id: number
|
||||||
|
number: number
|
||||||
|
title: string
|
||||||
|
body: string
|
||||||
|
html_url: string
|
||||||
|
state: 'open' | 'closed' | 'merged'
|
||||||
|
head: { ref: string }
|
||||||
|
base: { ref: string }
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaBranch {
|
||||||
|
name: string
|
||||||
|
commit: { id: string; message: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateRepoOptions {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
private?: boolean
|
||||||
|
auto_init?: boolean
|
||||||
|
default_branch?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateWebhookOptions {
|
||||||
|
repo: string
|
||||||
|
url: string
|
||||||
|
events: string[]
|
||||||
|
secret?: string
|
||||||
|
active?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCommitOptions {
|
||||||
|
repo: string
|
||||||
|
branch: string
|
||||||
|
message: string
|
||||||
|
files: GiteaCommitFile[]
|
||||||
|
author?: { name: string; email: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePROptions {
|
||||||
|
repo: string
|
||||||
|
title: string
|
||||||
|
body?: string
|
||||||
|
head: string
|
||||||
|
base?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GiteaError = {
|
||||||
|
message: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
@@ -95,6 +95,13 @@ export interface Database {
|
|||||||
slug: string
|
slug: string
|
||||||
description: string | null
|
description: string | null
|
||||||
gitea_repo: string | null
|
gitea_repo: string | null
|
||||||
|
gitea_webhook_id: number | null
|
||||||
|
automation_config: {
|
||||||
|
auto_review: boolean
|
||||||
|
auto_deploy: boolean
|
||||||
|
deployment_target: 'wws' | 'coolify' | 'both' | null
|
||||||
|
coolify_app_uuid: string | null
|
||||||
|
} | null
|
||||||
tech_stack: string[]
|
tech_stack: string[]
|
||||||
platform: string | null
|
platform: string | null
|
||||||
status: 'active' | 'archived' | 'paused'
|
status: 'active' | 'archived' | 'paused'
|
||||||
@@ -109,6 +116,13 @@ export interface Database {
|
|||||||
slug: string
|
slug: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
gitea_repo?: string | null
|
gitea_repo?: string | null
|
||||||
|
gitea_webhook_id?: number | null
|
||||||
|
automation_config?: {
|
||||||
|
auto_review?: boolean
|
||||||
|
auto_deploy?: boolean
|
||||||
|
deployment_target?: 'wws' | 'coolify' | 'both' | null
|
||||||
|
coolify_app_uuid?: string | null
|
||||||
|
} | null
|
||||||
tech_stack?: string[]
|
tech_stack?: string[]
|
||||||
platform?: string | null
|
platform?: string | null
|
||||||
status?: 'active' | 'archived' | 'paused'
|
status?: 'active' | 'archived' | 'paused'
|
||||||
@@ -123,6 +137,13 @@ export interface Database {
|
|||||||
slug?: string
|
slug?: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
gitea_repo?: string | null
|
gitea_repo?: string | null
|
||||||
|
gitea_webhook_id?: number | null
|
||||||
|
automation_config?: {
|
||||||
|
auto_review?: boolean
|
||||||
|
auto_deploy?: boolean
|
||||||
|
deployment_target?: 'wws' | 'coolify' | 'both' | null
|
||||||
|
coolify_app_uuid?: string | null
|
||||||
|
} | null
|
||||||
tech_stack?: string[]
|
tech_stack?: string[]
|
||||||
platform?: string | null
|
platform?: string | null
|
||||||
status?: 'active' | 'archived' | 'paused'
|
status?: 'active' | 'archived' | 'paused'
|
||||||
@@ -168,6 +189,18 @@ export interface Database {
|
|||||||
command: string
|
command: string
|
||||||
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
||||||
result: Json | null
|
result: Json | null
|
||||||
|
proposed_changes: {
|
||||||
|
description: string
|
||||||
|
files: Array<{
|
||||||
|
path: string
|
||||||
|
content: string
|
||||||
|
operation: 'create' | 'update' | 'delete'
|
||||||
|
}>
|
||||||
|
estimated_impact?: string
|
||||||
|
} | null
|
||||||
|
approval_status: 'pending' | 'approved' | 'rejected' | 'auto_approved'
|
||||||
|
approved_by: string | null
|
||||||
|
approved_at: string | null
|
||||||
started_at: string | null
|
started_at: string | null
|
||||||
completed_at: string | null
|
completed_at: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
@@ -179,6 +212,18 @@ export interface Database {
|
|||||||
command: string
|
command: string
|
||||||
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
||||||
result?: Json | null
|
result?: Json | null
|
||||||
|
proposed_changes?: {
|
||||||
|
description: string
|
||||||
|
files: Array<{
|
||||||
|
path: string
|
||||||
|
content: string
|
||||||
|
operation: 'create' | 'update' | 'delete'
|
||||||
|
}>
|
||||||
|
estimated_impact?: string
|
||||||
|
} | null
|
||||||
|
approval_status?: 'pending' | 'approved' | 'rejected' | 'auto_approved'
|
||||||
|
approved_by?: string | null
|
||||||
|
approved_at?: string | null
|
||||||
started_at?: string | null
|
started_at?: string | null
|
||||||
completed_at?: string | null
|
completed_at?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
@@ -190,6 +235,18 @@ export interface Database {
|
|||||||
command?: string
|
command?: string
|
||||||
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
||||||
result?: Json | null
|
result?: Json | null
|
||||||
|
proposed_changes?: {
|
||||||
|
description: string
|
||||||
|
files: Array<{
|
||||||
|
path: string
|
||||||
|
content: string
|
||||||
|
operation: 'create' | 'update' | 'delete'
|
||||||
|
}>
|
||||||
|
estimated_impact?: string
|
||||||
|
} | null
|
||||||
|
approval_status?: 'pending' | 'approved' | 'rejected' | 'auto_approved'
|
||||||
|
approved_by?: string | null
|
||||||
|
approved_at?: string | null
|
||||||
started_at?: string | null
|
started_at?: string | null
|
||||||
completed_at?: string | null
|
completed_at?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user