mirror of
https://github.com/microsoft/monaco-editor.git
synced 2025-12-22 17:25:39 +01:00
Prettier format pending files
This commit is contained in:
parent
c3b3d72037
commit
34b0d92e8a
11 changed files with 1330 additions and 726 deletions
|
|
@ -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(); });
|
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue