#433 install tea binary (#438)

* #433 install tea binary

* #433 check tea cli

* #433 install tea on install package

---------

Co-authored-by: neil molina <neil@neils-MacBook-Pro.local>
This commit is contained in:
Neil 2023-04-12 13:46:51 +08:00 committed by GitHub
parent 7f7efdb79d
commit e7bdad8027
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 109 additions and 180 deletions

View file

@ -15,6 +15,8 @@ import initializePushNotification, {
syncPackageTopicSubscriptions
} from "./libs/push-notification";
import init from "./libs/initialize";
log.info("App starting...");
if (app.isPackaged) {
Sentry.init({
@ -31,6 +33,9 @@ if (app.isPackaged) {
}
});
}
init();
const serveURL = serve({ directory: "." });
const port = process.env.PORT || 3000;
const allowDebug = !app.isPackaged || process.env.DEBUG_BUILD === "1";

View file

@ -1,44 +1,23 @@
import { spawn, execSync } from "child_process";
import { spawn, exec } from "child_process";
import { clean } from "semver";
import { getGuiPath } from "./tea-dir";
import fs from "fs";
import path from "path";
import axios from "axios";
import initializeTeaCli from "./initialize";
import * as log from "electron-log";
import { subscribeToPackageTopic } from "./push-notification";
const destinationDirectory = getGuiPath();
export const cliBinPath = path.join(destinationDirectory, "tea");
export async function installPackage(full_name: string) {
return await new Promise((resolve, reject) => {
let version = "";
let lastError = "";
const teaInstallation = spawn("/usr/local/bin/tea", [`+${full_name}`, "true"]);
const teaVersion = await initializeTeaCli();
teaInstallation.stdout.on("data", (data) => {
console.log("stdout:", data);
});
teaInstallation.stderr.on("data", (err) => {
lastError = err.toString();
if (lastError && lastError.includes("installed") && lastError.includes(full_name)) {
version = lastError.split("/").pop() || "";
}
});
teaInstallation.on("exit", async (code) => {
if (code === 0) {
try {
await subscribeToPackageTopic(full_name);
} catch (error) {
log.error(error);
} finally {
resolve({ version: clean(version) });
}
} else {
reject(new Error(lastError));
}
});
});
if (!teaVersion) throw new Error("no tea");
log.info(`installing package ${full_name}`);
await asyncExec(`cd ${destinationDirectory} && ./tea +${full_name} true`);
}
export async function openTerminal(cmd: string) {
@ -75,19 +54,6 @@ export async function openTerminal(cmd: string) {
}
}
export async function installTeaCli(version: string): Promise<string> {
try {
log.info("installing tea-cli");
const command = 'TEA_YES=1 bash -c "sh <(curl https://tea.xyz)"';
const output = execSync(command, { encoding: "utf-8" });
log.info("tea-cli installed");
return "success";
} catch (error) {
log.error(error);
return error.message;
}
}
const createCommandScriptFile = async (cmd: string): Promise<string> => {
try {
const guiFolder = getGuiPath();
@ -117,3 +83,17 @@ const createCommandScriptFile = async (cmd: string): Promise<string> => {
return "";
}
};
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);
});
});
}

View file

@ -0,0 +1,41 @@
import fs from "fs";
import { getGuiPath } from "./tea-dir";
import * as log from "electron-log";
import semver from "semver";
import { cliBinPath, asyncExec } from "./cli";
const destinationDirectory = getGuiPath();
// TODO: move device_id generation here
// Get the binary path from the current app directory
const binaryUrl = "https://tea.xyz/$(uname)/$(uname -m)";
export default async function initializeTeaCli(): Promise<string> {
let version = "";
let binCheck = "";
// Create the destination directory if it doesn't exist
if (!fs.existsSync(destinationDirectory)) {
fs.mkdirSync(destinationDirectory, { recursive: true });
}
const curlCommand = `curl -L -o "${cliBinPath}" "${binaryUrl}"`;
if (fs.existsSync(cliBinPath)) {
log.info("binary tea already exists at", cliBinPath);
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
} else {
try {
await asyncExec(curlCommand);
log.info("Binary downloaded and saved to", cliBinPath);
await asyncExec("chmod u+x " + cliBinPath);
log.info("Binary is now ready for use at", cliBinPath);
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
} catch (error) {
log.error("Error setting-up tea binary:", error);
}
}
version = binCheck.toString().split(" ")[1];
log.info("binary tea version:", version);
return semver.valid(version.trim()) ? version : "";
}

View file

@ -10,6 +10,8 @@ import path from "path";
import { installPackage, openTerminal, installTeaCli } from "./cli";
import initializeTeaCli from "./initialize";
import { getUpdater } from "./auto-updater";
import fetch from "node-fetch";
@ -174,14 +176,18 @@ export default function initializeHandlers() {
}
});
ipcMain.handle("install-tea-cli", async (_, data) => {
ipcMain.handle("get-tea-version", async () => {
try {
log.info("installing tea cli");
await installTeaCli(data.version);
return "success";
const version = await initializeTeaCli();
if (!version) {
throw new Error("failed to install tea cli");
}
return { version, message: "" };
} catch (error) {
log.error(error);
return error.message;
return { version: "", message: error.message };
}
});
}

View file

@ -1,92 +1,12 @@
<script lang="ts">
import Button from "@tea/ui/button/button.svelte";
import { PackageStates, type GUIPackage } from "$libs/types";
import { openTerminal, isPackageInstalled, installTeaCli } from '@native';
import { packagesStore } from "$libs/stores";
import clickOutside from "@tea/ui/lib/clickOutside";
import ProgressBar from "@tea/ui/progress-bar/progress-bar.svelte";
import { onMount } from "svelte";
import { withFakeLoader } from "$libs/stores/pkgs";
const log = window.require("electron-log");
export let tea:GUIPackage|undefined;
let installing = false;
let installProgress = 0;
let message:string;
let errorMessage = "";
let fakeTimer: NodeJS.Timer;
let checkTeaPoll: NodeJS.Timer | null;
const checkInstalled = async () => {
if (checkTeaPoll) return;
return new Promise((resolve) => {
checkTeaPoll = setInterval(async () => {
try {
log.info("checking if tea is installed");
const installed = await isPackageInstalled("tea.xyz", tea?.version);
log.info("tea is installed:", installed);
if (installed && checkTeaPoll) {
clearInterval(checkTeaPoll);
checkTeaPoll = null;
resolve(null);
close();
}
} catch (error) {
log.error(error);
}
}, 5000); // check every 5s
})
}
import { navStore } from "$libs/stores"
const close = () => {
packagesStore.requireTeaCli.set(false);
navStore.showWelcome.set(false);
}
const onOpenTerminal = async () => {
if (installing) return;
installing = true;
try {
openTerminal(`sh <(curl https://tea.xyz)`);
await checkInstalled();
packagesStore.updatePackage("tea.xyz", {
state: PackageStates.INSTALLED,
installed_versions: [tea?.version || "latest"]
});
} catch (error) {
console.log("install failed")
} finally {
installing = false;
}
}
const setupCli = async () => {
installing = true;
if (tea) {
fakeTimer = withFakeLoader(tea, (p) => {
installProgress = p;
})
try {
message = await installTeaCli(tea.version);
console.log("MESSAGE:", message)
if (message != "success") {
errorMessage = message;
}
} catch (error) {
console.log("install failed cli", error)
} finally {
clearInterval(fakeTimer);
installing = false;
installProgress = 100;
}
}
}
onMount(() => {
setupCli();
})
</script>
<section class="fixed z-50 top-0 left-0 flex items-center justify-center">
@ -99,29 +19,11 @@
<h1 class="text-primary text-4xl mb-4">Welcome to the tea app!</h1>
<p class="font-inter mb-4">This app is your gateway into the world of open-source software. Easily explore and manage packages with a click of a button. This app will notify you of any available software updates to ensure youre safe and secure. Under the hood is the powerful tea cli.</p>
{#if !errorMessage}
{#if installProgress != 100}
<ProgressBar width={installProgress} />
<p class="text-gray text-sm mt-2">initializing the app. please wait for a few seconds</p>
{:else}
<p class="text-gray text-sm mt-2">setup was succesfull!</p>
<Button type="plain" color="secondary" class="w-7/12"
onClick={() => close()}
>
EXPLORE OPEN-SOURCE
</Button>
{/if}
{:else}
<div class="h-10 w-7/12">
<Button type="plain" color="secondary"
loading={installing}
onClick={onOpenTerminal}
>
INSTALL TEA CLI v{tea?tea.version:"latest"}
</Button>
</div>
<p class="text-gray text-sm mt-2">tea cli is required in order to use our app. Clicking the link above will automatically install it via your command-line.</p>
{/if}
<Button type="plain" color="secondary" class="w-7/12"
onClick={() => close()}
>
EXPLORE OPEN-SOURCE
</Button>
</div>
</article>

View file

@ -252,7 +252,7 @@ export const writePackageCache = async (pkgs: Packages) => {
}
};
export const installTeaCli = async (version: string): Promise<string> => {
const res = await ipcRenderer.invoke("install-tea-cli", { version });
return res;
export const installTeaCli = async (): Promise<string> => {
const res = await ipcRenderer.invoke("install-tea-cli");
return res.version;
};

View file

@ -371,6 +371,6 @@ export const writePackageCache = async (pkgs: Packages) => {
console.log("write package cache", pkgs);
};
export const installTeaCli = async (): Promise<string> => {
return "success";
export const installTeaCli = async (): Promise<{ version: string; message: string }> => {
return { version: "0.0.0", message: "" };
};

View file

@ -1,9 +1,14 @@
import { writable } from "svelte/store";
import { goto } from "$app/navigation";
import { installTeaCli } from "@native";
const log = window.require("electron-log");
export default function initNavStore() {
const sideNavOpen = writable<boolean>(false);
const historyStore = writable<string[]>(["/"]);
const showWelcome = writable<boolean>(false);
let history = ["/"];
historyStore.subscribe((v) => (history = v));
@ -16,7 +21,14 @@ export default function initNavStore() {
let isMovingNext = false;
let isMovingBack = false;
installTeaCli().then((version: string) => {
if (!version) {
showWelcome.set(true);
}
});
return {
showWelcome,
historyStore,
sideNavOpen,
prevPath: prevPathStore,

View file

@ -31,8 +31,6 @@ export default function initPackagesStore() {
const packageMap = writable<Packages>({ version: "0", packages: {} });
const packageList = derived(packageMap, ($packages) => Object.values($packages.packages));
const requireTeaCli = writable<boolean>(false);
let packagesIndex: Fuse<GUIPackage>;
const updateAllPackages = (guiPkgs: GUIPackage[]) => {
@ -86,16 +84,6 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
updatePackage(guiPkg.full_name!, updatedPackage);
};
const checkTeaCLIPackage = async (teaPkg: Package, installedPkg?: InstalledPackage) => {
if (!installedPkg) {
requireTeaCli.set(true);
return;
}
const isUpToDate = teaPkg.version === installedPkg?.installed_versions[0];
log.info("check if Tea-CLI is up to date:", isUpToDate);
};
const init = async function () {
log.info("packages store: try initialize");
@ -124,11 +112,6 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
try {
const installedPkgs: InstalledPackage[] = await getInstalledPackages();
log.info("sync test for tea-cli");
const distTea = pkgs.find((p) => p.full_name === "tea.xyz");
const installedTeaPkg = installedPkgs.find((p) => p.full_name === "tea.xyz");
if (distTea) await checkTeaCLIPackage(distTea, installedTeaPkg);
log.info("set NEEDS_UPDATE state to pkgs");
for (const [i, iPkg] of installedPkgs.entries()) {
const pkg = guiPkgs.find((p) => p.full_name === iPkg.full_name);
@ -211,7 +194,6 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
return {
packageList,
syncProgress,
requireTeaCli,
search: async (term: string, limit = 5): Promise<GUIPackage[]> => {
if (!term || !packagesIndex) return [];
// TODO: if online, use algolia else use Fuse

View file

@ -4,7 +4,7 @@
import { page } from '$app/stores';
import { t } from '$libs/translations';
import { afterNavigate } from '$app/navigation';
import { packagesStore, notificationStore } from '$libs/stores';
import { packagesStore, navStore } from '$libs/stores';
import Packages from '$components/packages/packages.svelte';
import { PackageStates, SideMenuOptions, type GUIPackage } from '$libs/types';
// import SortingButtons from "$components/search-packages/sorting-buttons.svelte";
@ -16,7 +16,8 @@
const log = window.require("electron-log");
const { packageList, requireTeaCli } = packagesStore;
const { packageList } = packagesStore;
const { showWelcome } = navStore;
const url = $page.url;
@ -99,8 +100,8 @@
</div>
<SideMenu bind:activeOption={sideMenuOption}/>
{#if $requireTeaCli && teaPkg }
<WelcomeModal tea={teaPkg} />
{#if $showWelcome }
<WelcomeModal />
{/if}
<style>
#content {