Prettier format pending files

This commit is contained in:
Builder.io 2025-09-18 05:10:58 +00:00
parent c3b3d72037
commit 34b0d92e8a
11 changed files with 1330 additions and 726 deletions

View file

@ -1,14 +1,23 @@
const { app, BrowserWindow } = require('electron'); const { app, BrowserWindow } = require("electron");
const path = require('path'); const path = require("path");
function createWindow() { function createWindow() {
const win = new BrowserWindow({ width: 1280, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true } }); const win = new BrowserWindow({
const startUrl = process.env.SWITCH_URL || 'http://localhost:8080/switch.html'; width: 1280,
win.loadURL(startUrl); height: 800,
webPreferences: { nodeIntegration: false, contextIsolation: true },
});
const startUrl =
process.env.SWITCH_URL || "http://localhost:8080/switch.html";
win.loadURL(startUrl);
} }
app.whenReady().then(() => { app.whenReady().then(() => {
createWindow(); createWindow();
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
}); });
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });

View file

@ -1,26 +1,54 @@
const CACHE = 'switch-cache-v1'; const CACHE = "switch-cache-v1";
const ASSETS = [ const ASSETS = [
'/', "/",
'/index.html', "/index.html",
'/playground.html', "/playground.html",
'/monarch.html', "/monarch.html",
'/switch.html' "/switch.html",
]; ];
self.addEventListener('install', (e) => { self.addEventListener("install", (e) => {
e.waitUntil(caches.open(CACHE).then(c=>c.addAll(ASSETS)).then(()=>self.skipWaiting())); e.waitUntil(
caches
.open(CACHE)
.then((c) => c.addAll(ASSETS))
.then(() => self.skipWaiting())
);
}); });
self.addEventListener('activate', (e) => { self.addEventListener("activate", (e) => {
e.waitUntil(caches.keys().then(keys=>Promise.all(keys.filter(k=>k!==CACHE).map(k=>caches.delete(k)))).then(()=>self.clients.claim())); e.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))
)
)
.then(() => self.clients.claim())
);
}); });
self.addEventListener('fetch', (e) => { self.addEventListener("fetch", (e) => {
const url = new URL(e.request.url); const url = new URL(e.request.url);
if (url.origin === location.origin) { if (url.origin === location.origin) {
e.respondWith(caches.match(e.request).then(r=> r || fetch(e.request).then(resp=>{ e.respondWith(
if (e.request.method==='GET' && resp.ok && resp.type==='basic') { caches.match(e.request).then(
const clone = resp.clone(); (r) =>
caches.open(CACHE).then(c=>c.put(e.request, clone)); r ||
} fetch(e.request)
return resp; .then((resp) => {
}).catch(()=>caches.match('/index.html')))); if (
} e.request.method === "GET" &&
resp.ok &&
resp.type === "basic"
) {
const clone = resp.clone();
caches
.open(CACHE)
.then((c) => c.put(e.request, clone));
}
return resp;
})
.catch(() => caches.match("/index.html"))
)
);
}
}); });

View file

@ -93,7 +93,10 @@ async function _loadMonaco(setup: IMonacoSetup): Promise<typeof monaco> {
); );
} catch (e) { } catch (e) {
// If loading optional language contributions fails, still resolve the editor to keep app functional. // If loading optional language contributions fails, still resolve the editor to keep app functional.
console.error('Failed to load Monaco language contributions, continuing without them.', e); console.error(
"Failed to load Monaco language contributions, continuing without them.",
e
);
res(monaco); res(monaco);
} }
}); });

View file

@ -9,8 +9,8 @@ elem.className = "root";
document.body.append(elem); document.body.append(elem);
ReactDOM.render(<App />, elem); ReactDOM.render(<App />, elem);
if ('serviceWorker' in navigator) { if ("serviceWorker" in navigator) {
window.addEventListener('load', () => { window.addEventListener("load", () => {
navigator.serviceWorker.register('/sw.js').catch(() => {}); navigator.serviceWorker.register("/sw.js").catch(() => {});
}); });
} }

File diff suppressed because it is too large Load diff

View file

@ -1,167 +1,190 @@
export type StoreName = "repos" | "branches" | "commits" | "issues" | "fsHandles" | "settings"; export type StoreName =
| "repos"
| "branches"
| "commits"
| "issues"
| "fsHandles"
| "settings";
const DB_NAME = "switch-db"; const DB_NAME = "switch-db";
const DB_VERSION = 2; const DB_VERSION = 2;
export interface RepoRecord { export interface RepoRecord {
id: string; id: string;
name: string; name: string;
createdAt: number; createdAt: number;
defaultBranch: string; defaultBranch: string;
fsHandleId?: string; fsHandleId?: string;
} }
export interface BranchRecord { export interface BranchRecord {
id: string; id: string;
repoId: string; repoId: string;
name: string; name: string;
headCommitId?: string; headCommitId?: string;
} }
export interface CommitRecord { export interface CommitRecord {
id: string; id: string;
repoId: string; repoId: string;
message: string; message: string;
parentIds: string[]; parentIds: string[];
timestamp: number; timestamp: number;
} }
export interface IssueRecord { export interface IssueRecord {
id: string; id: string;
repoId: string; repoId: string;
title: string; title: string;
body: string; body: string;
labels: string[]; labels: string[];
status: "open" | "closed"; status: "open" | "closed";
createdAt: number; createdAt: number;
updatedAt: number; updatedAt: number;
branchId?: string; branchId?: string;
filePath?: string; filePath?: string;
} }
export interface FsHandleRecord { export interface FsHandleRecord {
id: string; id: string;
handle: FileSystemDirectoryHandle; handle: FileSystemDirectoryHandle;
} }
export interface RepoTemplateRecord { export interface RepoTemplateRecord {
id: string; id: string;
name: string; name: string;
fsHandleId: string; fsHandleId: string;
} }
export interface IssueTemplateRecord { export interface IssueTemplateRecord {
id: string; id: string;
name: string; name: string;
title: string; title: string;
body: string; body: string;
labels: string[]; labels: string[];
} }
export async function openDB(): Promise<IDBDatabase> { export async function openDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = indexedDB.open(DB_NAME, DB_VERSION); const req = indexedDB.open(DB_NAME, DB_VERSION);
req.onupgradeneeded = () => { req.onupgradeneeded = () => {
const db = req.result; const db = req.result;
if (!db.objectStoreNames.contains("repos")) { if (!db.objectStoreNames.contains("repos")) {
const store = db.createObjectStore("repos", { keyPath: "id" }); const store = db.createObjectStore("repos", { keyPath: "id" });
store.createIndex("by_name", "name", { unique: false }); store.createIndex("by_name", "name", { unique: false });
} }
if (!db.objectStoreNames.contains("branches")) { if (!db.objectStoreNames.contains("branches")) {
const store = db.createObjectStore("branches", { keyPath: "id" }); const store = db.createObjectStore("branches", {
store.createIndex("by_repo", "repoId", { unique: false }); keyPath: "id",
store.createIndex("by_repo_name", ["repoId", "name"], { unique: true }); });
} store.createIndex("by_repo", "repoId", { unique: false });
if (!db.objectStoreNames.contains("commits")) { store.createIndex("by_repo_name", ["repoId", "name"], {
const store = db.createObjectStore("commits", { keyPath: "id" }); unique: true,
store.createIndex("by_repo", "repoId", { unique: false }); });
} }
if (!db.objectStoreNames.contains("issues")) { if (!db.objectStoreNames.contains("commits")) {
const store = db.createObjectStore("issues", { keyPath: "id" }); const store = db.createObjectStore("commits", {
store.createIndex("by_repo", "repoId", { unique: false }); keyPath: "id",
} });
if (!db.objectStoreNames.contains("fsHandles")) { store.createIndex("by_repo", "repoId", { unique: false });
db.createObjectStore("fsHandles", { keyPath: "id" }); }
} if (!db.objectStoreNames.contains("issues")) {
if (!db.objectStoreNames.contains("settings")) { const store = db.createObjectStore("issues", { keyPath: "id" });
db.createObjectStore("settings", { keyPath: "id" }); store.createIndex("by_repo", "repoId", { unique: false });
} }
if (!db.objectStoreNames.contains("repoTemplates")) { if (!db.objectStoreNames.contains("fsHandles")) {
db.createObjectStore("repoTemplates", { keyPath: "id" }); db.createObjectStore("fsHandles", { keyPath: "id" });
} }
if (!db.objectStoreNames.contains("issueTemplates")) { if (!db.objectStoreNames.contains("settings")) {
db.createObjectStore("issueTemplates", { keyPath: "id" }); db.createObjectStore("settings", { keyPath: "id" });
} }
}; if (!db.objectStoreNames.contains("repoTemplates")) {
req.onsuccess = () => resolve(req.result); db.createObjectStore("repoTemplates", { keyPath: "id" });
req.onerror = () => reject(req.error); }
}); if (!db.objectStoreNames.contains("issueTemplates")) {
db.createObjectStore("issueTemplates", { keyPath: "id" });
}
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
} }
export async function tx<T>(storeNames: StoreName[], mode: IDBTransactionMode, fn: (tx: IDBTransaction) => Promise<T>): Promise<T> { export async function tx<T>(
const db = await openDB(); storeNames: StoreName[],
return new Promise<T>((resolve, reject) => { mode: IDBTransactionMode,
const transaction = db.transaction(storeNames, mode); fn: (tx: IDBTransaction) => Promise<T>
const done = async () => { ): Promise<T> {
try { const db = await openDB();
const r = await fn(transaction); return new Promise<T>((resolve, reject) => {
resolve(r); const transaction = db.transaction(storeNames, mode);
} catch (e) { const done = async () => {
reject(e); try {
} const r = await fn(transaction);
}; resolve(r);
transaction.oncomplete = () => db.close(); } catch (e) {
transaction.onabort = () => reject(transaction.error); reject(e);
transaction.onerror = () => reject(transaction.error); }
done(); };
}); transaction.oncomplete = () => db.close();
transaction.onabort = () => reject(transaction.error);
transaction.onerror = () => reject(transaction.error);
done();
});
} }
export async function put<T>(store: StoreName, value: T): Promise<void> { export async function put<T>(store: StoreName, value: T): Promise<void> {
await tx([store], "readwrite", async (t) => { await tx([store], "readwrite", async (t) => {
await requestAsPromise<void>(t.objectStore(store).put(value as any)); await requestAsPromise<void>(t.objectStore(store).put(value as any));
return undefined as any; return undefined as any;
}); });
} }
export async function get<T>(store: StoreName, key: IDBValidKey): Promise<T | undefined> { export async function get<T>(
return tx([store], "readonly", async (t) => { store: StoreName,
return requestAsPromise<T | undefined>(t.objectStore(store).get(key)); key: IDBValidKey
}); ): Promise<T | undefined> {
return tx([store], "readonly", async (t) => {
return requestAsPromise<T | undefined>(t.objectStore(store).get(key));
});
} }
export async function del(store: StoreName, key: IDBValidKey): Promise<void> { export async function del(store: StoreName, key: IDBValidKey): Promise<void> {
await tx([store], "readwrite", async (t) => { await tx([store], "readwrite", async (t) => {
await requestAsPromise<void>(t.objectStore(store).delete(key)); await requestAsPromise<void>(t.objectStore(store).delete(key));
return undefined as any; return undefined as any;
}); });
} }
export async function getAllByIndex<T>(store: StoreName, index: string, query: IDBValidKey | IDBKeyRange): Promise<T[]> { export async function getAllByIndex<T>(
return tx([store], "readonly", async (t) => { store: StoreName,
const idx = t.objectStore(store).index(index); index: string,
return requestAsPromise<T[]>(idx.getAll(query)); query: IDBValidKey | IDBKeyRange
}); ): Promise<T[]> {
return tx([store], "readonly", async (t) => {
const idx = t.objectStore(store).index(index);
return requestAsPromise<T[]>(idx.getAll(query));
});
} }
export async function getAll<T>(store: StoreName): Promise<T[]> { export async function getAll<T>(store: StoreName): Promise<T[]> {
return tx([store], "readonly", async (t) => { return tx([store], "readonly", async (t) => {
return requestAsPromise<T[]>(t.objectStore(store).getAll()); return requestAsPromise<T[]>(t.objectStore(store).getAll());
}); });
} }
export async function setSetting<T>(id: string, value: T): Promise<void> { export async function setSetting<T>(id: string, value: T): Promise<void> {
await put("settings", { id, value } as any); await put("settings", { id, value } as any);
} }
export async function getSetting<T>(id: string): Promise<T | undefined> { export async function getSetting<T>(id: string): Promise<T | undefined> {
const rec = await get<any>("settings", id); const rec = await get<any>("settings", id);
return rec?.value as T | undefined; return rec?.value as T | undefined;
} }
function requestAsPromise<T>(req: IDBRequest<T>): Promise<T> { function requestAsPromise<T>(req: IDBRequest<T>): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
req.onsuccess = () => resolve(req.result as T); req.onsuccess = () => resolve(req.result as T);
req.onerror = () => reject(req.error); req.onerror = () => reject(req.error);
}); });
} }

View file

@ -1,113 +1,162 @@
import { put, get, type FsHandleRecord } from "./db"; import { put, get, type FsHandleRecord } from "./db";
import { nanoid } from "./uid"; import { nanoid } from "./uid";
export async function pickDirectory(): Promise<{ id: string; handle: FileSystemDirectoryHandle }> { export async function pickDirectory(): Promise<{
// @ts-ignore id: string;
const handle: FileSystemDirectoryHandle = await (window as any).showDirectoryPicker(); handle: FileSystemDirectoryHandle;
const id = nanoid(); }> {
const rec: FsHandleRecord = { id, handle }; // @ts-ignore
await put("fsHandles", rec); const handle: FileSystemDirectoryHandle = await (
return { id, handle }; window as any
).showDirectoryPicker();
const id = nanoid();
const rec: FsHandleRecord = { id, handle };
await put("fsHandles", rec);
return { id, handle };
} }
export async function getDirectoryHandle(id: string): Promise<FileSystemDirectoryHandle | undefined> { export async function getDirectoryHandle(
const rec = await get<FsHandleRecord>("fsHandles", id); id: string
return rec?.handle; ): Promise<FileSystemDirectoryHandle | undefined> {
const rec = await get<FsHandleRecord>("fsHandles", id);
return rec?.handle;
} }
export async function ensureReadPerm(dir: FileSystemDirectoryHandle): Promise<boolean> { export async function ensureReadPerm(
const perm = await (dir as any).queryPermission?.({ mode: "read" }); dir: FileSystemDirectoryHandle
if (perm === "granted") return true; ): Promise<boolean> {
const req = await (dir as any).requestPermission?.({ mode: "read" }); const perm = await (dir as any).queryPermission?.({ mode: "read" });
return req === "granted"; if (perm === "granted") return true;
const req = await (dir as any).requestPermission?.({ mode: "read" });
return req === "granted";
} }
export async function ensureWritePerm(dir: FileSystemDirectoryHandle): Promise<boolean> { export async function ensureWritePerm(
const perm = await (dir as any).queryPermission?.({ mode: "readwrite" }); dir: FileSystemDirectoryHandle
if (perm === "granted") return true; ): Promise<boolean> {
const req = await (dir as any).requestPermission?.({ mode: "readwrite" }); const perm = await (dir as any).queryPermission?.({ mode: "readwrite" });
return req === "granted"; if (perm === "granted") return true;
const req = await (dir as any).requestPermission?.({ mode: "readwrite" });
return req === "granted";
} }
export async function getDirectoryHandleByPath(root: FileSystemDirectoryHandle, path: string, create = false): Promise<FileSystemDirectoryHandle | undefined> { export async function getDirectoryHandleByPath(
const parts = path.split("/").filter(Boolean); root: FileSystemDirectoryHandle,
let cur: FileSystemDirectoryHandle = root; path: string,
for (const name of parts) { create = false
const next = await (cur as any).getDirectoryHandle(name, { create }).catch(() => undefined); ): Promise<FileSystemDirectoryHandle | undefined> {
if (!next) return undefined; const parts = path.split("/").filter(Boolean);
cur = next; let cur: FileSystemDirectoryHandle = root;
} for (const name of parts) {
return cur; const next = await (cur as any)
.getDirectoryHandle(name, { create })
.catch(() => undefined);
if (!next) return undefined;
cur = next;
}
return cur;
} }
export async function getFileHandleByPath(root: FileSystemDirectoryHandle, path: string, create = false): Promise<FileSystemFileHandle | undefined> { export async function getFileHandleByPath(
const parts = path.split("/"); root: FileSystemDirectoryHandle,
const dirPath = parts.slice(0, -1).join("/"); path: string,
const fileName = parts[parts.length - 1]; create = false
const dir = dirPath ? await getDirectoryHandleByPath(root, dirPath, create) : root; ): Promise<FileSystemFileHandle | undefined> {
if (!dir) return undefined; const parts = path.split("/");
return (dir as any).getFileHandle(fileName, { create }).catch(() => undefined); const dirPath = parts.slice(0, -1).join("/");
const fileName = parts[parts.length - 1];
const dir = dirPath
? await getDirectoryHandleByPath(root, dirPath, create)
: root;
if (!dir) return undefined;
return (dir as any)
.getFileHandle(fileName, { create })
.catch(() => undefined);
} }
export async function readFileText(root: FileSystemDirectoryHandle, path: string): Promise<string | undefined> { export async function readFileText(
const fh = await getFileHandleByPath(root, path); root: FileSystemDirectoryHandle,
if (!fh) return undefined; path: string
const file = await fh.getFile(); ): Promise<string | undefined> {
return file.text(); const fh = await getFileHandleByPath(root, path);
if (!fh) return undefined;
const file = await fh.getFile();
return file.text();
} }
export async function writeFileText(root: FileSystemDirectoryHandle, path: string, content: string): Promise<void> { export async function writeFileText(
const dirPerm = await ensureWritePerm(root); root: FileSystemDirectoryHandle,
if (!dirPerm) throw new Error("No write permission"); path: string,
const fh = await getFileHandleByPath(root, path, true); content: string
if (!fh) throw new Error("Cannot create file"); ): Promise<void> {
const w = await (fh as any).createWritable(); const dirPerm = await ensureWritePerm(root);
await w.write(content); if (!dirPerm) throw new Error("No write permission");
await w.close(); const fh = await getFileHandleByPath(root, path, true);
if (!fh) throw new Error("Cannot create file");
const w = await (fh as any).createWritable();
await w.write(content);
await w.close();
} }
export async function createDirectory(root: FileSystemDirectoryHandle, path: string): Promise<void> { export async function createDirectory(
const ok = await ensureWritePerm(root); root: FileSystemDirectoryHandle,
if (!ok) throw new Error("No write permission"); path: string
await getDirectoryHandleByPath(root, path, true); ): Promise<void> {
const ok = await ensureWritePerm(root);
if (!ok) throw new Error("No write permission");
await getDirectoryHandleByPath(root, path, true);
} }
export async function deleteEntry(root: FileSystemDirectoryHandle, path: string, recursive = false): Promise<void> { export async function deleteEntry(
const parts = path.split("/"); root: FileSystemDirectoryHandle,
const dirPath = parts.slice(0, -1).join("/"); path: string,
const name = parts[parts.length - 1]; recursive = false
const dir = dirPath ? await getDirectoryHandleByPath(root, dirPath) : root; ): Promise<void> {
if (!dir) throw new Error("Path not found"); const parts = path.split("/");
await (dir as any).removeEntry(name, { recursive }).catch(() => undefined); const dirPath = parts.slice(0, -1).join("/");
const name = parts[parts.length - 1];
const dir = dirPath ? await getDirectoryHandleByPath(root, dirPath) : root;
if (!dir) throw new Error("Path not found");
await (dir as any).removeEntry(name, { recursive }).catch(() => undefined);
} }
export async function* walk(dir: FileSystemDirectoryHandle, pathPrefix = ""): AsyncGenerator<{ path: string; file: File }>{ export async function* walk(
// @ts-ignore dir: FileSystemDirectoryHandle,
for await (const [name, entry] of (dir as any).entries()) { pathPrefix = ""
const p = pathPrefix ? `${pathPrefix}/${name}` : name; ): AsyncGenerator<{ path: string; file: File }> {
if (entry.kind === "directory") { // @ts-ignore
yield* walk(entry as FileSystemDirectoryHandle, p); for await (const [name, entry] of (dir as any).entries()) {
} else { const p = pathPrefix ? `${pathPrefix}/${name}` : name;
const file = await (entry as FileSystemFileHandle).getFile(); if (entry.kind === "directory") {
yield { path: p, file }; yield* walk(entry as FileSystemDirectoryHandle, p);
} } else {
} const file = await (entry as FileSystemFileHandle).getFile();
yield { path: p, file };
}
}
} }
export async function copyDirectory(src: FileSystemDirectoryHandle, dest: FileSystemDirectoryHandle): Promise<void> { export async function copyDirectory(
const ok = await ensureWritePerm(dest); src: FileSystemDirectoryHandle,
if (!ok) throw new Error("No write permission on destination"); dest: FileSystemDirectoryHandle
// @ts-ignore ): Promise<void> {
for await (const [name, entry] of (src as any).entries()) { const ok = await ensureWritePerm(dest);
if (entry.kind === "directory") { if (!ok) throw new Error("No write permission on destination");
const sub = await (dest as any).getDirectoryHandle(name, { create: true }); // @ts-ignore
await copyDirectory(entry as FileSystemDirectoryHandle, sub); for await (const [name, entry] of (src as any).entries()) {
} else { if (entry.kind === "directory") {
const file = await (entry as FileSystemFileHandle).getFile(); const sub = await (dest as any).getDirectoryHandle(name, {
const fh = await (dest as any).getFileHandle(name, { create: true }); create: true,
const w = await (fh as any).createWritable(); });
await w.write(await file.arrayBuffer()); await copyDirectory(entry as FileSystemDirectoryHandle, sub);
await w.close(); } else {
} const file = await (entry as FileSystemFileHandle).getFile();
} const fh = await (dest as any).getFileHandle(name, {
create: true,
});
const w = await (fh as any).createWritable();
await w.write(await file.arrayBuffer());
await w.close();
}
}
} }

View file

@ -1,5 +1,6 @@
import { createClient, SupabaseClient } from '@supabase/supabase-js'; import { createClient, SupabaseClient } from "@supabase/supabase-js";
const url = process.env.SUPABASE_URL as string; const url = process.env.SUPABASE_URL as string;
const key = process.env.SUPABASE_ANON_KEY as string; const key = process.env.SUPABASE_ANON_KEY as string;
export const supabase: SupabaseClient | undefined = url && key ? createClient(url, key) : undefined; export const supabase: SupabaseClient | undefined =
url && key ? createClient(url, key) : undefined;

View file

@ -1,37 +1,64 @@
import { getAll, getAllByIndex, type RepoRecord, type BranchRecord, type CommitRecord, type IssueRecord } from './db'; import {
import { supabase } from './supabase'; getAll,
getAllByIndex,
type RepoRecord,
type BranchRecord,
type CommitRecord,
type IssueRecord,
} from "./db";
import { supabase } from "./supabase";
export interface ExportBundle { export interface ExportBundle {
repo: RepoRecord; repo: RepoRecord;
branches: BranchRecord[]; branches: BranchRecord[];
commits: CommitRecord[]; commits: CommitRecord[];
issues: IssueRecord[]; issues: IssueRecord[];
exportedAt: number; exportedAt: number;
} }
export async function exportRepoBundle(repoId: string): Promise<ExportBundle> { export async function exportRepoBundle(repoId: string): Promise<ExportBundle> {
const repos = await getAll<RepoRecord>('repos'); const repos = await getAll<RepoRecord>("repos");
const repo = repos.find(r=>r.id===repoId)!; const repo = repos.find((r) => r.id === repoId)!;
const branches = await getAllByIndex<BranchRecord>('branches','by_repo', repoId); const branches = await getAllByIndex<BranchRecord>(
const commits = await getAllByIndex<CommitRecord>('commits','by_repo', repoId); "branches",
const issues = await getAllByIndex<IssueRecord>('issues','by_repo', repoId); "by_repo",
return { repo, branches, commits, issues, exportedAt: Date.now() }; repoId
);
const commits = await getAllByIndex<CommitRecord>(
"commits",
"by_repo",
repoId
);
const issues = await getAllByIndex<IssueRecord>(
"issues",
"by_repo",
repoId
);
return { repo, branches, commits, issues, exportedAt: Date.now() };
} }
export async function pushRepoToSupabase(repoId: string): Promise<void> { export async function pushRepoToSupabase(repoId: string): Promise<void> {
if (!supabase) throw new Error('Supabase not configured'); if (!supabase) throw new Error("Supabase not configured");
const bundle = await exportRepoBundle(repoId); const bundle = await exportRepoBundle(repoId);
const path = `repos/${repoId}.json`; const path = `repos/${repoId}.json`;
const data = new Blob([JSON.stringify(bundle)], { type: 'application/json' }); const data = new Blob([JSON.stringify(bundle)], {
const { error } = await supabase.storage.from('switch').upload(path, data, { upsert: true }); type: "application/json",
if (error) throw error; });
const { error } = await supabase.storage
.from("switch")
.upload(path, data, { upsert: true });
if (error) throw error;
} }
export async function pullRepoFromSupabase(repoId: string): Promise<ExportBundle | undefined> { export async function pullRepoFromSupabase(
if (!supabase) throw new Error('Supabase not configured'); repoId: string
const path = `repos/${repoId}.json`; ): Promise<ExportBundle | undefined> {
const { data, error } = await supabase.storage.from('switch').download(path); if (!supabase) throw new Error("Supabase not configured");
if (error) throw error; const path = `repos/${repoId}.json`;
const text = await data.text(); const { data, error } = await supabase.storage
return JSON.parse(text) as ExportBundle; .from("switch")
.download(path);
if (error) throw error;
const text = await data.text();
return JSON.parse(text) as ExportBundle;
} }

View file

@ -1,26 +1,41 @@
import { get, put, del, getAll, type RepoTemplateRecord, type IssueTemplateRecord } from "./db"; import {
get,
put,
del,
getAll,
type RepoTemplateRecord,
type IssueTemplateRecord,
} from "./db";
import { nanoid } from "./uid"; import { nanoid } from "./uid";
export async function createRepoTemplate(name: string, fsHandleId: string): Promise<RepoTemplateRecord> { export async function createRepoTemplate(
const rec: RepoTemplateRecord = { id: nanoid(), name, fsHandleId }; name: string,
await put("repoTemplates" as any, rec as any); fsHandleId: string
return rec; ): Promise<RepoTemplateRecord> {
const rec: RepoTemplateRecord = { id: nanoid(), name, fsHandleId };
await put("repoTemplates" as any, rec as any);
return rec;
} }
export async function listRepoTemplates(): Promise<RepoTemplateRecord[]> { export async function listRepoTemplates(): Promise<RepoTemplateRecord[]> {
return getAll<any>("repoTemplates" as any) as any; return getAll<any>("repoTemplates" as any) as any;
} }
export async function deleteRepoTemplate(id: string): Promise<void> { export async function deleteRepoTemplate(id: string): Promise<void> {
await del("repoTemplates" as any, id); await del("repoTemplates" as any, id);
} }
export async function createIssueTemplate(data: { name: string; title: string; body: string; labels: string[] }): Promise<IssueTemplateRecord> { export async function createIssueTemplate(data: {
const rec: IssueTemplateRecord = { id: nanoid(), ...data }; name: string;
await put("issueTemplates" as any, rec as any); title: string;
return rec; body: string;
labels: string[];
}): Promise<IssueTemplateRecord> {
const rec: IssueTemplateRecord = { id: nanoid(), ...data };
await put("issueTemplates" as any, rec as any);
return rec;
} }
export async function listIssueTemplates(): Promise<IssueTemplateRecord[]> { export async function listIssueTemplates(): Promise<IssueTemplateRecord[]> {
return getAll<any>("issueTemplates" as any) as any; return getAll<any>("issueTemplates" as any) as any;
} }
export async function deleteIssueTemplate(id: string): Promise<void> { export async function deleteIssueTemplate(id: string): Promise<void> {
await del("issueTemplates" as any, id); await del("issueTemplates" as any, id);
} }

View file

@ -76,7 +76,9 @@ module.exports = {
"process.env": { "process.env": {
YEAR: JSON.stringify(new Date().getFullYear()), YEAR: JSON.stringify(new Date().getFullYear()),
SUPABASE_URL: JSON.stringify(process.env.SUPABASE_URL || ""), SUPABASE_URL: JSON.stringify(process.env.SUPABASE_URL || ""),
SUPABASE_ANON_KEY: JSON.stringify(process.env.SUPABASE_ANON_KEY || ""), SUPABASE_ANON_KEY: JSON.stringify(
process.env.SUPABASE_ANON_KEY || ""
),
}, },
}), }),
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
@ -119,7 +121,11 @@ module.exports = {
}), }),
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: "./typedoc/dist", to: "./typedoc/", noErrorOnMissing: true }, {
from: "./typedoc/dist",
to: "./typedoc/",
noErrorOnMissing: true,
},
], ],
}), }),
new CopyPlugin({ new CopyPlugin({