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
|
syncPackageTopicSubscriptions
|
||||||
} from "./libs/push-notification";
|
} from "./libs/push-notification";
|
||||||
|
|
||||||
import init, { initializeTeaCli } from "./libs/initialize";
|
import init from "./libs/initialize";
|
||||||
import { readSessionData } from "./libs/auth";
|
import { readSessionData } from "./libs/auth";
|
||||||
|
|
||||||
import { isDev } from "./libs/auto-updater";
|
import { isDev } from "./libs/auto-updater";
|
||||||
|
@ -38,11 +38,10 @@ if (app.isPackaged) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Sentry.configureScope(async (scope) => {
|
Sentry.configureScope(async (scope) => {
|
||||||
const [session, cliVersion] = await Promise.all([readSessionData(), initializeTeaCli()]);
|
const session = await readSessionData();
|
||||||
scope.setUser({
|
scope.setUser({
|
||||||
id: session.device_id, // device_id this should exist in our pg db: developer_id is to many device_id
|
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
|
username: session?.user?.login || "" // github username or handler
|
||||||
tea: cliVersion
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setSentryLogging(Sentry);
|
setSentryLogging(Sentry);
|
||||||
|
|
|
@ -1,139 +1,76 @@
|
||||||
import { spawn, exec } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import { getGuiPath, getTeaPath } from "./tea-dir";
|
import { getGuiPath } from "./tea-dir";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { initializeTeaCli } from "./initialize";
|
import { hooks } from "@teaxyz/lib";
|
||||||
|
|
||||||
import { app } from "electron";
|
|
||||||
import log from "./logger";
|
import log from "./logger";
|
||||||
import { MainWindowNotifier } from "./types";
|
import { MainWindowNotifier } from "./types";
|
||||||
|
import { Installation, Package, porcelain } from "@teaxyz/lib";
|
||||||
// Be careful with globbing when passing this to a shell which might expand it. Either escape it or quote it.
|
import type { Resolution } from "@teaxyz/lib/script/src/plumbing/resolve";
|
||||||
export const cliBinPath = path.join(getTeaPath(), "tea.xyz/v*/bin/tea");
|
|
||||||
|
|
||||||
export async function installPackage(
|
export async function installPackage(
|
||||||
full_name: string,
|
full_name: string,
|
||||||
version: string,
|
version: string,
|
||||||
notifyMainWindow: MainWindowNotifier
|
notifyMainWindow: MainWindowNotifier
|
||||||
) {
|
) {
|
||||||
const teaVersion = await initializeTeaCli();
|
const notifier = newInstallProgressNotifier(full_name, notifyMainWindow);
|
||||||
const progressNotifier = newInstallProgressNotifier(full_name, notifyMainWindow);
|
|
||||||
|
|
||||||
if (!teaVersion) throw new Error("no tea");
|
|
||||||
|
|
||||||
const qualifedPackage = `${full_name}@${version}`;
|
const qualifedPackage = `${full_name}@${version}`;
|
||||||
|
|
||||||
log.info(`installing package ${qualifedPackage}`);
|
log.info(`installing package ${qualifedPackage}`);
|
||||||
|
const result = await porcelain.install(qualifedPackage, notifier);
|
||||||
let stdout = "";
|
console.log(`successfully installed ${qualifedPackage}`, result);
|
||||||
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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
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
|
// the totall number of packages to install - this is set by the "resolved" message
|
||||||
let numberOfPackages = 1;
|
let numberOfPackages = 1;
|
||||||
|
|
||||||
// the current package number - this is incremented by the "installed" or "downloaded" message
|
// the current package number - this is incremented by the "installed" or "downloaded" message
|
||||||
let currentPackageNumber = 0;
|
let currentPackageNumber = 0;
|
||||||
|
|
||||||
return function (msg: any) {
|
return {
|
||||||
if (msg.status !== "downloading" && msg.status !== "installing") {
|
resolved: ({ pending }: Resolution) => {
|
||||||
log.info("cli:", msg);
|
numberOfPackages = pending.length ?? 1;
|
||||||
}
|
log.info(`resolved ${numberOfPackages} packages to install`);
|
||||||
|
},
|
||||||
if (msg.status === "resolved") {
|
installing: ({ pkg, progress }: { pkg: Package; progress: number | undefined }) => {
|
||||||
numberOfPackages = msg.pkgs?.length ?? 1;
|
log.info(`installing ${pkg.project}@${pkg.version} - ${progress}`);
|
||||||
log.info(`installing ${numberOfPackages} packages`);
|
if (progress && progress > 0) {
|
||||||
} else if (msg.status === "downloading") {
|
// how many total packages are completed
|
||||||
counter++;
|
const completedProgress = (currentPackageNumber / numberOfPackages) * 100;
|
||||||
if (counter % 10 !== 0) return;
|
// overallProgress is the total packages completed plus the percentage of the current package
|
||||||
|
const overallProgress = completedProgress + (progress / numberOfPackages) * 100;
|
||||||
const { received = 0, "content-size": contentSize = 0 } = msg;
|
notifyMainWindow("install-progress", { full_name, progress: overallProgress });
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
} else if (msg.status === "installed") {
|
},
|
||||||
|
installed: (installation: Installation) => {
|
||||||
|
log.info("installed", installation);
|
||||||
|
const { project, version } = installation.pkg;
|
||||||
|
|
||||||
currentPackageNumber++;
|
currentPackageNumber++;
|
||||||
const progress = (currentPackageNumber / numberOfPackages) * 100;
|
const progress = (currentPackageNumber / numberOfPackages) * 100;
|
||||||
notifyMainWindow("install-progress", { full_name, progress });
|
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) => {
|
// the tea cli package is needed to open any other package in the terminal, so make sure it's installed and return the path
|
||||||
try {
|
async function installTeaCli() {
|
||||||
const [full_name, version] = rawPkg.split("=");
|
const installations = await porcelain.install("tea.xyz");
|
||||||
notifyMainWindow("pkg-installed", { full_name, version });
|
const teaPkg = installations.find((i) => i.pkg.project === "tea.xyz");
|
||||||
} catch (err) {
|
if (!teaPkg) {
|
||||||
log.error("failed to notify package installed", err);
|
throw new Error("could not find or install tea cli!");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
return teaPkg.path.join("bin/tea");
|
||||||
|
}
|
||||||
|
|
||||||
export async function openPackageEntrypointInTerminal(pkg: string) {
|
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} `;
|
let sh = `"${cliBinPath}" --sync --env=false +${pkg} `;
|
||||||
switch (pkg) {
|
switch (pkg) {
|
||||||
case "github.com/AUTOMATIC1111/stable-diffusion-webui":
|
case "github.com/AUTOMATIC1111/stable-diffusion-webui":
|
||||||
|
@ -194,24 +131,8 @@ const createCommandScriptFile = async (cmd: string): Promise<string> => {
|
||||||
return tmpFilePath;
|
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() {
|
export async function syncPantry() {
|
||||||
const teaVersion = await initializeTeaCli();
|
log.info("syncing pantry");
|
||||||
|
await hooks.useSync();
|
||||||
if (!teaVersion) throw new Error("no tea");
|
log.info("syncing pantry completed");
|
||||||
log.info("Syncing pantry", teaVersion);
|
|
||||||
await asyncExec(`DEBUG=1 "${cliBinPath}" --sync --env=false`);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import fs from "fs";
|
|
||||||
import { getTeaPath } from "./tea-dir";
|
|
||||||
import { authFileState } from "./auth";
|
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";
|
type InitState = "NOT_INITIALIZED" | "PENDING" | "INITIALIZED";
|
||||||
|
|
||||||
|
@ -63,99 +57,6 @@ export class InitWatcher<T> {
|
||||||
return this.initState;
|
return this.initState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export default async function initialize() {
|
||||||
// Be careful with globbing when passing this to a shell which might expand it. Either escape it or quote it.
|
await authFileState.observe();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,6 @@ import log from "./logger";
|
||||||
import { syncLogsAt } from "./v1-client";
|
import { syncLogsAt } from "./v1-client";
|
||||||
import { installPackage, openPackageEntrypointInTerminal, syncPantry } from "./cli";
|
import { installPackage, openPackageEntrypointInTerminal, syncPantry } from "./cli";
|
||||||
|
|
||||||
import { initializeTeaCli, cliInitializationState } from "./initialize";
|
|
||||||
|
|
||||||
import { getAutoUpdateStatus, getUpdater, isDev } from "./auto-updater";
|
import { getAutoUpdateStatus, getUpdater, isDev } from "./auto-updater";
|
||||||
|
|
||||||
import { loadPackageCache, writePackageCache } from "./package";
|
import { loadPackageCache, writePackageCache } from "./package";
|
||||||
|
@ -59,8 +57,7 @@ export default function initializeHandlers({ notifyMainWindow }: HandlerOptions)
|
||||||
ipcMain.handle("get-session", async () => {
|
ipcMain.handle("get-session", async () => {
|
||||||
try {
|
try {
|
||||||
log.info("getting session");
|
log.info("getting session");
|
||||||
const [session, cliVersion] = await Promise.all([readSessionData(), initializeTeaCli()]);
|
const session = await readSessionData();
|
||||||
session.teaVersion = cliVersion;
|
|
||||||
log.debug(session ? "found session data" : "no session data found");
|
log.debug(session ? "found session data" : "no session data found");
|
||||||
return session;
|
return session;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -168,10 +165,6 @@ export default function initializeHandlers({ notifyMainWindow }: HandlerOptions)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(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) => {
|
ipcMain.handle("topbar-double-click", async (event: Electron.IpcMainInvokeEvent) => {
|
||||||
const mainWindow = BrowserWindow.fromWebContents(event.sender);
|
const mainWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
"@sentry/electron": "^4.4.0",
|
"@sentry/electron": "^4.4.0",
|
||||||
"@sentry/svelte": "^7.47.0",
|
"@sentry/svelte": "^7.47.0",
|
||||||
"@sentry/vite-plugin": "^0.7.2",
|
"@sentry/vite-plugin": "^0.7.2",
|
||||||
"@teaxyz/lib": "^0.2.2",
|
"@teaxyz/lib": "^0.3.0",
|
||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/mousetrap": "^1.6.11",
|
"@types/mousetrap": "^1.6.11",
|
||||||
"@vitest/coverage-c8": "^0.27.1",
|
"@vitest/coverage-c8": "^0.27.1",
|
||||||
|
|
|
@ -30,7 +30,7 @@ importers:
|
||||||
'@sveltejs/adapter-static': ^1.0.0-next.48
|
'@sveltejs/adapter-static': ^1.0.0-next.48
|
||||||
'@sveltejs/kit': ^1.15.9
|
'@sveltejs/kit': ^1.15.9
|
||||||
'@tea/ui': workspace:*
|
'@tea/ui': workspace:*
|
||||||
'@teaxyz/lib': ^0.2.2
|
'@teaxyz/lib': ^0.3.0
|
||||||
'@testing-library/jest-dom': ^5.16.5
|
'@testing-library/jest-dom': ^5.16.5
|
||||||
'@testing-library/svelte': ^3.2.2
|
'@testing-library/svelte': ^3.2.2
|
||||||
'@testing-library/webdriverio': ^3.2.1
|
'@testing-library/webdriverio': ^3.2.1
|
||||||
|
@ -110,7 +110,7 @@ importers:
|
||||||
'@sentry/electron': 4.5.0
|
'@sentry/electron': 4.5.0
|
||||||
'@sentry/svelte': 7.51.2_svelte@3.59.1
|
'@sentry/svelte': 7.51.2_svelte@3.59.1
|
||||||
'@sentry/vite-plugin': 0.7.2
|
'@sentry/vite-plugin': 0.7.2
|
||||||
'@teaxyz/lib': 0.2.2
|
'@teaxyz/lib': 0.3.0
|
||||||
'@types/electron': 1.6.10
|
'@types/electron': 1.6.10
|
||||||
'@types/mousetrap': 1.6.11
|
'@types/mousetrap': 1.6.11
|
||||||
'@vitest/coverage-c8': 0.27.3_jsdom@21.1.2
|
'@vitest/coverage-c8': 0.27.3_jsdom@21.1.2
|
||||||
|
@ -3637,8 +3637,8 @@ packages:
|
||||||
tailwindcss: 3.3.2
|
tailwindcss: 3.3.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@teaxyz/lib/0.2.2:
|
/@teaxyz/lib/0.3.0:
|
||||||
resolution: {integrity: sha512-xChLHuuwbUXZcHMmqMsGIB6RbXqEb1hXgRYJk4Zg3KfozhRllXuiZ6VkUZYPib66ygMjPlndclG7tj2cDlsLkg==}
|
resolution: {integrity: sha512-NFoVdSE4iX5JBdiXXOo0aGk4fsTb5zjBOqkY9ldA8asa8pITSJjPOxoa34H7gkkxuTcjv/zjeiOREveWdibcdA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@deno/shim-crypto': 0.3.1
|
'@deno/shim-crypto': 0.3.1
|
||||||
'@deno/shim-deno': 0.16.1
|
'@deno/shim-deno': 0.16.1
|
||||||
|
|
Loading…
Reference in a new issue