feat: apply Charbon·Lime design system, add gear icon, multi-provider git settings
Release APK / build (push) Has been cancelled
Release APK / build (push) Has been cancelled
- Replace app icon with new Charbon·Lime brand assets (icon.png + adaptive-icon.png) - Apply design tokens to theme.ts: bg #1B2433, primary/success #88D656 (lime), text1 #EEF2F5 (cream) - Add ⚙ gear button (lime) in HomeScreen header → navigates to /settings - Expand settings: git provider selector (Gitea / GitHub / GitLab) with per-provider fields - Add lib/git.ts: unified pushIncidentToGit() for Gitea, GitHub (PUT), and GitLab APIs - Update incident detail screen to use lib/git.ts and dynamic provider routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
import { incidentToFilename, renderMarkdown } from "./markdown";
|
||||
import type { Incident } from "@/types/incident";
|
||||
|
||||
export type GitProvider = "gitea" | "github" | "gitlab";
|
||||
|
||||
export interface GitConfig {
|
||||
provider: GitProvider;
|
||||
url?: string; // required for gitea and self-hosted gitlab
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}
|
||||
|
||||
export interface PushResult {
|
||||
success: boolean;
|
||||
url?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function pushIncidentToGit(
|
||||
incident: Incident,
|
||||
config: GitConfig,
|
||||
markdownTemplate?: string
|
||||
): Promise<PushResult> {
|
||||
switch (config.provider) {
|
||||
case "gitea":
|
||||
return pushGitea(incident, config, markdownTemplate);
|
||||
case "github":
|
||||
return pushGitHub(incident, config, markdownTemplate);
|
||||
case "gitlab":
|
||||
return pushGitLab(incident, config, markdownTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
async function pushGitea(
|
||||
incident: Incident,
|
||||
config: GitConfig,
|
||||
markdownTemplate?: string
|
||||
): Promise<PushResult> {
|
||||
const filepath = incidentToFilename(incident);
|
||||
const content = renderMarkdown(incident, markdownTemplate);
|
||||
const base64Content = btoa(unescape(encodeURIComponent(content)));
|
||||
const apiBase = (config.url ?? "").replace(/\/$/, "");
|
||||
const endpoint = `${apiBase}/api/v1/repos/${config.owner}/${config.repo}/contents/${filepath}`;
|
||||
|
||||
try {
|
||||
const existing = await fetch(endpoint, {
|
||||
headers: { Authorization: `Bearer ${config.token}` },
|
||||
});
|
||||
let sha: string | undefined;
|
||||
if (existing.ok) {
|
||||
const data = await existing.json() as { sha?: string };
|
||||
sha = data.sha;
|
||||
}
|
||||
|
||||
const body: Record<string, string> = {
|
||||
message: `incident: ${incident.title}`,
|
||||
content: base64Content,
|
||||
};
|
||||
if (sha) body.sha = sha;
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return { success: false, error: `HTTP ${res.status}: ${await res.text()}` };
|
||||
}
|
||||
const data = await res.json() as { content?: { html_url?: string } };
|
||||
return { success: true, url: data.content?.html_url };
|
||||
} catch (e) {
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function pushGitHub(
|
||||
incident: Incident,
|
||||
config: GitConfig,
|
||||
markdownTemplate?: string
|
||||
): Promise<PushResult> {
|
||||
const filepath = incidentToFilename(incident);
|
||||
const content = renderMarkdown(incident, markdownTemplate);
|
||||
const base64Content = btoa(unescape(encodeURIComponent(content)));
|
||||
const endpoint = `https://api.github.com/repos/${config.owner}/${config.repo}/contents/${filepath}`;
|
||||
|
||||
try {
|
||||
const existing = await fetch(endpoint, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
},
|
||||
});
|
||||
let sha: string | undefined;
|
||||
if (existing.ok) {
|
||||
const data = await existing.json() as { sha?: string };
|
||||
sha = data.sha;
|
||||
}
|
||||
|
||||
const body: Record<string, string> = {
|
||||
message: `incident: ${incident.title}`,
|
||||
content: base64Content,
|
||||
};
|
||||
if (sha) body.sha = sha;
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return { success: false, error: `HTTP ${res.status}: ${await res.text()}` };
|
||||
}
|
||||
const data = await res.json() as { content?: { html_url?: string } };
|
||||
return { success: true, url: data.content?.html_url };
|
||||
} catch (e) {
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function pushGitLab(
|
||||
incident: Incident,
|
||||
config: GitConfig,
|
||||
markdownTemplate?: string
|
||||
): Promise<PushResult> {
|
||||
const filepath = incidentToFilename(incident);
|
||||
const content = renderMarkdown(incident, markdownTemplate);
|
||||
const base64Content = btoa(unescape(encodeURIComponent(content)));
|
||||
const apiBase = (config.url ?? "https://gitlab.com").replace(/\/$/, "");
|
||||
const projectId = encodeURIComponent(`${config.owner}/${config.repo}`);
|
||||
const encodedPath = encodeURIComponent(filepath);
|
||||
const endpoint = `${apiBase}/api/v4/projects/${projectId}/repository/files/${encodedPath}`;
|
||||
|
||||
try {
|
||||
const existing = await fetch(`${endpoint}?ref=HEAD`, {
|
||||
headers: { "PRIVATE-TOKEN": config.token },
|
||||
});
|
||||
const method = existing.ok ? "PUT" : "POST";
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method,
|
||||
headers: {
|
||||
"PRIVATE-TOKEN": config.token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
branch: "main",
|
||||
content: base64Content,
|
||||
encoding: "base64",
|
||||
commit_message: `incident: ${incident.title}`,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return { success: false, error: `HTTP ${res.status}: ${await res.text()}` };
|
||||
}
|
||||
const data = await res.json() as { file_path?: string };
|
||||
const webUrl = data.file_path
|
||||
? `${apiBase}/${config.owner}/${config.repo}/-/blob/main/${data.file_path}`
|
||||
: undefined;
|
||||
return { success: true, url: webUrl };
|
||||
} catch (e) {
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user