mirror of
https://github.com/ivabus/gui
synced 2025-04-23 14:07:14 +03:00
use libtea instead of installing the cli (#649)
This commit is contained in:
parent
79bd897146
commit
46c8b26682
6 changed files with 53 additions and 254 deletions
|
@ -14,7 +14,7 @@ import initializePushNotification, {
|
|||
syncPackageTopicSubscriptions
|
||||
} from "./libs/push-notification";
|
||||
|
||||
import init, { initializeTeaCli } from "./libs/initialize";
|
||||
import init from "./libs/initialize";
|
||||
import { readSessionData } from "./libs/auth";
|
||||
|
||||
import { isDev } from "./libs/auto-updater";
|
||||
|
@ -38,11 +38,10 @@ if (app.isPackaged) {
|
|||
}
|
||||
});
|
||||
Sentry.configureScope(async (scope) => {
|
||||
const [session, cliVersion] = await Promise.all([readSessionData(), initializeTeaCli()]);
|
||||
const session = await readSessionData();
|
||||
scope.setUser({
|
||||
id: session.device_id, // device_id this should exist in our pg db: developer_id is to many device_id
|
||||
username: session?.user?.login || "", // github username or handler
|
||||
tea: cliVersion
|
||||
username: session?.user?.login || "" // github username or handler
|
||||
});
|
||||
});
|
||||
setSentryLogging(Sentry);
|
||||
|
|
|
@ -1,139 +1,76 @@
|
|||
import { spawn, exec } from "child_process";
|
||||
import { getGuiPath, getTeaPath } from "./tea-dir";
|
||||
import { spawn } from "child_process";
|
||||
import { getGuiPath } from "./tea-dir";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { initializeTeaCli } from "./initialize";
|
||||
import { hooks } from "@teaxyz/lib";
|
||||
|
||||
import { app } from "electron";
|
||||
import log from "./logger";
|
||||
import { MainWindowNotifier } from "./types";
|
||||
|
||||
// Be careful with globbing when passing this to a shell which might expand it. Either escape it or quote it.
|
||||
export const cliBinPath = path.join(getTeaPath(), "tea.xyz/v*/bin/tea");
|
||||
import { Installation, Package, porcelain } from "@teaxyz/lib";
|
||||
import type { Resolution } from "@teaxyz/lib/script/src/plumbing/resolve";
|
||||
|
||||
export async function installPackage(
|
||||
full_name: string,
|
||||
version: string,
|
||||
notifyMainWindow: MainWindowNotifier
|
||||
) {
|
||||
const teaVersion = await initializeTeaCli();
|
||||
const progressNotifier = newInstallProgressNotifier(full_name, notifyMainWindow);
|
||||
|
||||
if (!teaVersion) throw new Error("no tea");
|
||||
const notifier = newInstallProgressNotifier(full_name, notifyMainWindow);
|
||||
|
||||
const qualifedPackage = `${full_name}@${version}`;
|
||||
|
||||
log.info(`installing package ${qualifedPackage}`);
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
// tea requires HOME to be set.
|
||||
const opts = { env: { HOME: app.getPath("home"), NO_COLOR: "1" } };
|
||||
|
||||
const child = spawn(
|
||||
cliBinPath,
|
||||
["--env=false", "--sync", "--json", `+${qualifedPackage}`],
|
||||
opts
|
||||
);
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
stdout += data.toString().trim();
|
||||
});
|
||||
|
||||
child.stderr.on("data", (data) => {
|
||||
try {
|
||||
data
|
||||
.toString()
|
||||
.split("\n")
|
||||
.map((s: string) => s.trim())
|
||||
.filter((s: string) => s.length > 0)
|
||||
.forEach((line) => {
|
||||
try {
|
||||
const msg = JSON.parse(line.trim());
|
||||
progressNotifier(msg);
|
||||
} catch (err) {
|
||||
log.error("handling cli notification line", line, err);
|
||||
}
|
||||
});
|
||||
|
||||
stderr += data.toString();
|
||||
} catch (err) {
|
||||
log.error("error processing cli data", data.toString(), err);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
log.info("cli exited with code:", code);
|
||||
log.info("cli stdout:", stdout);
|
||||
if (code !== 0) {
|
||||
log.info("cli stderr:", stderr);
|
||||
reject(new Error("tea exited with non-zero code: " + code));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", () => {
|
||||
reject(new Error(stderr));
|
||||
});
|
||||
});
|
||||
const result = await porcelain.install(qualifedPackage, notifier);
|
||||
console.log(`successfully installed ${qualifedPackage}`, result);
|
||||
}
|
||||
|
||||
// This is hacky and kind of complex because of the output we get from the CLI. When the CLI
|
||||
// gives better output this definitely should get looked at.
|
||||
function newInstallProgressNotifier(full_name: string, notifyMainWindow: MainWindowNotifier) {
|
||||
// the install progress is super spammy, only send every 10th update
|
||||
let counter = 0;
|
||||
|
||||
// the totall number of packages to install - this is set by the "resolved" message
|
||||
let numberOfPackages = 1;
|
||||
|
||||
// the current package number - this is incremented by the "installed" or "downloaded" message
|
||||
let currentPackageNumber = 0;
|
||||
|
||||
return function (msg: any) {
|
||||
if (msg.status !== "downloading" && msg.status !== "installing") {
|
||||
log.info("cli:", msg);
|
||||
}
|
||||
|
||||
if (msg.status === "resolved") {
|
||||
numberOfPackages = msg.pkgs?.length ?? 1;
|
||||
log.info(`installing ${numberOfPackages} packages`);
|
||||
} else if (msg.status === "downloading") {
|
||||
counter++;
|
||||
if (counter % 10 !== 0) return;
|
||||
|
||||
const { received = 0, "content-size": contentSize = 0 } = msg;
|
||||
if (contentSize > 0) {
|
||||
// how many total pacakges are completed
|
||||
const overallProgress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
// how much of the current package is completed
|
||||
const packageProgress = (received / contentSize) * 100;
|
||||
// progress is the total packages completed plus the percentage of the current package
|
||||
const progress = overallProgress + packageProgress / numberOfPackages;
|
||||
notifyMainWindow("install-progress", { full_name, progress });
|
||||
return {
|
||||
resolved: ({ pending }: Resolution) => {
|
||||
numberOfPackages = pending.length ?? 1;
|
||||
log.info(`resolved ${numberOfPackages} packages to install`);
|
||||
},
|
||||
installing: ({ pkg, progress }: { pkg: Package; progress: number | undefined }) => {
|
||||
log.info(`installing ${pkg.project}@${pkg.version} - ${progress}`);
|
||||
if (progress && progress > 0) {
|
||||
// how many total packages are completed
|
||||
const completedProgress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
// overallProgress is the total packages completed plus the percentage of the current package
|
||||
const overallProgress = completedProgress + (progress / numberOfPackages) * 100;
|
||||
notifyMainWindow("install-progress", { full_name, progress: overallProgress });
|
||||
}
|
||||
} else if (msg.status === "installed") {
|
||||
},
|
||||
installed: (installation: Installation) => {
|
||||
log.info("installed", installation);
|
||||
const { project, version } = installation.pkg;
|
||||
|
||||
currentPackageNumber++;
|
||||
const progress = (currentPackageNumber / numberOfPackages) * 100;
|
||||
notifyMainWindow("install-progress", { full_name, progress });
|
||||
notifyPackageInstalled(msg.pkg, notifyMainWindow);
|
||||
notifyMainWindow("pkg-installed", { full_name: project, version: version.toString() });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const notifyPackageInstalled = (rawPkg: string, notifyMainWindow: MainWindowNotifier) => {
|
||||
try {
|
||||
const [full_name, version] = rawPkg.split("=");
|
||||
notifyMainWindow("pkg-installed", { full_name, version });
|
||||
} catch (err) {
|
||||
log.error("failed to notify package installed", err);
|
||||
// the tea cli package is needed to open any other package in the terminal, so make sure it's installed and return the path
|
||||
async function installTeaCli() {
|
||||
const installations = await porcelain.install("tea.xyz");
|
||||
const teaPkg = installations.find((i) => i.pkg.project === "tea.xyz");
|
||||
if (!teaPkg) {
|
||||
throw new Error("could not find or install tea cli!");
|
||||
}
|
||||
};
|
||||
|
||||
return teaPkg.path.join("bin/tea");
|
||||
}
|
||||
|
||||
export async function openPackageEntrypointInTerminal(pkg: string) {
|
||||
const cliBinPath = await installTeaCli();
|
||||
log.info(`opening package ${pkg} with tea cli at ${cliBinPath}`);
|
||||
|
||||
let sh = `"${cliBinPath}" --sync --env=false +${pkg} `;
|
||||
switch (pkg) {
|
||||
case "github.com/AUTOMATIC1111/stable-diffusion-webui":
|
||||
|
@ -194,24 +131,8 @@ const createCommandScriptFile = async (cmd: string): Promise<string> => {
|
|||
return tmpFilePath;
|
||||
};
|
||||
|
||||
export async function asyncExec(cmd: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (err, stdout) => {
|
||||
if (err) {
|
||||
console.log("err:", err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
console.log("stdout:", stdout);
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncPantry() {
|
||||
const teaVersion = await initializeTeaCli();
|
||||
|
||||
if (!teaVersion) throw new Error("no tea");
|
||||
log.info("Syncing pantry", teaVersion);
|
||||
await asyncExec(`DEBUG=1 "${cliBinPath}" --sync --env=false`);
|
||||
log.info("syncing pantry");
|
||||
await hooks.useSync();
|
||||
log.info("syncing pantry completed");
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import fs from "fs";
|
||||
import { getTeaPath } from "./tea-dir";
|
||||
import { authFileState } from "./auth";
|
||||
import * as https from "https";
|
||||
import { spawn } from "child_process";
|
||||
import path from "path";
|
||||
import { semver } from "@teaxyz/lib";
|
||||
|
||||
type InitState = "NOT_INITIALIZED" | "PENDING" | "INITIALIZED";
|
||||
|
||||
|
@ -63,99 +57,6 @@ export class InitWatcher<T> {
|
|||
return this.initState;
|
||||
}
|
||||
}
|
||||
|
||||
// Be careful with globbing when passing this to a shell which might expand it. Either escape it or quote it.
|
||||
const teaCliPrefix = path.join(getTeaPath(), "tea.xyz/v*");
|
||||
|
||||
export const cliInitializationState = new InitWatcher<string>(async () => {
|
||||
if (!fs.existsSync(path.join(teaCliPrefix, "bin/tea"))) {
|
||||
return installTeaCli();
|
||||
} else {
|
||||
const dir = fs.readlinkSync(teaCliPrefix);
|
||||
const v = semver.parse(dir)?.toString();
|
||||
if (!v) throw new Error(`couldn't parse to semver: ${dir}`);
|
||||
return v;
|
||||
}
|
||||
});
|
||||
|
||||
cliInitializationState.initialize();
|
||||
|
||||
export async function initializeTeaCli(): Promise<string> {
|
||||
if (
|
||||
cliInitializationState.getState() === "INITIALIZED" &&
|
||||
!fs.existsSync(path.join(teaCliPrefix, "bin/tea"))
|
||||
) {
|
||||
cliInitializationState.reset();
|
||||
}
|
||||
return cliInitializationState.observe();
|
||||
}
|
||||
|
||||
//NOTE copy pasta from https://github.com/teaxyz/setup/blob/main/action.js
|
||||
//FIXME ideally we'd not copy pasta this
|
||||
//NOTE using `tar` is not ideal ∵ Windows and even though tar is POSIX it's still not guaranteed to be available
|
||||
async function installTeaCli() {
|
||||
const PREFIX = `${process.env.HOME}/.tea`;
|
||||
|
||||
const midfix = (() => {
|
||||
switch (process.arch) {
|
||||
case "arm64":
|
||||
return `${process.platform}/aarch64`;
|
||||
case "x64":
|
||||
return `${process.platform}/x86-64`;
|
||||
default:
|
||||
throw new Error(`unsupported platform: ${process.platform}/${process.arch}`);
|
||||
}
|
||||
})();
|
||||
|
||||
/// versions.txt is guaranteed semver-sorted
|
||||
const v: string | undefined = await new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(`https://dist.tea.xyz/tea.xyz/${midfix}/versions.txt`, (rsp) => {
|
||||
if (rsp.statusCode != 200) return reject(rsp.statusCode);
|
||||
rsp.setEncoding("utf8");
|
||||
const chunks: string[] = [];
|
||||
rsp.on("data", (x) => chunks.push(x));
|
||||
rsp.on("end", () => {
|
||||
resolve(chunks.join("").trim().split("\n").at(-1));
|
||||
});
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
|
||||
if (!v) throw new Error(`invalid versions.txt for tea/cli`);
|
||||
|
||||
fs.mkdirSync(PREFIX, { recursive: true });
|
||||
|
||||
const exitcode = await new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(`https://dist.tea.xyz/tea.xyz/${midfix}/v${v}.tar.gz`, (rsp) => {
|
||||
if (rsp.statusCode != 200) return reject(rsp.statusCode);
|
||||
const tar = spawn("tar", ["xzf", "-"], {
|
||||
stdio: ["pipe", "inherit", "inherit"],
|
||||
cwd: PREFIX
|
||||
});
|
||||
rsp.pipe(tar.stdin);
|
||||
tar.on("close", resolve);
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
|
||||
if (exitcode != 0) {
|
||||
throw new Error(`tar: ${exitcode}`);
|
||||
}
|
||||
|
||||
const oldwd = process.cwd();
|
||||
process.chdir(`${PREFIX}/tea.xyz`);
|
||||
if (fs.existsSync(`v*`)) fs.unlinkSync(`v*`);
|
||||
fs.symlinkSync(`v${v}`, `v*`, "dir");
|
||||
if (fs.existsSync(`v0`)) fs.unlinkSync(`v0`);
|
||||
fs.symlinkSync(`v${v}`, `v0`, "dir"); //FIXME
|
||||
process.chdir(oldwd);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
export default async function initialize(): Promise<string> {
|
||||
const [version] = await Promise.all([initializeTeaCli(), authFileState.observe()]);
|
||||
return version;
|
||||
export default async function initialize() {
|
||||
await authFileState.observe();
|
||||
}
|
||||
|
|
|
@ -13,8 +13,6 @@ import log from "./logger";
|
|||
import { syncLogsAt } from "./v1-client";
|
||||
import { installPackage, openPackageEntrypointInTerminal, syncPantry } from "./cli";
|
||||
|
||||
import { initializeTeaCli, cliInitializationState } from "./initialize";
|
||||
|
||||
import { getAutoUpdateStatus, getUpdater, isDev } from "./auto-updater";
|
||||
|
||||
import { loadPackageCache, writePackageCache } from "./package";
|
||||
|
@ -59,8 +57,7 @@ export default function initializeHandlers({ notifyMainWindow }: HandlerOptions)
|
|||
ipcMain.handle("get-session", async () => {
|
||||
try {
|
||||
log.info("getting session");
|
||||
const [session, cliVersion] = await Promise.all([readSessionData(), initializeTeaCli()]);
|
||||
session.teaVersion = cliVersion;
|
||||
const session = await readSessionData();
|
||||
log.debug(session ? "found session data" : "no session data found");
|
||||
return session;
|
||||
} catch (error) {
|
||||
|
@ -168,10 +165,6 @@ export default function initializeHandlers({ notifyMainWindow }: HandlerOptions)
|
|||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
} finally {
|
||||
if (fullName === "tea.xyz") {
|
||||
cliInitializationState.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -193,21 +186,6 @@ export default function initializeHandlers({ notifyMainWindow }: HandlerOptions)
|
|||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-tea-version", async () => {
|
||||
try {
|
||||
log.info("installing tea cli");
|
||||
const version = await initializeTeaCli();
|
||||
if (!version) {
|
||||
throw new Error("failed to install tea cli");
|
||||
}
|
||||
|
||||
return { version, message: "" };
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return { version: "", message: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("topbar-double-click", async (event: Electron.IpcMainInvokeEvent) => {
|
||||
const mainWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
if (mainWindow) {
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
"@sentry/electron": "^4.4.0",
|
||||
"@sentry/svelte": "^7.47.0",
|
||||
"@sentry/vite-plugin": "^0.7.2",
|
||||
"@teaxyz/lib": "^0.2.2",
|
||||
"@teaxyz/lib": "^0.3.0",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@vitest/coverage-c8": "^0.27.1",
|
||||
|
|
|
@ -30,7 +30,7 @@ importers:
|
|||
'@sveltejs/adapter-static': ^1.0.0-next.48
|
||||
'@sveltejs/kit': ^1.15.9
|
||||
'@tea/ui': workspace:*
|
||||
'@teaxyz/lib': ^0.2.2
|
||||
'@teaxyz/lib': ^0.3.0
|
||||
'@testing-library/jest-dom': ^5.16.5
|
||||
'@testing-library/svelte': ^3.2.2
|
||||
'@testing-library/webdriverio': ^3.2.1
|
||||
|
@ -110,7 +110,7 @@ importers:
|
|||
'@sentry/electron': 4.5.0
|
||||
'@sentry/svelte': 7.51.2_svelte@3.59.1
|
||||
'@sentry/vite-plugin': 0.7.2
|
||||
'@teaxyz/lib': 0.2.2
|
||||
'@teaxyz/lib': 0.3.0
|
||||
'@types/electron': 1.6.10
|
||||
'@types/mousetrap': 1.6.11
|
||||
'@vitest/coverage-c8': 0.27.3_jsdom@21.1.2
|
||||
|
@ -3637,8 +3637,8 @@ packages:
|
|||
tailwindcss: 3.3.2
|
||||
dev: false
|
||||
|
||||
/@teaxyz/lib/0.2.2:
|
||||
resolution: {integrity: sha512-xChLHuuwbUXZcHMmqMsGIB6RbXqEb1hXgRYJk4Zg3KfozhRllXuiZ6VkUZYPib66ygMjPlndclG7tj2cDlsLkg==}
|
||||
/@teaxyz/lib/0.3.0:
|
||||
resolution: {integrity: sha512-NFoVdSE4iX5JBdiXXOo0aGk4fsTb5zjBOqkY9ldA8asa8pITSJjPOxoa34H7gkkxuTcjv/zjeiOREveWdibcdA==}
|
||||
dependencies:
|
||||
'@deno/shim-crypto': 0.3.1
|
||||
'@deno/shim-deno': 0.16.1
|
||||
|
|
Loading…
Reference in a new issue