show version correctly (#544)

* show version correctly

* fix lint/prettier

* button style
This commit is contained in:
ABevier 2023-05-03 00:53:38 -04:00 committed by GitHub
parent 6c8bc7585f
commit 1d282ac7e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 572 additions and 82 deletions

View file

@ -1,13 +1,11 @@
import fs from "fs";
import { getGuiPath } from "./tea-dir";
import log from "./logger";
import semver from "semver";
import { cliBinPath, asyncExec } from "./cli";
import { createInitialSessionFile } from "./auth";
import semverCompare from "semver/functions/compare";
import { SemVer, isValidSemVer } from "@tea/libtea";
// Versions before this do not support the --json flag
const MINIMUM_TEA_VERSION = "0.28.3";
const MINIMUM_TEA_VERSION = "0.31.2";
const destinationDirectory = getGuiPath();
@ -35,55 +33,46 @@ export async function initializeTeaCli(): Promise<string> {
}
async function initializeTeaCliInternal(): Promise<string> {
try {
let binCheck = "";
let needsUpdate = false;
let binCheck = "";
let needsUpdate = false;
// Create the destination directory if it doesn't exist
if (!fs.existsSync(destinationDirectory)) {
fs.mkdirSync(destinationDirectory, { recursive: true });
}
// replace this with max's pr
const curlCommand = `curl --insecure -L -o "${cliBinPath}" "${binaryUrl}"`;
const exists = fs.existsSync(cliBinPath);
if (exists) {
log.info("binary tea already exists at", cliBinPath);
try {
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
const teaVersion = binCheck.toString().split(" ")[1];
if (semverCompare(teaVersion, MINIMUM_TEA_VERSION) < 0) {
log.info("binary tea version is too old, updating");
needsUpdate = true;
}
} catch (error) {
// probably binary is not executable or no permission
log.error("Error checking tea binary version:", error);
needsUpdate = true;
await asyncExec(`cd ${destinationDirectory} && rm tea`);
}
}
if (!exists || needsUpdate) {
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);
}
}
const version = binCheck.toString().split(" ")[1];
log.info("binary tea version:", version);
return semver.valid(version.trim()) ? version : "";
} catch (error) {
log.error(error);
return "";
// Create the destination directory if it doesn't exist
if (!fs.existsSync(destinationDirectory)) {
fs.mkdirSync(destinationDirectory, { recursive: true });
}
// replace this with max's pr
const curlCommand = `curl --insecure -L -o "${cliBinPath}" "${binaryUrl}"`;
const exists = fs.existsSync(cliBinPath);
if (exists) {
log.info("binary tea already exists at", cliBinPath);
try {
binCheck = await asyncExec(`cd ${destinationDirectory} && ./tea --version`);
const teaVersion = binCheck.toString().split(" ")[1].trim();
if (new SemVer(teaVersion).compare(new SemVer(MINIMUM_TEA_VERSION)) < 0) {
log.info("binary tea version is too old, updating");
needsUpdate = true;
}
} catch (error) {
// probably binary is not executable or no permission
log.error("Error checking tea binary version:", error);
needsUpdate = true;
await asyncExec(`cd ${destinationDirectory} && rm tea`);
}
}
if (!exists || needsUpdate) {
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`);
}
const version = binCheck.toString().split(" ")[1];
log.info("binary tea version:", version);
return isValidSemVer(version.trim()) ? version : "";
}
export default async function initialize(): Promise<string> {

View file

@ -2,12 +2,11 @@
import fs from "fs";
import path from "path";
import { app } from "electron";
import semver, { SemVer } from "semver";
import log from "./logger";
import type { InstalledPackage } from "../../src/libs/types";
import semverCompare from "semver/functions/compare";
import { mkdirp } from "mkdirp";
import fetch from "node-fetch";
import { SemVer, isValidSemVer } from "@tea/libtea";
type Dir = {
name: string;
@ -34,26 +33,26 @@ export async function getInstalledPackages(): Promise<InstalledPackage[]> {
log.info("recursively reading:", pkgsPath);
const folders = await deepReadDir({
dir: pkgsPath,
continueDeeper: (name: string) => !semver.valid(name) && name !== ".tea",
filter: (name: string) => !!semver.valid(name) && name !== ".tea"
continueDeeper: (name: string) => !isValidSemVer(name) && name !== ".tea",
filter: (name: string) => !!isValidSemVer(name) && name !== ".tea"
});
const bottles = folders
.map((p: string) => p.split(".tea/")[1])
.map(parseVersionFromPath)
.filter((v): v is ParsedVersion => !!v)
.sort((a, b) => semverCompare(b.semVer, a.semVer));
.sort((a, b) => b.semVer.compare(a.semVer));
log.info("installed bottles:", bottles.length);
return bottles.reduce<InstalledPackage[]>((pkgs, bottle) => {
const pkg = pkgs.find((v) => v.full_name === bottle.full_name);
if (pkg) {
pkg.installed_versions.push(bottle.semVer.version);
pkg.installed_versions.push(bottle.semVer.toString());
} else {
pkgs.push({
full_name: bottle.full_name,
installed_versions: [bottle.semVer.version]
installed_versions: [bottle.semVer.toString()]
});
}
return pkgs;
@ -65,7 +64,7 @@ const parseVersionFromPath = (versionPath: string): ParsedVersion | null => {
const path = versionPath.trim().split("/");
const version = path.pop();
return {
semVer: new SemVer(semver.clean(version || "") || ""),
semVer: new SemVer(version ?? ""),
full_name: path.join("/")
};
} catch (e) {
@ -119,9 +118,9 @@ export const deepReadDir = async ({
if (f.isDirectory() && deeper) {
const nextFiles = await deepReadDir({ dir: nextPath, continueDeeper, filter });
arrayOfFiles.push(...nextFiles);
} else if (filter && filter(f.name)) {
} else if (!f.isSymbolicLink() && filter && filter(f.name)) {
arrayOfFiles.push(nextPath);
} else if (!filter) {
} else if (!f.isSymbolicLink() && !filter) {
arrayOfFiles.push(nextPath);
}
}

View file

@ -39,6 +39,7 @@
"@sveltejs/adapter-static": "^1.0.0-next.48",
"@sveltejs/kit": "^1.15.9",
"@tea/ui": "workspace:*",
"@tea/libtea": "workspace:*",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/svelte": "^3.2.2",
"@types/bcryptjs": "^2.4.2",
@ -103,7 +104,6 @@
"mousetrap": "^1.6.5",
"pushy-electron": "^1.0.11",
"renderer": "link:@types/electron/renderer",
"semver": "^7.3.8",
"svelte-infinite-scroll": "^2.0.1",
"svelte-markdown": "^0.2.3",
"svelte-watch-resize": "^1.0.3",
@ -114,7 +114,8 @@
},
"pnpm": {
"onlyBuiltDependencies": [
"@tea/ui"
"@tea/ui",
"@tea/libtea"
]
},
"homepage": "https://tea.xyz",

View file

@ -5,7 +5,6 @@
import Button from "@tea/ui/button/button.svelte";
import ButtonIcon from "@tea/ui/button-icon/button-icon.svelte";
import ToolTip from "@tea/ui/tool-tip/tool-tip.svelte";
import semverCompare from "semver/functions/compare";
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
import type { GUIPackage } from "$libs/types";
@ -16,6 +15,7 @@
import PackageImage from "../package-card/bg-image.svelte";
import PackageVersionSelector from "$components/package-install-button/package-version-selector.svelte";
import { fixPackageName } from "$libs/packages/pkg-utils";
import { semverCompare } from "$libs/packages/pkg-utils";
export let pkg: GUIPackage;
let installing = false;

View file

@ -57,7 +57,7 @@
<div class="flex w-fit flex-col items-center">
<div class="install-button {layout}" on:mousedown={preventPropagation}>
{#if pkg.state === PackageStates.INSTALLED}
<PackageInstalledBadge version={pkg.version} />
<PackageInstalledBadge {pkg} />
{:else}
<PackageInstallButton
{pkg}

View file

@ -24,6 +24,13 @@
return state === PackageStates.INSTALLING || state === PackageStates.UPDATING;
};
const getVersion = (pkg: GUIPackage) => {
if (pkg.state === PackageStates.INSTALLED) {
return pkg.installed_versions?.[0] ?? pkg.version;
}
return pkg.version;
};
const badgeClass: Record<PackageStates, string> = {
[PackageStates.AVAILABLE]: "install-badge",
[PackageStates.INSTALLING]: "install-badge",
@ -53,7 +60,7 @@
>
<div class="flex items-center gap-x-2">
<div>{ctaLabel}</div>
<div class="version-label {badgeClass[pkg.state]}">{pkg.version}</div>
<div class="version-label {badgeClass[pkg.state]}">v{getVersion(pkg)}</div>
</div>
{#if hasVersionSelectorDropdown}
<i class="icon-downward-arrow flex" />

View file

@ -1,5 +1,7 @@
<script lang="ts">
export let version = "1.0.0";
import type { GUIPackage } from "$libs/types";
export let pkg: GUIPackage | null = null;
</script>
<div class="container relative h-full">
@ -7,7 +9,7 @@
<i class="icon-check-circle-o flex text-sm text-[#00ffd0]" />
<div class="text-xs">INSTALLED</div>
<div class="rounded-sm bg-white px-1 text-[10px] leading-[12px] text-black">
v{version}
v{pkg?.installed_versions?.[0]}
</div>
</div>
</div>

View file

@ -2,7 +2,7 @@
import { PackageStates, type GUIPackage } from "$libs/types";
import clickOutside from "@tea/ui/lib/clickOutside";
import PackageStateButton from "./package-install-button.svelte";
import semver from "semver";
import { semverCompare } from "$libs/packages/pkg-utils";
export let buttonSize: "small" | "large" = "small";
export let pkg: GUIPackage;
@ -27,7 +27,7 @@
$: installedVersions = pkg.installed_versions || [];
$: allVersions = Array.from(new Set([pkg.version, ...availableVersions])).sort(
(a: string, b: string) => semver.rcompare(a, b)
(a: string, b: string) => semverCompare(b, a)
);
const handleClick = (evt: MouseEvent, version: string) => {

View file

@ -1,6 +1,6 @@
import log from "$libs/logger";
import type { GUIPackage } from "$libs/types";
import { clean } from "semver";
import semverCompare from "semver/functions/compare";
import { SemVer } from "@tea/libtea";
// Find a list of available versions for a package based on the bottles
export const findAvailableVersions = (pkg: Pick<GUIPackage, "bottles" | "version">) => {
@ -15,10 +15,19 @@ export const findAvailableVersions = (pkg: Pick<GUIPackage, "bottles" | "version
if (b.arch === arch) versionSet.add(b.version);
}
return Array.from(versionSet).sort((a, b) => semverCompare(cleanVersion(b), cleanVersion(a)));
return Array.from(versionSet).sort((a, b) => semverCompare(b, a));
};
export const cleanVersion = (version: string) => clean(version) || "0.0.0";
export const semverCompare = (a: string, b: string) => {
try {
return new SemVer(a).compare(new SemVer(b));
} catch (err) {
log.error(`Failed to compare versions ${a} and ${b}`, err);
// This is bad if it happens, but it's better than crashing, the tea semver library is very permissive
// and it would be extremely unlikely for this to happen in practice as how would something get bottled in the first place?
return a.localeCompare(b);
}
};
// Add a new version to the list of installed versions while maintaining the sort order
export const addInstalledVersion = (
@ -29,9 +38,7 @@ export const addInstalledVersion = (
return [newVersion];
}
return [...installedVersions, newVersion].sort((a, b) =>
semverCompare(cleanVersion(b), cleanVersion(a))
);
return [...installedVersions, newVersion].sort((a, b) => semverCompare(b, a));
};
export const findRecentInstalledVersion = (pkg: GUIPackage) => {
@ -50,3 +57,13 @@ export const isInstalling = (pkg: GUIPackage) => {
export const fixPackageName = (title: string) => {
return title.replace("-", "\u2011");
};
// Checks if an installed package is up to date. It is assumed that the package is installed.
export const isPackageUpToDate = (pkg: GUIPackage) => {
if (!pkg.installed_versions?.length) {
return false;
}
// if the installed version is equal or newer than the latest version, it's up to date
return semverCompare(pkg.installed_versions[0], pkg.version) >= 0;
};

View file

@ -27,6 +27,7 @@ import { notificationStore } from "$libs/stores";
import withRetry from "$libs/utils/retry";
import log from "$libs/logger";
import { isPackageUpToDate } from "../packages/pkg-utils";
const packageRefreshInterval = 1000 * 60 * 60; // 1 hour
@ -80,7 +81,7 @@ export default function initPackagesStore() {
return PackageStates.AVAILABLE;
}
const isUpToDate = pkg.version === pkg.installed_versions?.[0];
const isUpToDate = isPackageUpToDate(pkg);
if (isInstalling(pkg)) {
const hasNoVersions = !pkg.installed_versions?.length;

View file

@ -96,7 +96,7 @@
{/if}
<div>
<Button
class="h-8 w-48 text-xs"
class="h-8 w-48 p-2 text-xs"
loading={updating}
type="plain"
color="secondary"

View file

@ -17,7 +17,8 @@
"$libs/*": ["./src/libs/*"],
"@native": ["./src/libs/native-electron.ts"],
"$components/*": ["./src/components/*"],
"@tea/ui/*": ["../ui/src/*"]
"@tea/ui/*": ["../ui/src/*"],
"@tea/libtea/*": ["../libtea/src/*"]
},
"typeRoots": ["./node_modules/@types", "./types"]
},

View file

@ -9,6 +9,7 @@ const config: UserConfig = {
resolve: {
alias: {
"@tea/ui/*": path.resolve("../ui/src/*"),
"@tea/libtea/*": path.resolve("../libtea/src/*"),
// this dynamic-ish static importing is followed by the svelte build
// but for vscode editing intellisense tsconfig.json is being used
"@native": isMock

View file

@ -0,0 +1,17 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
coverage/*
build/*
dist/*
electron/dist/*

View file

@ -0,0 +1,22 @@
module.exports = {
root: true,
globals: {
NodeJS: true
},
parser: "@typescript-eslint/parser",
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
plugins: ["@typescript-eslint"],
ignorePatterns: ["*.cjs"],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
},
rules: {
"@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }]
}
};

2
modules/libtea/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist/*

View file

@ -0,0 +1 @@
dist/*

View file

@ -0,0 +1,10 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"pluginSearchDirs": ["../../node_modules"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View file

@ -0,0 +1,17 @@
{
"name": "@tea/libtea",
"version": "0.0.1",
"description": "library of tea utilities",
"author": "tea.xyz",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"devDependencies": {
"typescript": "^4.7.4"
},
"scripts": {
"build": "tsc --build",
"postinstall": "tsc --build",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
}
}

View file

@ -0,0 +1 @@
export { default as SemVer, isValidSemVer } from "./semver";

View file

@ -0,0 +1,376 @@
//
// This is a copy from tea/cli 0.31.2
// https://github.com/teaxyz/cli/blob/31c2fc0bbddc41cbd63d9ee14593efe3f9b09301/src/utils/semver.ts
//
/**
* we have our own implementation because open source is full of weird
* but *almost* valid semver schemes, eg:
* openssl 1.1.1q
* ghc 5.64.3.2
* it also allows us to implement semver_intersection without hating our lives
*/
export default class SemVer {
readonly components: number[];
major: number;
minor: number;
patch: number;
//FIXME parse these
readonly prerelease: string[] = [];
readonly build: string[] = [];
readonly raw: string;
readonly pretty?: string;
constructor(
input: string | number[] | Range | SemVer,
{ tolerant }: { tolerant: boolean } = { tolerant: false }
) {
if (typeof input == "string") {
const vprefix = input.startsWith("v");
const raw = vprefix ? input.slice(1) : input;
const parts = raw.split(".");
if (parts.length < 3 && !vprefix && !tolerant)
throw new Error(`too short to parse without a \`v\` prefix: ${input}`);
let pretty_is_raw = false;
this.components = parts.flatMap((x, index) => {
const match = x.match(/^(\d+)([a-z])$/);
if (match) {
if (index != parts.length - 1) throw new Error(`invalid version: ${input}`);
const n = parseInt(match[1]);
if (isNaN(n)) throw new Error(`invalid version: ${input}`);
pretty_is_raw = true;
return [n, char_to_num(match[2])];
} else if (/^\d+$/.test(x)) {
const n = parseInt(x); // parseInt will parse eg. `5-start` to `5`
if (isNaN(n)) throw new Error(`invalid version: ${input}`);
return [n];
} else {
throw new Error(`invalid version: ${input}`);
}
});
this.raw = raw;
if (pretty_is_raw) this.pretty = raw;
} else if (input instanceof Range || input instanceof SemVer) {
const v = input instanceof Range ? input.single() : input;
if (!v) throw new Error(`range represents more than a single version: ${input}`);
this.components = v.components;
this.raw = v.raw;
this.pretty = v.pretty;
} else {
this.components = input;
this.raw = input.join(".");
}
this.major = this.components[0];
this.minor = this.components[1] ?? 0;
this.patch = this.components[2] ?? 0;
function char_to_num(c: string) {
return c.charCodeAt(0) - "a".charCodeAt(0) + 1;
}
}
toString(): string {
return (
this.pretty ??
(this.components.length <= 3
? `${this.major}.${this.minor}.${this.patch}`
: this.components.join("."))
);
}
eq(that: SemVer): boolean {
return this.compare(that) == 0;
}
neq(that: SemVer): boolean {
return this.compare(that) != 0;
}
gt(that: SemVer): boolean {
return this.compare(that) > 0;
}
lt(that: SemVer): boolean {
return this.compare(that) < 0;
}
compare(that: SemVer): number {
return _compare(this, that);
}
[Symbol.for("Deno.customInspect")]() {
return this.toString();
}
}
/// the same as the constructor but swallows the error returning undefined instead
/// also slightly more tolerant parsing
export function parse(input: string, tolerant = true) {
try {
return new SemVer(input, { tolerant });
} catch {
return undefined;
}
}
export function isValidSemVer(input: string, tolerant = false) {
return parse(input, tolerant) != undefined;
}
/// we dont support as much as node-semver but we refuse to do so because it is badness
export class Range {
// contract [0, 1] where 0 != 1 and 0 < 1
readonly set: ([SemVer, SemVer] | SemVer)[] | "*";
constructor(input: string | ([SemVer, SemVer] | SemVer)[]) {
if (input === "*") {
this.set = "*";
} else if (!isString(input)) {
this.set = input;
} else {
input = input.trim();
const err = () => new Error(`invalid semver range: ${input}`);
this.set = input.split(/(?:,|\s*\|\|\s*)/).map((input) => {
let match = input.match(/^>=((\d+\.)*\d+)\s*(<((\d+\.)*\d+))?$/);
if (match) {
const v1 = new SemVer(match[1], { tolerant: true });
const v2 = match[3]
? new SemVer(match[4], { tolerant: true })
: new SemVer([Infinity, Infinity, Infinity]);
return [v1, v2];
} else if ((match = input.match(/^([~=<^])(.+)$/))) {
let v1: SemVer | undefined, v2: SemVer | undefined;
switch (match[1]) {
case "^":
v1 = new SemVer(match[2], { tolerant: true });
// eslint-disable-next-line no-case-declarations
const parts: number[] = [];
for (let i = 0; i < v1.components.length; i++) {
if (v1.components[i] === 0 && i < v1.components.length - 1) {
parts.push(0);
} else {
parts.push(v1.components[i] + 1);
break;
}
}
v2 = new SemVer(parts, { tolerant: true });
return [v1, v2];
case "~":
{
v1 = new SemVer(match[2], { tolerant: true });
if (v1.components.length == 1) {
// yep this is the official policy
v2 = new SemVer([v1.major + 1], { tolerant: true });
} else {
v2 = new SemVer([v1.major, v1.minor + 1], { tolerant: true });
}
}
return [v1, v2];
case "<":
v1 = new SemVer([0], { tolerant: true });
v2 = new SemVer(match[2], { tolerant: true });
return [v1, v2];
case "=":
return new SemVer(match[2], { tolerant: true });
}
}
throw err();
});
if (this.set.length == 0) {
throw err();
}
for (const i of this.set) {
if (isArray(i) && !i[0].lt(i[1])) throw err();
}
}
}
toString(): string {
if (this.set === "*") {
return "*";
} else {
return this.set
.map((v) => {
if (!isArray(v)) return `=${v.toString()}`;
const [v1, v2] = v;
if (v1.major > 0 && v2.major == v1.major + 1 && v2.minor == 0 && v2.patch == 0) {
const v = chomp(v1);
return `^${v}`;
} else if (v2.major == v1.major && v2.minor == v1.minor + 1 && v2.patch == 0) {
const v = chomp(v1);
return `~${v}`;
} else if (v2.major == Infinity) {
const v = chomp(v1);
return `>=${v}`;
} else {
return `>=${chomp(v1)}<${chomp(v2)}`;
}
})
.join(",");
}
}
// eq(that: Range): boolean {
// if (this.set.length !== that.set.length) return false
// for (let i = 0; i < this.set.length; i++) {
// const [a,b] = [this.set[i], that.set[i]]
// if (typeof a !== 'string' && typeof b !== 'string') {
// if (a[0].neq(b[0])) return false
// if (a[1].neq(b[1])) return false
// } else if (a != b) {
// return false
// }
// }
// return true
// }
/// tolerant to stuff in the wild that hasnt semver specifiers
static parse(input: string): Range | undefined {
try {
return new Range(input);
} catch {
if (!/^(\d+\.)*\d+$/.test(input)) return;
// AFAICT this is what people expect
// verified via https://jubianchi.github.io/semver-check/
const parts = input.split(".");
if (parts.length < 3) {
return new Range(`^${input}`);
} else {
return new Range(`~${input}`);
}
}
}
satisfies(version: SemVer): boolean {
if (this.set === "*") {
return true;
} else {
return this.set.some((v) => {
if (isArray(v)) {
const [v1, v2] = v;
return version.compare(v1) >= 0 && version.compare(v2) < 0;
} else {
return version.eq(v);
}
});
}
}
max(versions: SemVer[]): SemVer | undefined {
return versions
.filter((x) => this.satisfies(x))
.sort((a, b) => a.compare(b))
.pop();
}
single(): SemVer | undefined {
if (this.set === "*") return;
if (this.set.length > 1) return;
return isArray(this.set[0]) ? undefined : this.set[0];
}
[Symbol.for("Deno.customInspect")]() {
return this.toString();
}
}
function zip<T, U>(a: T[], b: U[]) {
const N = Math.max(a.length, b.length);
const rv: [T | undefined, U | undefined][] = [];
for (let i = 0; i < N; ++i) {
rv.push([a[i], b[i]]);
}
return rv;
}
function _compare(a: SemVer, b: SemVer): number {
for (const [c, d] of zip(a.components, b.components)) {
if (c != d) return (c ?? 0) - (d ?? 0);
}
return 0;
}
export { _compare as compare };
export function intersect(a: Range, b: Range): Range {
if (b.set === "*") return a;
if (a.set === "*") return b;
// calculate the intersection between two semver.Ranges
const set: ([SemVer, SemVer] | SemVer)[] = [];
for (const aa of a.set) {
for (const bb of b.set) {
if (!isArray(aa) && !isArray(bb)) {
if (aa.eq(bb)) set.push(aa);
} else if (!isArray(aa)) {
const bbb = bb as [SemVer, SemVer];
if (aa.compare(bbb[0]) >= 0 && aa.lt(bbb[1])) set.push(aa);
} else if (!isArray(bb)) {
const aaa = aa as [SemVer, SemVer];
if (bb.compare(aaa[0]) >= 0 && bb.lt(aaa[1])) set.push(bb);
} else {
const a1 = aa[0];
const a2 = aa[1];
const b1 = bb[0];
const b2 = bb[1];
if (a1.compare(b2) >= 0 || b1.compare(a2) >= 0) {
continue;
}
set.push([a1.compare(b1) > 0 ? a1 : b1, a2.compare(b2) < 0 ? a2 : b2]);
}
}
}
if (set.length <= 0) throw new Error(`cannot intersect: ${a} && ${b}`);
return new Range(set);
}
//FIXME yes yes this is not sufficient
export const regex = /\d+\.\d+\.\d+/;
function chomp(v: SemVer) {
return v.toString().replace(/(\.0)+$/g, "") || "0";
}
/**
* Returns whether the payload is an array
*
* @param {any} payload
* @returns {payload is any[]}
*/
export function isArray(payload: unknown): payload is unknown[] {
return getType(payload) === "Array";
}
/**
* Returns whether the payload is a string
*
* @param {*} payload
* @returns {payload is string}
*/
export function isString(payload: unknown): payload is string {
return getType(payload) === "String";
}
/**
* Returns the object type of the given payload
*
* @param {*} payload
* @returns {string}
*/
export function getType(payload: unknown): string {
return Object.prototype.toString.call(payload).slice(8, -1);
}

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"declaration": true,
"module": "ES6",
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"lib": ["es2019"],
"outDir": "./dist"
},
"include": ["./src/**/*.ts"]
}

View file

@ -26,6 +26,7 @@ importers:
'@sveltejs/adapter-node': ^1.0.0-next.101
'@sveltejs/adapter-static': ^1.0.0-next.48
'@sveltejs/kit': ^1.15.9
'@tea/libtea': workspace:*
'@tea/ui': workspace:*
'@testing-library/jest-dom': ^5.16.5
'@testing-library/svelte': ^3.2.2
@ -74,7 +75,6 @@ importers:
prettier-plugin-tailwindcss: ^0.2.8
pushy-electron: ^1.0.11
renderer: link:@types/electron/renderer
semver: ^7.3.8
svelte: ^3.55.1
svelte-check: ^2.8.0
svelte-infinite-scroll: ^2.0.1
@ -122,7 +122,6 @@ importers:
mousetrap: 1.6.5
pushy-electron: 1.0.11
renderer: link:@types/electron/renderer
semver: 7.5.0
svelte-infinite-scroll: 2.0.1
svelte-markdown: 0.2.3_svelte@3.58.0
svelte-watch-resize: 1.0.3
@ -138,6 +137,7 @@ importers:
'@sveltejs/adapter-node': 1.2.4_@sveltejs+kit@1.15.9
'@sveltejs/adapter-static': 1.0.6_@sveltejs+kit@1.15.9
'@sveltejs/kit': 1.15.9_svelte@3.58.0+vite@4.3.3
'@tea/libtea': link:../libtea
'@tea/ui': link:../ui
'@testing-library/jest-dom': 5.16.5
'@testing-library/svelte': 3.2.2_svelte@3.58.0
@ -171,6 +171,12 @@ importers:
vite: 4.3.3
vitest: 0.28.5_jsdom@21.1.2
modules/libtea:
specifiers:
typescript: ^4.7.4
devDependencies:
typescript: 4.9.5
modules/ui:
specifiers:
'@magidoc/plugin-svelte-prismjs': ^3.0.6