mirror of
https://github.com/kunkunsh/kunkun.git
synced 2025-04-11 17:29:44 +00:00
Feature: install extension from JSR (#53)
* chore: upgrade many dependencies * fix: @kksh/svelte, migrate 5 * refactor: move dialog plugin code out of ui package * chore: disable check-types in api build.ts Causing build error in kunkun-services * feat: add jsr package * feat: implement jsr package with API and parsers for jsr * feat: modify API, add function to extract linked github repo from html * perf: improve jsr package API with @hk/jsr-client * feat: add jsr package version table for publishing extension * fix: dependency and type incompatibility in ui package * feat: add validateJsrPackageAsKunkunExtension function in jsr package * feat: improve jsr table * feat: add a ElementAlert component * feat: update ElementAlert UI * chore: update deno.lock * chore: enable submodule support in jsr-publish workflow * chore: bump version to 0.0.48 in jsr.json * feat: regenerate supabase types, add author_id * Move @kksh/jsr package to @kksh/api * update deno.lock * chore: change @tauri-plugin/plugin-upload version from git url to version * feat: add rounded corner for ElementAlert * chore: update deno.lock * chore: bump version to 0.0.51 in jsr.json and update import paths for ExtPackageJson * feat: add publishExtJSR API to SupabaseAPI * refactor: replace "@hk/jsr-client" from jsr with @huakunshen/jsr-client from npm * chore: update deno.lock * feat: update validateJsrPackageAsKunkunExtension return type * refactor: improve error message * feat: add metadata to ext_publish, update database.types.ts * feat: add models module for Supabase with ExtPublishMetadata and source type enumeration * feat: support installing JSR package as extension Since JSR overwrites package.json with its own code to be compatible with npm, causing manifest parsing to be impossible. I add metadata field to ext_publish. When extension comes from jsr, kunkun app will fetch the original package.json from jsr and overwrite the one modified by jsr. * fix: add missing dep @tauri-apps/plugin-upload to @kksh/extension * chore: update version to 0.0.52 in version.ts
This commit is contained in:
parent
e096e10bc0
commit
e21bef154e
2
.github/workflows/jsr-publish.yml
vendored
2
.github/workflows/jsr-publish.yml
vendored
@ -12,6 +12,8 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: "true"
|
||||||
- uses: denoland/setup-deno@v2
|
- uses: denoland/setup-deno@v2
|
||||||
with:
|
with:
|
||||||
deno-version: v2.x
|
deno-version: v2.x
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bits-ui": "1.0.0-next.72",
|
"bits-ui": "1.0.0-next.72",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.468.0",
|
"lucide-svelte": "^0.469.0",
|
||||||
"svelte-radix": "^2.0.1",
|
"svelte-radix": "^2.0.1",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwind-variants": "^0.3.0",
|
"tailwind-variants": "^0.3.0",
|
||||||
|
@ -12,7 +12,11 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
|||||||
getExtensionsFromStore: () => ExtPackageJsonExtra[]
|
getExtensionsFromStore: () => ExtPackageJsonExtra[]
|
||||||
installTarball: (tarballPath: string, extsDir: string) => Promise<ExtPackageJsonExtra>
|
installTarball: (tarballPath: string, extsDir: string) => Promise<ExtPackageJsonExtra>
|
||||||
installDevExtensionDir: (dirPath: string) => Promise<ExtPackageJsonExtra>
|
installDevExtensionDir: (dirPath: string) => Promise<ExtPackageJsonExtra>
|
||||||
installFromTarballUrl: (tarballUrl: string, installDir: string) => Promise<ExtPackageJsonExtra>
|
installFromTarballUrl: (
|
||||||
|
tarballUrl: string,
|
||||||
|
installDir: string,
|
||||||
|
extras?: { overwritePackageJson?: string }
|
||||||
|
) => Promise<ExtPackageJsonExtra>
|
||||||
installFromNpmPackageName: (name: string, installDir: string) => Promise<ExtPackageJsonExtra>
|
installFromNpmPackageName: (name: string, installDir: string) => Promise<ExtPackageJsonExtra>
|
||||||
findStoreExtensionByIdentifier: (identifier: string) => ExtPackageJsonExtra | undefined
|
findStoreExtensionByIdentifier: (identifier: string) => ExtPackageJsonExtra | undefined
|
||||||
registerNewExtensionByPath: (extPath: string) => Promise<ExtPackageJsonExtra>
|
registerNewExtensionByPath: (extPath: string) => Promise<ExtPackageJsonExtra>
|
||||||
@ -86,8 +90,12 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installFromTarballUrl(tarballUrl: string, extsDir: string) {
|
async function installFromTarballUrl(
|
||||||
return extAPI.installTarballUrl(tarballUrl, extsDir).then((extInstallPath) => {
|
tarballUrl: string,
|
||||||
|
extsDir: string,
|
||||||
|
extras?: { overwritePackageJson?: string }
|
||||||
|
) {
|
||||||
|
return extAPI.installTarballUrl(tarballUrl, extsDir, extras).then((extInstallPath) => {
|
||||||
return registerNewExtensionByPath(extInstallPath)
|
return registerNewExtensionByPath(extInstallPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
import { extensions, installedStoreExts } from "@/stores/extensions.js"
|
import { extensions, installedStoreExts } from "@/stores/extensions.js"
|
||||||
import { supabaseAPI } from "@/supabase"
|
import { supabaseAPI } from "@/supabase"
|
||||||
import { goBack } from "@/utils/route.js"
|
import { goBack } from "@/utils/route.js"
|
||||||
|
import type { Tables } from "@kksh/api/supabase/types"
|
||||||
|
import { ExtPublishMetadata } from "@kksh/supabase/models"
|
||||||
import { Button } from "@kksh/svelte5"
|
import { Button } from "@kksh/svelte5"
|
||||||
import { cn } from "@kksh/svelte5/utils"
|
import { cn } from "@kksh/svelte5/utils"
|
||||||
import { Constants } from "@kksh/ui"
|
import { Constants } from "@kksh/ui"
|
||||||
@ -13,10 +15,11 @@
|
|||||||
import { ArrowLeftIcon } from "lucide-svelte"
|
import { ArrowLeftIcon } from "lucide-svelte"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
import { get, derived as storeDerived } from "svelte/store"
|
import { derived as storeDerived } from "svelte/store"
|
||||||
|
import { getInstallExtras } from "./helper.js"
|
||||||
|
|
||||||
const { data } = $props()
|
const { data } = $props()
|
||||||
const ext = $derived(data.ext)
|
const ext: Tables<"ext_publish"> & { metadata: ExtPublishMetadata } = $derived(data.ext)
|
||||||
const manifest = $derived(data.manifest)
|
const manifest = $derived(data.manifest)
|
||||||
const installedExt = storeDerived(installedStoreExts, ($e) => {
|
const installedExt = storeDerived(installedStoreExts, ($e) => {
|
||||||
return $e.find((e) => e.kunkun.identifier === ext.identifier)
|
return $e.find((e) => e.kunkun.identifier === ext.identifier)
|
||||||
@ -69,24 +72,27 @@
|
|||||||
|
|
||||||
async function onInstallSelected() {
|
async function onInstallSelected() {
|
||||||
loading.install = true
|
loading.install = true
|
||||||
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
|
const tarballUrl = ext.tarball_path.startsWith("http")
|
||||||
|
? ext.tarball_path
|
||||||
|
: supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
|
||||||
|
const installExtras = await getInstallExtras(ext)
|
||||||
const installDir = await getExtensionsFolder()
|
const installDir = await getExtensionsFolder()
|
||||||
return extensions
|
return extensions
|
||||||
.installFromTarballUrl(tarballUrl, installDir)
|
.installFromTarballUrl(tarballUrl, installDir, installExtras)
|
||||||
.then(() => toast.success(`Plugin ${ext.name} Installed`))
|
.then(() => toast.success(`Plugin ${ext.name} Installed`))
|
||||||
.then(async (loadedExt) =>
|
.then((loadedExt) => {
|
||||||
supabaseAPI.incrementDownloads({
|
supabaseAPI.incrementDownloads({
|
||||||
identifier: ext.identifier,
|
identifier: ext.identifier,
|
||||||
version: ext.version
|
version: ext.version
|
||||||
})
|
})
|
||||||
)
|
showBtn.install = false
|
||||||
|
showBtn.uninstall = true
|
||||||
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
toast.error("Fail to install tarball", { description: err })
|
toast.error("Fail to install tarball", { description: err })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.install = false
|
loading.install = false
|
||||||
showBtn.install = false
|
|
||||||
showBtn.uninstall = true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +152,7 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
class={cn("fixed left-3 top-3", Constants.CLASSNAMES.BACK_BUTTON)}
|
class={cn("fixed left-3 top-3 z-50", Constants.CLASSNAMES.BACK_BUTTON)}
|
||||||
data-flip-id={Constants.CLASSNAMES.BACK_BUTTON}
|
data-flip-id={Constants.CLASSNAMES.BACK_BUTTON}
|
||||||
onclick={() => goto("/app/extension/store")}
|
onclick={() => goto("/app/extension/store")}
|
||||||
>
|
>
|
||||||
|
@ -2,30 +2,29 @@ import { extensions } from "@/stores"
|
|||||||
import { supabaseAPI } from "@/supabase"
|
import { supabaseAPI } from "@/supabase"
|
||||||
import { KunkunExtManifest, type ExtPackageJsonExtra } from "@kksh/api/models"
|
import { KunkunExtManifest, type ExtPackageJsonExtra } from "@kksh/api/models"
|
||||||
import type { Tables } from "@kksh/api/supabase/types"
|
import type { Tables } from "@kksh/api/supabase/types"
|
||||||
|
import { ExtPublishMetadata } from "@kksh/supabase/models"
|
||||||
import { error } from "@sveltejs/kit"
|
import { error } from "@sveltejs/kit"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
import { get } from "svelte/store"
|
|
||||||
import * as v from "valibot"
|
import * as v from "valibot"
|
||||||
import type { PageLoad } from "./$types"
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
export const load: PageLoad = async ({
|
export const load: PageLoad = async ({
|
||||||
params
|
params
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
ext: Tables<"ext_publish">
|
ext: Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
|
||||||
manifest: KunkunExtManifest
|
manifest: KunkunExtManifest
|
||||||
params: {
|
params: {
|
||||||
identifier: string
|
identifier: string
|
||||||
}
|
}
|
||||||
}> => {
|
}> => {
|
||||||
console.log("store[identifier] params", params)
|
|
||||||
|
|
||||||
const { error: dbError, data: ext } = await supabaseAPI.getLatestExtPublish(params.identifier)
|
const { error: dbError, data: ext } = await supabaseAPI.getLatestExtPublish(params.identifier)
|
||||||
|
const metadataParse = v.safeParse(ExtPublishMetadata, ext?.metadata ?? {})
|
||||||
if (dbError) {
|
if (dbError) {
|
||||||
return error(400, {
|
return error(400, {
|
||||||
message: dbError.message
|
message: dbError.message
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const metadata = metadataParse.success ? metadataParse.output : {}
|
||||||
const parseManifest = v.safeParse(KunkunExtManifest, ext.manifest)
|
const parseManifest = v.safeParse(KunkunExtManifest, ext.manifest)
|
||||||
if (!parseManifest.success) {
|
if (!parseManifest.success) {
|
||||||
const errMsg = "Invalid extension manifest, you may need to upgrade your app."
|
const errMsg = "Invalid extension manifest, you may need to upgrade your app."
|
||||||
@ -34,7 +33,7 @@ export const load: PageLoad = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ext,
|
ext: { ...ext, metadata },
|
||||||
params,
|
params,
|
||||||
manifest: parseManifest.output
|
manifest: parseManifest.output
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import type { Tables } from "@kksh/api/supabase/types"
|
||||||
|
import type { ExtPublishMetadata } from "@kksh/supabase/models"
|
||||||
|
|
||||||
|
export async function getInstallExtras(
|
||||||
|
ext: Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
|
||||||
|
): Promise<{ overwritePackageJson?: string }> {
|
||||||
|
const extras: { overwritePackageJson?: string } = {}
|
||||||
|
if (ext.metadata.sourceType) {
|
||||||
|
if (ext.metadata.sourceType === "jsr") {
|
||||||
|
if (ext.metadata.source) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${ext.metadata.source}/package.json`)
|
||||||
|
const pkgJsonContent = await res.text()
|
||||||
|
extras.overwritePackageJson = pkgJsonContent
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fail to fetch jsr package.json", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extras
|
||||||
|
}
|
13
package.json
13
package.json
@ -13,23 +13,23 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@kksh/svelte5": "0.1.10",
|
"@kksh/svelte5": "0.1.11",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.2",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
"svelte": "^5.16.2",
|
"svelte": "^5.16.6",
|
||||||
"svelte-check": "^4.1.1",
|
"svelte-check": "^4.1.1",
|
||||||
"turbo": "^2.3.3",
|
"turbo": "^2.3.3",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.2",
|
"packageManager": "pnpm@9.15.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@changesets/cli": "^2.27.11",
|
"@changesets/cli": "^2.27.11",
|
||||||
"@iconify/svelte": "^4.2.0",
|
"@iconify/svelte": "^4.2.0",
|
||||||
"@supabase/supabase-js": "^2.47.10",
|
"@supabase/supabase-js": "^2.47.11",
|
||||||
"@tauri-apps/api": "^2.2.0",
|
"@tauri-apps/api": "^2.2.0",
|
||||||
"@tauri-apps/cli": "^2.2.2",
|
"@tauri-apps/cli": "^2.2.2",
|
||||||
"@tauri-apps/plugin-deep-link": "^2.2.0",
|
"@tauri-apps/plugin-deep-link": "^2.2.0",
|
||||||
@ -43,14 +43,13 @@
|
|||||||
"@tauri-apps/plugin-process": "2.2.0",
|
"@tauri-apps/plugin-process": "2.2.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
"@tauri-apps/plugin-store": "^2.2.0",
|
"@tauri-apps/plugin-store": "^2.2.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.3.0",
|
"@tauri-apps/plugin-updater": "^2.3.1",
|
||||||
"@tauri-apps/plugin-upload": "https://gitpkg.vercel.app/HuakunShen/tauri-plugins-workspace/plugins/upload?69b198b0ccba269fe7622a95ec6a33ae392bff03",
|
|
||||||
"supabase": "^2.2.1",
|
"supabase": "^2.2.1",
|
||||||
"tauri-plugin-network-api": "workspace:*",
|
"tauri-plugin-network-api": "workspace:*",
|
||||||
"tauri-plugin-keyring-api": "workspace:*",
|
"tauri-plugin-keyring-api": "workspace:*",
|
||||||
"tauri-plugin-shellx-api": "^2.0.14",
|
"tauri-plugin-shellx-api": "^2.0.14",
|
||||||
"tauri-plugin-system-info-api": "workspace:*",
|
"tauri-plugin-system-info-api": "workspace:*",
|
||||||
"valibot": "^1.0.0-beta.10",
|
"valibot": "^1.0.0-beta.11",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
@ -18,4 +18,3 @@ if (!schemaFile.exists()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await $`bun patch-version.ts`
|
await $`bun patch-version.ts`
|
||||||
await $`bun run check-types`
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
||||||
"name": "@kunkun/api",
|
"name": "@kunkun/api",
|
||||||
"version": "0.0.47",
|
"version": "0.0.52",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
@ -15,7 +15,8 @@
|
|||||||
"./supabase": "./src/supabase/index.ts",
|
"./supabase": "./src/supabase/index.ts",
|
||||||
"./supabase/types": "./src/supabase/database.types.ts",
|
"./supabase/types": "./src/supabase/database.types.ts",
|
||||||
"./dev": "./src/dev/index.ts",
|
"./dev": "./src/dev/index.ts",
|
||||||
"./events": "./src/events.ts"
|
"./events": "./src/events.ts",
|
||||||
|
"./extensions/jsr": "./src/extensions/jsr/index.ts"
|
||||||
},
|
},
|
||||||
"imports": {}
|
"imports": {}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kksh/api",
|
"name": "@kksh/api",
|
||||||
"version": "0.0.48",
|
"version": "0.0.52",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
@ -16,7 +16,8 @@
|
|||||||
"./events": "./src/events.ts",
|
"./events": "./src/events.ts",
|
||||||
"./supabase": "./src/supabase/index.ts",
|
"./supabase": "./src/supabase/index.ts",
|
||||||
"./supabase/types": "./src/supabase/database.types.ts",
|
"./supabase/types": "./src/supabase/database.types.ts",
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json",
|
||||||
|
"./extensions/jsr": "./src/extensions/jsr/index.ts"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -41,6 +42,7 @@
|
|||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@huakunshen/jsr-client": "^0.1.5",
|
||||||
"@tauri-apps/api": "^2.2.0",
|
"@tauri-apps/api": "^2.2.0",
|
||||||
"@tauri-apps/cli": "^2.2.2",
|
"@tauri-apps/cli": "^2.2.2",
|
||||||
"@tauri-apps/plugin-deep-link": "^2.2.0",
|
"@tauri-apps/plugin-deep-link": "^2.2.0",
|
||||||
@ -55,7 +57,7 @@
|
|||||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
"@tauri-apps/plugin-store": "^2.2.0",
|
"@tauri-apps/plugin-store": "^2.2.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.3.0",
|
"@tauri-apps/plugin-updater": "^2.3.0",
|
||||||
"@tauri-apps/plugin-upload": "https://gitpkg.vercel.app/HuakunShen/tauri-plugins-workspace/plugins/upload?69b198b0ccba269fe7622a95ec6a33ae392bff03",
|
"@tauri-apps/plugin-upload": "^2.2.1",
|
||||||
"kkrpc": "^0.0.13",
|
"kkrpc": "^0.0.13",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
|
133
packages/api/src/extensions/jsr/__tests__/api.test.ts
Normal file
133
packages/api/src/extensions/jsr/__tests__/api.test.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import * as v from "valibot";
|
||||||
|
import { ExtPackageJson } from "../../../models/manifest";
|
||||||
|
import {
|
||||||
|
getAllVersionsOfJsrPackage,
|
||||||
|
getJsrNpmPackageVersionMetadata,
|
||||||
|
getJsrNpmPkgMetadata,
|
||||||
|
getJsrPackageGitHubRepo,
|
||||||
|
getJsrPackageHtml,
|
||||||
|
getJsrPackageMetadata,
|
||||||
|
getJsrPackageSrcFile,
|
||||||
|
getNpmPackageTarballUrl,
|
||||||
|
isSignedByGitHubAction,
|
||||||
|
jsrPackageExists,
|
||||||
|
splitRawJsrPkgName,
|
||||||
|
translateJsrToNpmPkgName,
|
||||||
|
} from "../index";
|
||||||
|
import { JsrPackageMetadata, NpmPkgMetadata } from "../models";
|
||||||
|
|
||||||
|
describe("Test the helper functions", () => {
|
||||||
|
test("Get Package Html", async () => {
|
||||||
|
const html = await getJsrPackageHtml("kunkun", "kkrpc");
|
||||||
|
expect(html).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Signed By GitHub Action", async () => {
|
||||||
|
const kkrpcSigned = await isSignedByGitHubAction(
|
||||||
|
"kunkun",
|
||||||
|
"kkrpc",
|
||||||
|
"0.0.14",
|
||||||
|
);
|
||||||
|
expect(kkrpcSigned).toBe(true);
|
||||||
|
const kkrpcSignedVersion = await isSignedByGitHubAction(
|
||||||
|
"kunkun",
|
||||||
|
"kkrpc",
|
||||||
|
"0.0.14",
|
||||||
|
);
|
||||||
|
expect(kkrpcSignedVersion).toBe(true);
|
||||||
|
expect(kkrpcSignedVersion).toBe(true);
|
||||||
|
const kunkunApiSigned = await isSignedByGitHubAction(
|
||||||
|
"kunkun",
|
||||||
|
"api",
|
||||||
|
"0.0.47",
|
||||||
|
);
|
||||||
|
expect(kunkunApiSigned).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Linked GitHub Repo", async () => {
|
||||||
|
const repo = await getJsrPackageGitHubRepo("kunkun", "kkrpc");
|
||||||
|
expect(repo).toBeDefined();
|
||||||
|
expect(repo?.owner).toBe("kunkunsh");
|
||||||
|
expect(repo?.name).toBe("kkrpc");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Package Metadata", async () => {
|
||||||
|
const metadata = await getJsrPackageMetadata("kunkun", "api");
|
||||||
|
const parsed = v.parse(JsrPackageMetadata, metadata);
|
||||||
|
expect(parsed).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Package's package.json", async () => {
|
||||||
|
const packageJson = await getJsrPackageSrcFile(
|
||||||
|
"kunkun",
|
||||||
|
"ext-image-processing",
|
||||||
|
"0.0.6",
|
||||||
|
"package.json",
|
||||||
|
);
|
||||||
|
expect(packageJson).toBeDefined();
|
||||||
|
const parsed = v.parse(ExtPackageJson, JSON.parse(packageJson!));
|
||||||
|
expect(parsed).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Package's README.md", async () => {
|
||||||
|
const readme = await getJsrPackageSrcFile(
|
||||||
|
"kunkun",
|
||||||
|
"api",
|
||||||
|
"0.0.47",
|
||||||
|
"README.md",
|
||||||
|
);
|
||||||
|
expect(readme).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Translate Jsr Package Name to Npm Package Name", () => {
|
||||||
|
const npmPkgName = translateJsrToNpmPkgName("kunkun", "api");
|
||||||
|
expect(npmPkgName).toBe("kunkun__api");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Split Jsr Package Name", async () => {
|
||||||
|
const { scope, name } = await splitRawJsrPkgName("@kunkun/api");
|
||||||
|
expect(scope).toBe("kunkun");
|
||||||
|
expect(name).toBe("api");
|
||||||
|
expect(splitRawJsrPkgName("kunkun/api")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Npm Package Metadata", async () => {
|
||||||
|
const metadata = await getJsrNpmPkgMetadata("kunkun", "api");
|
||||||
|
const parsed = v.parse(NpmPkgMetadata, metadata);
|
||||||
|
expect(parsed).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Npm Package Version Metadata", async () => {
|
||||||
|
const metadata = await getJsrNpmPackageVersionMetadata(
|
||||||
|
"kunkun",
|
||||||
|
"api",
|
||||||
|
"0.0.47",
|
||||||
|
);
|
||||||
|
expect(metadata).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get Npm Package Tarball Url", async () => {
|
||||||
|
const url = await getNpmPackageTarballUrl("kunkun", "api", "0.0.47");
|
||||||
|
expect(url).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get All Versions Of Jsr Package", async () => {
|
||||||
|
const versions = await getAllVersionsOfJsrPackage("kunkun", "api");
|
||||||
|
expect(versions).toBeDefined();
|
||||||
|
// verify: versions should match npm api
|
||||||
|
const npmPkgMetadata = await getJsrNpmPkgMetadata("kunkun", "api");
|
||||||
|
expect(versions).toEqual(Object.keys(npmPkgMetadata.versions));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Jsr Package Exists", async () => {
|
||||||
|
expect(await jsrPackageExists("kunkun", "api")).toBe(true);
|
||||||
|
expect(await jsrPackageExists("hk", "non-existent-package")).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(await jsrPackageExists("hk", "jsr-client", "0.1.2")).toBe(true);
|
||||||
|
expect(await jsrPackageExists("hk", "jsr-client", "0.1.500")).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
71
packages/api/src/extensions/jsr/__tests__/ext-pkg.test.ts
Normal file
71
packages/api/src/extensions/jsr/__tests__/ext-pkg.test.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { validateJsrPackageAsKunkunExtension } from "../index";
|
||||||
|
|
||||||
|
describe("Validate Jsr package as Kunkun extension", () => {
|
||||||
|
test("Package not signed by GitHub Actions", async () => {
|
||||||
|
expect(
|
||||||
|
(await validateJsrPackageAsKunkunExtension({
|
||||||
|
jsrPackage: {
|
||||||
|
scope: "kunkun",
|
||||||
|
name: "api",
|
||||||
|
version: "0.0.47",
|
||||||
|
},
|
||||||
|
githubUsername: "kunkunsh",
|
||||||
|
})).error,
|
||||||
|
).toBe("JSR package is not signed by GitHub Actions");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Non-existent package", async () => {
|
||||||
|
expect(
|
||||||
|
(await validateJsrPackageAsKunkunExtension({
|
||||||
|
jsrPackage: {
|
||||||
|
scope: "kunkun",
|
||||||
|
name: "non-existent-package",
|
||||||
|
version: "0.0.47",
|
||||||
|
},
|
||||||
|
githubUsername: "kunkunsh",
|
||||||
|
})).error,
|
||||||
|
).toBe("JSR package does not exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Package not linked to a GitHub repository", async () => {
|
||||||
|
expect(
|
||||||
|
(await validateJsrPackageAsKunkunExtension({
|
||||||
|
jsrPackage: {
|
||||||
|
scope: "hk",
|
||||||
|
name: "tauri-plugin-network-api",
|
||||||
|
version: "2.0.3-beta.1",
|
||||||
|
},
|
||||||
|
githubUsername: "kunkunsh",
|
||||||
|
})).error,
|
||||||
|
).toBe("JSR package is not linked to a GitHub repository");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GitHub repository owner does not match JSR package owner", async () => {
|
||||||
|
expect(
|
||||||
|
(await validateJsrPackageAsKunkunExtension({
|
||||||
|
jsrPackage: {
|
||||||
|
scope: "kunkun",
|
||||||
|
name: "ext-image-processing",
|
||||||
|
version: "0.0.6",
|
||||||
|
},
|
||||||
|
githubUsername: "kunkunsh", // should be HuakunShen
|
||||||
|
})).error,
|
||||||
|
).toBe(
|
||||||
|
"GitHub repository owner does not match JSR package owner: HuakunShen !== kunkunsh",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("A valid extension package", async () => {
|
||||||
|
expect(
|
||||||
|
(await validateJsrPackageAsKunkunExtension({
|
||||||
|
jsrPackage: {
|
||||||
|
scope: "kunkun",
|
||||||
|
name: "ext-image-processing",
|
||||||
|
version: "0.0.6",
|
||||||
|
},
|
||||||
|
githubUsername: "HuakunShen",
|
||||||
|
})).data,
|
||||||
|
).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
386
packages/api/src/extensions/jsr/index.ts
Normal file
386
packages/api/src/extensions/jsr/index.ts
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
import {
|
||||||
|
client,
|
||||||
|
getPackage,
|
||||||
|
getPackageVersion,
|
||||||
|
type GitHubRepository,
|
||||||
|
} from "@huakunshen/jsr-client/hey-api-client";
|
||||||
|
import * as v from "valibot";
|
||||||
|
import { ExtPackageJson } from "../../models/manifest";
|
||||||
|
import type { JsrPackageMetadata, NpmPkgMetadata } from "./models";
|
||||||
|
|
||||||
|
client.setConfig({
|
||||||
|
baseUrl: "https://api.jsr.io",
|
||||||
|
});
|
||||||
|
|
||||||
|
export function splitRawJsrPkgName(
|
||||||
|
packageName: string,
|
||||||
|
): Promise<{ scope: string; name: string }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// write a regex to match the scope and name
|
||||||
|
const regex = /^@([^@]+)\/([^@]+)$/;
|
||||||
|
const match = packageName.match(regex);
|
||||||
|
if (!match) {
|
||||||
|
return reject(new Error("Invalid Jsr package name"));
|
||||||
|
}
|
||||||
|
const [, rawScope, name] = match;
|
||||||
|
const scope = rawScope.startsWith("@") ? rawScope.slice(1) : rawScope;
|
||||||
|
return resolve({ scope, name });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a Jsr package name to an npm package name
|
||||||
|
* All packages are under `@jsr` scope, thus the npm package name is `@jsr/scope__name`
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const translateJsrToNpmPkgName = (scope: string, name: string) =>
|
||||||
|
`${scope}__${name}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
/**
|
||||||
|
* Get the html of a Jsr package main page
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @param version
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getJsrPackageHtml(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
version?: string,
|
||||||
|
) {
|
||||||
|
const url = `https://jsr.io/@${scope}/${name}${
|
||||||
|
version ? `@${version}` : ""
|
||||||
|
}`;
|
||||||
|
return fetch(url, {
|
||||||
|
headers: {
|
||||||
|
"sec-fetch-dest": "document",
|
||||||
|
},
|
||||||
|
}).then((res) => res.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a Jsr package is signed by GitHub Actions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function isSignedByGitHubAction(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const pkgVersion = await getPackageVersion({
|
||||||
|
path: {
|
||||||
|
scope,
|
||||||
|
package: name,
|
||||||
|
version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return !!pkgVersion.data?.rekorLogId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getJsrPackageGitHubRepo(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<GitHubRepository | null> {
|
||||||
|
const pkg = await getPackage({
|
||||||
|
path: {
|
||||||
|
scope,
|
||||||
|
package: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return pkg.data?.githubRepository ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the metadata of a Jsr package
|
||||||
|
* Data includes
|
||||||
|
* - latest version
|
||||||
|
* - versions (whether a version is yanked)
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getJsrPackageMetadata(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<JsrPackageMetadata> {
|
||||||
|
const url = `https://jsr.io/@${scope}/${name}/meta.json`;
|
||||||
|
return fetch(url).then((res) => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a jsr package and path to the file, return the file content
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @param version
|
||||||
|
* @param file
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getJsrPackageSrcFile(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
file: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const url = `https://jsr.io/@${scope}/${name}/${version}/${file}`;
|
||||||
|
return fetch(url)
|
||||||
|
.then((res) => res.text())
|
||||||
|
.catch(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jsr provides a npm compatible registry, so we can get the metadata of the npm package
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getJsrNpmPkgMetadata(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<NpmPkgMetadata> {
|
||||||
|
// Sample: https://npm.jsr.io/@jsr/kunkun__api
|
||||||
|
const url = `https://npm.jsr.io/@jsr/${
|
||||||
|
translateJsrToNpmPkgName(scope, name)
|
||||||
|
}`;
|
||||||
|
return fetch(url).then((res) => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the metadata of a specific version of a Jsr package
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @param version
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getJsrNpmPackageVersionMetadata(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
) {
|
||||||
|
return getJsrNpmPkgMetadata(scope, name).then((metadata) => {
|
||||||
|
return metadata.versions[version];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tarball url of a Jsr package
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @param version
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function getNpmPackageTarballUrl(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const metadata = await getJsrNpmPackageVersionMetadata(
|
||||||
|
scope,
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
);
|
||||||
|
const tarballUrl: string | undefined = metadata?.dist
|
||||||
|
.tarball;
|
||||||
|
return tarballUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all versions of a Jsr package
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function getAllVersionsOfJsrPackage(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const metadata = await getJsrNpmPkgMetadata(scope, name);
|
||||||
|
return Object.keys(metadata.versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a Jsr package exists
|
||||||
|
* @param scope
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function jsrPackageExists(
|
||||||
|
scope: string,
|
||||||
|
name: string,
|
||||||
|
version?: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (version) {
|
||||||
|
return getPackageVersion({
|
||||||
|
path: {
|
||||||
|
scope,
|
||||||
|
package: name,
|
||||||
|
version,
|
||||||
|
},
|
||||||
|
}).then((res) => res.response.ok && res.response.status === 200);
|
||||||
|
}
|
||||||
|
return getPackage({
|
||||||
|
path: {
|
||||||
|
scope,
|
||||||
|
package: name,
|
||||||
|
},
|
||||||
|
}).then((res) => res.response.ok && res.response.status === 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tarball size of a Jsr package
|
||||||
|
* @param url tarball url, can technically be any url
|
||||||
|
* @returns tarball size in bytes
|
||||||
|
*/
|
||||||
|
export function getTarballSize(url: string): Promise<number> {
|
||||||
|
return fetch(url, { method: "HEAD" }).then((res) => {
|
||||||
|
if (!(res.ok && res.status === 200)) {
|
||||||
|
throw new Error("Failed to fetch tarball size");
|
||||||
|
}
|
||||||
|
return Number(res.headers.get("Content-Length"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a Jsr package as a Kunkun extension
|
||||||
|
* - check if jsr pkg is linked to a github repo
|
||||||
|
* - check if jsr pkg is signed with github action
|
||||||
|
* - check if user's github username is the same as repo's owner name
|
||||||
|
* - check if jsr.json or deno.json has the same version as package.json
|
||||||
|
* - validate package.json format against latest schema
|
||||||
|
* @param payload
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function validateJsrPackageAsKunkunExtension(payload: {
|
||||||
|
jsrPackage: {
|
||||||
|
scope: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
githubUsername: string;
|
||||||
|
tarballSizeLimit?: number;
|
||||||
|
}): Promise<{
|
||||||
|
error?: string;
|
||||||
|
data?: {
|
||||||
|
pkgJson: ExtPackageJson;
|
||||||
|
tarballUrl: string;
|
||||||
|
shasum: string;
|
||||||
|
apiVersion: string;
|
||||||
|
tarballSize: number;
|
||||||
|
};
|
||||||
|
}> {
|
||||||
|
// check if jsr package exists
|
||||||
|
const jsrExists = await jsrPackageExists(
|
||||||
|
payload.jsrPackage.scope,
|
||||||
|
payload.jsrPackage.name,
|
||||||
|
payload.jsrPackage.version,
|
||||||
|
);
|
||||||
|
if (!jsrExists) {
|
||||||
|
return { error: "JSR package does not exist" };
|
||||||
|
}
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* check if jsr pkg is linked to a github repo */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const githubRepo = await getJsrPackageGitHubRepo(
|
||||||
|
payload.jsrPackage.scope,
|
||||||
|
payload.jsrPackage.name,
|
||||||
|
);
|
||||||
|
if (githubRepo === null) {
|
||||||
|
return { error: "JSR package is not linked to a GitHub repository" };
|
||||||
|
}
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* check if jsr pkg is signed with github action */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const signed = await isSignedByGitHubAction(
|
||||||
|
payload.jsrPackage.scope,
|
||||||
|
payload.jsrPackage.name,
|
||||||
|
payload.jsrPackage.version,
|
||||||
|
);
|
||||||
|
if (!signed) {
|
||||||
|
return { error: "JSR package is not signed by GitHub Actions" };
|
||||||
|
}
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* check if user's github username is the same as repo's owner name */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
if (
|
||||||
|
githubRepo.owner?.toLowerCase() !== payload.githubUsername.toLowerCase()
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
`GitHub repository owner does not match JSR package owner: ${githubRepo.owner} !== ${payload.githubUsername}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* check if jsr.json or deno.json has the same version as package.json */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const packageJsonContent = await getJsrPackageSrcFile(
|
||||||
|
payload.jsrPackage.scope,
|
||||||
|
payload.jsrPackage.name,
|
||||||
|
payload.jsrPackage.version,
|
||||||
|
"package.json",
|
||||||
|
);
|
||||||
|
if (!packageJsonContent) {
|
||||||
|
return { error: "Could not find package.json in JSR package" };
|
||||||
|
}
|
||||||
|
let packageJson: any;
|
||||||
|
try {
|
||||||
|
packageJson = JSON.parse(packageJsonContent);
|
||||||
|
} catch (error) {
|
||||||
|
return { error: "Failed to parse package.json" };
|
||||||
|
}
|
||||||
|
if (packageJson.version !== payload.jsrPackage.version) {
|
||||||
|
// no need to fetch jsr.json or deno.json content, as we already know the version is valid with JSR API
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
"Package version in package.json does not match JSR package version",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* validate package.json format against latest schema */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const parseResult = v.safeParse(ExtPackageJson, packageJson);
|
||||||
|
if (!parseResult.success) {
|
||||||
|
return { error: "package.json format not valid" };
|
||||||
|
}
|
||||||
|
const npmPkgVersionMetadata = await getJsrNpmPackageVersionMetadata(
|
||||||
|
payload.jsrPackage.scope,
|
||||||
|
payload.jsrPackage.name,
|
||||||
|
payload.jsrPackage.version,
|
||||||
|
);
|
||||||
|
const tarballUrl = npmPkgVersionMetadata.dist.tarball;
|
||||||
|
const shasum = npmPkgVersionMetadata.dist.shasum;
|
||||||
|
if (!tarballUrl) {
|
||||||
|
return { error: "Could not get tarball URL for JSR package" };
|
||||||
|
}
|
||||||
|
const tarballSize = await getTarballSize(tarballUrl);
|
||||||
|
const sizeLimit = payload.tarballSizeLimit ?? 50 * 1024 * 1024; // default to 50MB
|
||||||
|
if (tarballSize > sizeLimit) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
`Package tarball size (${tarballSize} bytes) exceeds limit of ${sizeLimit} bytes`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* get @kksh/api dependency version */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const apiVersion = parseResult.output.dependencies?.["@kksh/api"];
|
||||||
|
if (!apiVersion) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
`Extension ${packageJson.kunkun.identifier} doesn't not have @kksh/api as a dependency`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
pkgJson: parseResult.output,
|
||||||
|
tarballUrl,
|
||||||
|
shasum,
|
||||||
|
apiVersion,
|
||||||
|
tarballSize,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
42
packages/api/src/extensions/jsr/models.ts
Normal file
42
packages/api/src/extensions/jsr/models.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as v from "valibot"
|
||||||
|
|
||||||
|
export const JsrPackageMetadata = v.object({
|
||||||
|
scope: v.string(),
|
||||||
|
name: v.string(),
|
||||||
|
latest: v.string(),
|
||||||
|
versions: v.record(
|
||||||
|
v.string(),
|
||||||
|
v.object({
|
||||||
|
yanked: v.optional(v.boolean())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
export type JsrPackageMetadata = v.InferOutput<typeof JsrPackageMetadata>
|
||||||
|
|
||||||
|
export const NpmPkgMetadata = v.object({
|
||||||
|
name: v.string(),
|
||||||
|
description: v.optional(v.string()),
|
||||||
|
"dist-tags": v.record(v.string(), v.string()), // latest, next, beta, rc
|
||||||
|
versions: v.record(
|
||||||
|
v.string(),
|
||||||
|
v.object({
|
||||||
|
name: v.string(),
|
||||||
|
version: v.string(),
|
||||||
|
description: v.optional(v.string()),
|
||||||
|
dist: v.object({
|
||||||
|
tarball: v.string(),
|
||||||
|
shasum: v.string(),
|
||||||
|
integrity: v.string()
|
||||||
|
}),
|
||||||
|
dependencies: v.record(v.string(), v.string())
|
||||||
|
})
|
||||||
|
),
|
||||||
|
time: v.objectWithRest(
|
||||||
|
{
|
||||||
|
created: v.string(),
|
||||||
|
modified: v.string()
|
||||||
|
},
|
||||||
|
v.string()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
export type NpmPkgMetadata = v.InferOutput<typeof NpmPkgMetadata>
|
@ -1,36 +1,36 @@
|
|||||||
import { FsPermissionSchema } from "tauri-api-adapter/permissions"
|
import { FsPermissionSchema } from "tauri-api-adapter/permissions";
|
||||||
import * as v from "valibot"
|
import * as v from "valibot";
|
||||||
import {
|
import {
|
||||||
AllKunkunPermission,
|
AllKunkunPermission,
|
||||||
FsPermissionScopedSchema,
|
FsPermissionScopedSchema,
|
||||||
KunkunFsPermissionSchema,
|
KunkunFsPermissionSchema,
|
||||||
KunkunManifestPermission,
|
KunkunManifestPermission,
|
||||||
OpenPermissionScopedSchema,
|
OpenPermissionScopedSchema,
|
||||||
ShellPermissionScopedSchema
|
ShellPermissionScopedSchema,
|
||||||
} from "../permissions"
|
} from "../permissions";
|
||||||
import { CmdType } from "./extension"
|
import { CmdType } from "./extension";
|
||||||
import { Icon } from "./icon"
|
import { Icon } from "./icon";
|
||||||
|
|
||||||
export enum OSPlatformEnum {
|
export enum OSPlatformEnum {
|
||||||
linux = "linux",
|
linux = "linux",
|
||||||
macos = "macos",
|
macos = "macos",
|
||||||
windows = "windows"
|
windows = "windows",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OSPlatform = v.enum_(OSPlatformEnum)
|
export const OSPlatform = v.enum_(OSPlatformEnum);
|
||||||
export type OSPlatform = v.InferOutput<typeof OSPlatform>
|
export type OSPlatform = v.InferOutput<typeof OSPlatform>;
|
||||||
const allPlatforms = Object.values(OSPlatformEnum)
|
const allPlatforms = Object.values(OSPlatformEnum);
|
||||||
export const TriggerCmd = v.object({
|
export const TriggerCmd = v.object({
|
||||||
type: v.union([v.literal("text"), v.literal("regex")]),
|
type: v.union([v.literal("text"), v.literal("regex")]),
|
||||||
value: v.string()
|
value: v.string(),
|
||||||
})
|
});
|
||||||
export type TriggerCmd = v.InferOutput<typeof TriggerCmd>
|
export type TriggerCmd = v.InferOutput<typeof TriggerCmd>;
|
||||||
export enum TitleBarStyleEnum {
|
export enum TitleBarStyleEnum {
|
||||||
"visible" = "visible",
|
"visible" = "visible",
|
||||||
"transparent" = "transparent",
|
"transparent" = "transparent",
|
||||||
"overlay" = "overlay"
|
"overlay" = "overlay",
|
||||||
}
|
}
|
||||||
export const TitleBarStyle = v.enum_(TitleBarStyleEnum)
|
export const TitleBarStyle = v.enum_(TitleBarStyleEnum);
|
||||||
// JS new WebViewWindow only accepts lowercase, while manifest loaded from Rust is capitalized. I run toLowerCase() on the value before passing it to the WebViewWindow.
|
// JS new WebViewWindow only accepts lowercase, while manifest loaded from Rust is capitalized. I run toLowerCase() on the value before passing it to the WebViewWindow.
|
||||||
// This lowercase title bar style schema is used to validate and set the type so TypeScript won't complaint
|
// This lowercase title bar style schema is used to validate and set the type so TypeScript won't complaint
|
||||||
// export const TitleBarStyleAllLower = z.enum(["visible", "transparent", "overlay"]);
|
// export const TitleBarStyleAllLower = z.enum(["visible", "transparent", "overlay"]);
|
||||||
@ -66,85 +66,101 @@ export const WindowConfig = v.object({
|
|||||||
minimizable: v.optional(v.nullable(v.boolean())),
|
minimizable: v.optional(v.nullable(v.boolean())),
|
||||||
closable: v.optional(v.nullable(v.boolean())),
|
closable: v.optional(v.nullable(v.boolean())),
|
||||||
parent: v.optional(v.nullable(v.string())),
|
parent: v.optional(v.nullable(v.string())),
|
||||||
visibleOnAllWorkspaces: v.optional(v.nullable(v.boolean()))
|
visibleOnAllWorkspaces: v.optional(v.nullable(v.boolean())),
|
||||||
})
|
});
|
||||||
export type WindowConfig = v.InferOutput<typeof WindowConfig>
|
export type WindowConfig = v.InferOutput<typeof WindowConfig>;
|
||||||
export const BaseCmd = v.object({
|
export const BaseCmd = v.object({
|
||||||
main: v.string("HTML file to load, e.g. dist/index.html"),
|
main: v.string("HTML file to load, e.g. dist/index.html"),
|
||||||
description: v.optional(v.nullable(v.string("Description of the Command"), ""), ""),
|
description: v.optional(
|
||||||
|
v.nullable(v.string("Description of the Command"), ""),
|
||||||
|
"",
|
||||||
|
),
|
||||||
name: v.string("Name of the command"),
|
name: v.string("Name of the command"),
|
||||||
cmds: v.array(TriggerCmd, "Commands to trigger the UI"),
|
cmds: v.array(TriggerCmd, "Commands to trigger the UI"),
|
||||||
icon: v.optional(Icon),
|
icon: v.optional(Icon),
|
||||||
platforms: v.optional(
|
platforms: v.optional(
|
||||||
v.nullable(
|
v.nullable(
|
||||||
v.array(OSPlatform, "Platforms available on. Leave empty for all platforms."),
|
v.array(
|
||||||
allPlatforms
|
OSPlatform,
|
||||||
|
"Platforms available on. Leave empty for all platforms.",
|
||||||
|
),
|
||||||
|
allPlatforms,
|
||||||
),
|
),
|
||||||
allPlatforms
|
allPlatforms,
|
||||||
)
|
),
|
||||||
})
|
});
|
||||||
export const CustomUiCmd = v.object({
|
export const CustomUiCmd = v.object({
|
||||||
...BaseCmd.entries,
|
...BaseCmd.entries,
|
||||||
type: v.optional(CmdType, CmdType.enum.UiIframe),
|
type: v.optional(CmdType, CmdType.enum.UiIframe),
|
||||||
dist: v.string("Dist folder to load, e.g. dist, build, out"),
|
dist: v.string("Dist folder to load, e.g. dist, build, out"),
|
||||||
devMain: v.string(
|
devMain: v.string(
|
||||||
"URL to load in development to support live reload, e.g. http://localhost:5173/"
|
"URL to load in development to support live reload, e.g. http://localhost:5173/",
|
||||||
),
|
),
|
||||||
window: v.optional(v.nullable(WindowConfig))
|
window: v.optional(v.nullable(WindowConfig)),
|
||||||
})
|
});
|
||||||
export type CustomUiCmd = v.InferOutput<typeof CustomUiCmd>
|
export type CustomUiCmd = v.InferOutput<typeof CustomUiCmd>;
|
||||||
|
|
||||||
export const TemplateUiCmd = v.object({
|
export const TemplateUiCmd = v.object({
|
||||||
...BaseCmd.entries,
|
...BaseCmd.entries,
|
||||||
type: v.optional(CmdType, CmdType.enum.UiWorker),
|
type: v.optional(CmdType, CmdType.enum.UiWorker),
|
||||||
window: v.optional(v.nullable(WindowConfig))
|
window: v.optional(v.nullable(WindowConfig)),
|
||||||
})
|
});
|
||||||
export const HeadlessCmd = v.object({
|
export const HeadlessCmd = v.object({
|
||||||
...BaseCmd.entries,
|
...BaseCmd.entries,
|
||||||
type: v.optional(CmdType, CmdType.enum.HeadlessWorker)
|
type: v.optional(CmdType, CmdType.enum.HeadlessWorker),
|
||||||
})
|
});
|
||||||
export type HeadlessCmd = v.InferOutput<typeof HeadlessCmd>
|
export type HeadlessCmd = v.InferOutput<typeof HeadlessCmd>;
|
||||||
export type TemplateUiCmd = v.InferOutput<typeof TemplateUiCmd>
|
export type TemplateUiCmd = v.InferOutput<typeof TemplateUiCmd>;
|
||||||
export const PermissionUnion = v.union([
|
export const PermissionUnion = v.union([
|
||||||
KunkunManifestPermission,
|
KunkunManifestPermission,
|
||||||
FsPermissionScopedSchema,
|
FsPermissionScopedSchema,
|
||||||
OpenPermissionScopedSchema,
|
OpenPermissionScopedSchema,
|
||||||
ShellPermissionScopedSchema
|
ShellPermissionScopedSchema,
|
||||||
])
|
]);
|
||||||
export type PermissionUnion = v.InferOutput<typeof PermissionUnion>
|
export type PermissionUnion = v.InferOutput<typeof PermissionUnion>;
|
||||||
export const KunkunExtManifest = v.object({
|
export const KunkunExtManifest = v.object({
|
||||||
name: v.string("Name of the extension (Human Readable)"),
|
name: v.string("Name of the extension (Human Readable)"),
|
||||||
shortDescription: v.string("Description of the extension (Will be displayed in store)"),
|
shortDescription: v.string(
|
||||||
longDescription: v.string("Long description of the extension (Will be displayed in store)"),
|
"Description of the extension (Will be displayed in store)",
|
||||||
|
),
|
||||||
|
longDescription: v.string(
|
||||||
|
"Long description of the extension (Will be displayed in store)",
|
||||||
|
),
|
||||||
identifier: v.string(
|
identifier: v.string(
|
||||||
"Unique identifier for the extension, must be the same as extension folder name"
|
"Unique identifier for the extension, must be the same as extension folder name",
|
||||||
),
|
),
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
permissions: v.array(
|
permissions: v.array(
|
||||||
PermissionUnion,
|
PermissionUnion,
|
||||||
"Permissions Declared by the extension. e.g. clipboard-all. Not declared APIs will be blocked."
|
"Permissions Declared by the extension. e.g. clipboard-all. Not declared APIs will be blocked.",
|
||||||
),
|
),
|
||||||
demoImages: v.array(v.string("Demo images for the extension")),
|
demoImages: v.array(v.string("Demo images for the extension")),
|
||||||
customUiCmds: v.optional(v.array(CustomUiCmd, "Custom UI Commands")),
|
customUiCmds: v.optional(v.array(CustomUiCmd, "Custom UI Commands")),
|
||||||
templateUiCmds: v.optional(v.array(TemplateUiCmd, "Template UI Commands")),
|
templateUiCmds: v.optional(v.array(TemplateUiCmd, "Template UI Commands")),
|
||||||
headlessCmds: v.optional(v.array(HeadlessCmd, "Headless Commands"))
|
headlessCmds: v.optional(v.array(HeadlessCmd, "Headless Commands")),
|
||||||
})
|
});
|
||||||
export type KunkunExtManifest = v.InferOutput<typeof KunkunExtManifest>
|
export type KunkunExtManifest = v.InferOutput<typeof KunkunExtManifest>;
|
||||||
|
|
||||||
const Person = v.union([
|
const Person = v.union([
|
||||||
v.object({
|
v.object({
|
||||||
name: v.string("GitHub Username"),
|
name: v.string("GitHub Username"),
|
||||||
email: v.string("Email of the person"),
|
email: v.string("Email of the person"),
|
||||||
url: v.optional(v.nullable(v.string("URL of the person")))
|
url: v.optional(v.nullable(v.string("URL of the person"))),
|
||||||
}),
|
}),
|
||||||
v.string("GitHub Username")
|
v.string("GitHub Username"),
|
||||||
])
|
]);
|
||||||
|
|
||||||
export const ExtPackageJson = v.object({
|
export const ExtPackageJson = v.object({
|
||||||
name: v.string("Package name for the extension (just a regular npm package name)"),
|
name: v.string(
|
||||||
|
"Package name for the extension (just a regular npm package name)",
|
||||||
|
),
|
||||||
version: v.string("Version of the extension"),
|
version: v.string("Version of the extension"),
|
||||||
author: v.optional(Person),
|
author: v.optional(Person),
|
||||||
draft: v.optional(v.boolean("Whether the extension is a draft, draft will not be published")),
|
draft: v.optional(
|
||||||
|
v.boolean(
|
||||||
|
"Whether the extension is a draft, draft will not be published",
|
||||||
|
),
|
||||||
|
),
|
||||||
contributors: v.optional(v.array(Person, "Contributors of the extension")),
|
contributors: v.optional(v.array(Person, "Contributors of the extension")),
|
||||||
repository: v.optional(
|
repository: v.optional(
|
||||||
v.union([
|
v.union([
|
||||||
@ -152,14 +168,17 @@ export const ExtPackageJson = v.object({
|
|||||||
v.object({
|
v.object({
|
||||||
type: v.string("Type of the repository"),
|
type: v.string("Type of the repository"),
|
||||||
url: v.string("URL of the repository"),
|
url: v.string("URL of the repository"),
|
||||||
directory: v.string("Directory of the repository")
|
directory: v.string("Directory of the repository"),
|
||||||
})
|
}),
|
||||||
])
|
]),
|
||||||
),
|
),
|
||||||
|
dependencies: v.optional(v.record(v.string(), v.string())),
|
||||||
kunkun: KunkunExtManifest,
|
kunkun: KunkunExtManifest,
|
||||||
files: v.array(v.string("Files to include in the extension. e.g. ['dist']"))
|
files: v.array(
|
||||||
})
|
v.string("Files to include in the extension. e.g. ['dist']"),
|
||||||
export type ExtPackageJson = v.InferOutput<typeof ExtPackageJson>
|
),
|
||||||
|
});
|
||||||
|
export type ExtPackageJson = v.InferOutput<typeof ExtPackageJson>;
|
||||||
/**
|
/**
|
||||||
* Extra fields for ExtPackageJson
|
* Extra fields for ExtPackageJson
|
||||||
* e.g. path to the extension
|
* e.g. path to the extension
|
||||||
@ -168,8 +187,8 @@ export const ExtPackageJsonExtra = v.object({
|
|||||||
...ExtPackageJson.entries,
|
...ExtPackageJson.entries,
|
||||||
...{
|
...{
|
||||||
extPath: v.string(),
|
extPath: v.string(),
|
||||||
extFolderName: v.string()
|
extFolderName: v.string(),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export type ExtPackageJsonExtra = v.InferOutput<typeof ExtPackageJsonExtra>
|
export type ExtPackageJsonExtra = v.InferOutput<typeof ExtPackageJsonExtra>;
|
||||||
|
@ -1,268 +1,288 @@
|
|||||||
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]
|
export type Json =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| { [key: string]: Json | undefined }
|
||||||
|
| Json[]
|
||||||
|
|
||||||
export type Database = {
|
export type Database = {
|
||||||
public: {
|
public: {
|
||||||
Tables: {
|
Tables: {
|
||||||
events: {
|
events: {
|
||||||
Row: {
|
Row: {
|
||||||
created_at: string
|
created_at: string
|
||||||
data: Json | null
|
data: Json | null
|
||||||
event_type: Database["public"]["Enums"]["event_type"]
|
event_type: Database["public"]["Enums"]["event_type"]
|
||||||
id: number
|
id: number
|
||||||
ip: string
|
ip: string
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
created_at?: string
|
created_at?: string
|
||||||
data?: Json | null
|
data?: Json | null
|
||||||
event_type: Database["public"]["Enums"]["event_type"]
|
event_type: Database["public"]["Enums"]["event_type"]
|
||||||
id?: number
|
id?: number
|
||||||
ip: string
|
ip: string
|
||||||
}
|
}
|
||||||
Update: {
|
Update: {
|
||||||
created_at?: string
|
created_at?: string
|
||||||
data?: Json | null
|
data?: Json | null
|
||||||
event_type?: Database["public"]["Enums"]["event_type"]
|
event_type?: Database["public"]["Enums"]["event_type"]
|
||||||
id?: number
|
id?: number
|
||||||
ip?: string
|
ip?: string
|
||||||
}
|
}
|
||||||
Relationships: []
|
Relationships: []
|
||||||
}
|
}
|
||||||
ext_images: {
|
ext_images: {
|
||||||
Row: {
|
Row: {
|
||||||
created_at: string
|
created_at: string
|
||||||
image_path: string
|
image_path: string
|
||||||
sha512: string
|
sha512: string
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
created_at?: string
|
created_at?: string
|
||||||
image_path: string
|
image_path: string
|
||||||
sha512: string
|
sha512: string
|
||||||
}
|
}
|
||||||
Update: {
|
Update: {
|
||||||
created_at?: string
|
created_at?: string
|
||||||
image_path?: string
|
image_path?: string
|
||||||
sha512?: string
|
sha512?: string
|
||||||
}
|
}
|
||||||
Relationships: []
|
Relationships: []
|
||||||
}
|
}
|
||||||
ext_publish: {
|
ext_publish: {
|
||||||
Row: {
|
Row: {
|
||||||
api_version: string | null
|
api_version: string | null
|
||||||
cmd_count: number
|
cmd_count: number
|
||||||
created_at: string
|
created_at: string
|
||||||
demo_images: string[]
|
demo_images: string[]
|
||||||
downloads: number
|
downloads: number
|
||||||
id: number
|
id: number
|
||||||
identifier: string
|
identifier: string
|
||||||
manifest: Json
|
manifest: Json
|
||||||
name: string
|
metadata: Json | null
|
||||||
shasum: string
|
name: string
|
||||||
size: number
|
shasum: string
|
||||||
tarball_path: string
|
size: number
|
||||||
version: string
|
tarball_path: string
|
||||||
}
|
version: string
|
||||||
Insert: {
|
}
|
||||||
api_version?: string | null
|
Insert: {
|
||||||
cmd_count: number
|
api_version?: string | null
|
||||||
created_at?: string
|
cmd_count: number
|
||||||
demo_images: string[]
|
created_at?: string
|
||||||
downloads: number
|
demo_images: string[]
|
||||||
id?: number
|
downloads: number
|
||||||
identifier: string
|
id?: number
|
||||||
manifest: Json
|
identifier: string
|
||||||
name: string
|
manifest: Json
|
||||||
shasum: string
|
metadata?: Json | null
|
||||||
size: number
|
name: string
|
||||||
tarball_path: string
|
shasum: string
|
||||||
version: string
|
size: number
|
||||||
}
|
tarball_path: string
|
||||||
Update: {
|
version: string
|
||||||
api_version?: string | null
|
}
|
||||||
cmd_count?: number
|
Update: {
|
||||||
created_at?: string
|
api_version?: string | null
|
||||||
demo_images?: string[]
|
cmd_count?: number
|
||||||
downloads?: number
|
created_at?: string
|
||||||
id?: number
|
demo_images?: string[]
|
||||||
identifier?: string
|
downloads?: number
|
||||||
manifest?: Json
|
id?: number
|
||||||
name?: string
|
identifier?: string
|
||||||
shasum?: string
|
manifest?: Json
|
||||||
size?: number
|
metadata?: Json | null
|
||||||
tarball_path?: string
|
name?: string
|
||||||
version?: string
|
shasum?: string
|
||||||
}
|
size?: number
|
||||||
Relationships: [
|
tarball_path?: string
|
||||||
{
|
version?: string
|
||||||
foreignKeyName: "ext_publish_identifier_fkey"
|
}
|
||||||
columns: ["identifier"]
|
Relationships: [
|
||||||
isOneToOne: false
|
{
|
||||||
referencedRelation: "extensions"
|
foreignKeyName: "ext_publish_identifier_fkey"
|
||||||
referencedColumns: ["identifier"]
|
columns: ["identifier"]
|
||||||
}
|
isOneToOne: false
|
||||||
]
|
referencedRelation: "extensions"
|
||||||
}
|
referencedColumns: ["identifier"]
|
||||||
extensions: {
|
},
|
||||||
Row: {
|
]
|
||||||
api_version: string
|
}
|
||||||
created_at: string
|
extensions: {
|
||||||
downloads: number
|
Row: {
|
||||||
icon: Json | null
|
api_version: string
|
||||||
identifier: string
|
author_id: string | null
|
||||||
long_description: string | null
|
created_at: string
|
||||||
name: string
|
downloads: number
|
||||||
readme: string | null
|
icon: Json | null
|
||||||
short_description: string
|
identifier: string
|
||||||
version: string
|
long_description: string | null
|
||||||
}
|
name: string
|
||||||
Insert: {
|
readme: string | null
|
||||||
api_version: string
|
short_description: string
|
||||||
created_at?: string
|
version: string
|
||||||
downloads: number
|
}
|
||||||
icon?: Json | null
|
Insert: {
|
||||||
identifier: string
|
api_version: string
|
||||||
long_description?: string | null
|
author_id?: string | null
|
||||||
name: string
|
created_at?: string
|
||||||
readme?: string | null
|
downloads: number
|
||||||
short_description: string
|
icon?: Json | null
|
||||||
version: string
|
identifier: string
|
||||||
}
|
long_description?: string | null
|
||||||
Update: {
|
name: string
|
||||||
api_version?: string
|
readme?: string | null
|
||||||
created_at?: string
|
short_description: string
|
||||||
downloads?: number
|
version: string
|
||||||
icon?: Json | null
|
}
|
||||||
identifier?: string
|
Update: {
|
||||||
long_description?: string | null
|
api_version?: string
|
||||||
name?: string
|
author_id?: string | null
|
||||||
readme?: string | null
|
created_at?: string
|
||||||
short_description?: string
|
downloads?: number
|
||||||
version?: string
|
icon?: Json | null
|
||||||
}
|
identifier?: string
|
||||||
Relationships: []
|
long_description?: string | null
|
||||||
}
|
name?: string
|
||||||
}
|
readme?: string | null
|
||||||
Views: {
|
short_description?: string
|
||||||
[_ in never]: never
|
version?: string
|
||||||
}
|
}
|
||||||
Functions: {
|
Relationships: []
|
||||||
get_aggregated_downloads: {
|
}
|
||||||
Args: Record<PropertyKey, never>
|
}
|
||||||
Returns: {
|
Views: {
|
||||||
identifier: string
|
[_ in never]: never
|
||||||
total_downloads: number
|
}
|
||||||
}[]
|
Functions: {
|
||||||
}
|
get_aggregated_downloads: {
|
||||||
get_aggregated_downloads_with_details: {
|
Args: Record<PropertyKey, never>
|
||||||
Args: Record<PropertyKey, never>
|
Returns: {
|
||||||
Returns: {
|
identifier: string
|
||||||
identifier: string
|
total_downloads: number
|
||||||
total_downloads: number
|
}[]
|
||||||
name: string
|
}
|
||||||
short_description: string
|
get_aggregated_downloads_with_details: {
|
||||||
}[]
|
Args: Record<PropertyKey, never>
|
||||||
}
|
Returns: {
|
||||||
increment_downloads: {
|
identifier: string
|
||||||
Args: {
|
total_downloads: number
|
||||||
t_identifier: string
|
name: string
|
||||||
t_version: string
|
short_description: string
|
||||||
}
|
}[]
|
||||||
Returns: number
|
}
|
||||||
}
|
increment_downloads: {
|
||||||
}
|
Args: {
|
||||||
Enums: {
|
t_identifier: string
|
||||||
event_type: "download" | "updater" | "schema" | "nightly_schema"
|
t_version: string
|
||||||
}
|
}
|
||||||
CompositeTypes: {
|
Returns: number
|
||||||
[_ in never]: never
|
}
|
||||||
}
|
}
|
||||||
}
|
Enums: {
|
||||||
|
event_type: "download" | "updater" | "schema" | "nightly_schema"
|
||||||
|
}
|
||||||
|
CompositeTypes: {
|
||||||
|
[_ in never]: never
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicSchema = Database[Extract<keyof Database, "public">]
|
type PublicSchema = Database[Extract<keyof Database, "public">]
|
||||||
|
|
||||||
export type Tables<
|
export type Tables<
|
||||||
PublicTableNameOrOptions extends
|
PublicTableNameOrOptions extends
|
||||||
| keyof (PublicSchema["Tables"] & PublicSchema["Views"])
|
| keyof (PublicSchema["Tables"] & PublicSchema["Views"])
|
||||||
| { schema: keyof Database },
|
| { schema: keyof Database },
|
||||||
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
||||||
? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
|
? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
|
||||||
Database[PublicTableNameOrOptions["schema"]]["Views"])
|
Database[PublicTableNameOrOptions["schema"]]["Views"])
|
||||||
: never = never
|
: never = never,
|
||||||
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
||||||
? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
|
? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
|
||||||
Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
|
Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
|
||||||
Row: infer R
|
Row: infer R
|
||||||
}
|
}
|
||||||
? R
|
? R
|
||||||
: never
|
: never
|
||||||
: PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & PublicSchema["Views"])
|
: PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
|
||||||
? (PublicSchema["Tables"] & PublicSchema["Views"])[PublicTableNameOrOptions] extends {
|
PublicSchema["Views"])
|
||||||
Row: infer R
|
? (PublicSchema["Tables"] &
|
||||||
}
|
PublicSchema["Views"])[PublicTableNameOrOptions] extends {
|
||||||
? R
|
Row: infer R
|
||||||
: never
|
}
|
||||||
: never
|
? R
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
|
||||||
export type TablesInsert<
|
export type TablesInsert<
|
||||||
PublicTableNameOrOptions extends keyof PublicSchema["Tables"] | { schema: keyof Database },
|
PublicTableNameOrOptions extends
|
||||||
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
| keyof PublicSchema["Tables"]
|
||||||
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
|
| { schema: keyof Database },
|
||||||
: never = never
|
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
||||||
|
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
|
||||||
|
: never = never,
|
||||||
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
||||||
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||||
Insert: infer I
|
Insert: infer I
|
||||||
}
|
}
|
||||||
? I
|
? I
|
||||||
: never
|
: never
|
||||||
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
|
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
|
||||||
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
|
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
|
||||||
Insert: infer I
|
Insert: infer I
|
||||||
}
|
}
|
||||||
? I
|
? I
|
||||||
: never
|
: never
|
||||||
: never
|
: never
|
||||||
|
|
||||||
export type TablesUpdate<
|
export type TablesUpdate<
|
||||||
PublicTableNameOrOptions extends keyof PublicSchema["Tables"] | { schema: keyof Database },
|
PublicTableNameOrOptions extends
|
||||||
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
| keyof PublicSchema["Tables"]
|
||||||
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
|
| { schema: keyof Database },
|
||||||
: never = never
|
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
||||||
|
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
|
||||||
|
: never = never,
|
||||||
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
||||||
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||||
Update: infer U
|
Update: infer U
|
||||||
}
|
}
|
||||||
? U
|
? U
|
||||||
: never
|
: never
|
||||||
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
|
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
|
||||||
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
|
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
|
||||||
Update: infer U
|
Update: infer U
|
||||||
}
|
}
|
||||||
? U
|
? U
|
||||||
: never
|
: never
|
||||||
: never
|
: never
|
||||||
|
|
||||||
export type Enums<
|
export type Enums<
|
||||||
PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] | { schema: keyof Database },
|
PublicEnumNameOrOptions extends
|
||||||
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
|
| keyof PublicSchema["Enums"]
|
||||||
? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
|
| { schema: keyof Database },
|
||||||
: never = never
|
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
|
||||||
|
? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
|
||||||
|
: never = never,
|
||||||
> = PublicEnumNameOrOptions extends { schema: keyof Database }
|
> = PublicEnumNameOrOptions extends { schema: keyof Database }
|
||||||
? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
|
? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
|
||||||
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
|
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
|
||||||
? PublicSchema["Enums"][PublicEnumNameOrOptions]
|
? PublicSchema["Enums"][PublicEnumNameOrOptions]
|
||||||
: never
|
: never
|
||||||
|
|
||||||
export type CompositeTypes<
|
export type CompositeTypes<
|
||||||
PublicCompositeTypeNameOrOptions extends
|
PublicCompositeTypeNameOrOptions extends
|
||||||
| keyof PublicSchema["CompositeTypes"]
|
| keyof PublicSchema["CompositeTypes"]
|
||||||
| { schema: keyof Database },
|
| { schema: keyof Database },
|
||||||
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
|
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
|
||||||
schema: keyof Database
|
schema: keyof Database
|
||||||
}
|
}
|
||||||
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
|
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
|
||||||
: never = never
|
: never = never,
|
||||||
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
|
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
|
||||||
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
|
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
|
||||||
: PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
|
: PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
|
||||||
? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
|
? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
|
||||||
: never
|
: never
|
||||||
|
@ -21,7 +21,7 @@ export const breakingChangesVersionCheckpoints = [
|
|||||||
const checkpointVersions = breakingChangesVersionCheckpoints.map((c) => c.version)
|
const checkpointVersions = breakingChangesVersionCheckpoints.map((c) => c.version)
|
||||||
const sortedCheckpointVersions = sort(checkpointVersions)
|
const sortedCheckpointVersions = sort(checkpointVersions)
|
||||||
|
|
||||||
export const version = "0.0.48"
|
export const version = "0.0.52"
|
||||||
|
|
||||||
export function isVersionBetween(v: string, start: string, end: string) {
|
export function isVersionBetween(v: string, start: string, end: string) {
|
||||||
const vCleaned = clean(v)
|
const vCleaned = clean(v)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@kksh/supabase": "workspace:*",
|
"@kksh/supabase": "workspace:*",
|
||||||
"@std/semver": "npm:@jsr/std__semver@^1.0.3",
|
"@std/semver": "npm:@jsr/std__semver@^1.0.3",
|
||||||
|
"@tauri-apps/plugin-upload": "^2.2.1",
|
||||||
"uuid": "^11.0.3"
|
"uuid": "^11.0.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@ -19,7 +19,13 @@ import { loadExtensionManifestFromDisk } from "./load"
|
|||||||
*
|
*
|
||||||
* @param tarballPath path to .tar.gz file
|
* @param tarballPath path to .tar.gz file
|
||||||
*/
|
*/
|
||||||
export async function installTarball(tarballPath: string, extsDir: string): Promise<string> {
|
export async function installTarball(
|
||||||
|
tarballPath: string,
|
||||||
|
extsDir: string,
|
||||||
|
extras?: {
|
||||||
|
overwritePackageJson?: string
|
||||||
|
}
|
||||||
|
): Promise<string> {
|
||||||
const tempDirPath = await path.tempDir()
|
const tempDirPath = await path.tempDir()
|
||||||
if (!extsDir) {
|
if (!extsDir) {
|
||||||
return Promise.reject("Extension Folder Not Set")
|
return Promise.reject("Extension Folder Not Set")
|
||||||
@ -32,7 +38,11 @@ export async function installTarball(tarballPath: string, extsDir: string): Prom
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return loadExtensionManifestFromDisk(await path.join(decompressDest, "package.json"))
|
const pkgJsonPath = await path.join(decompressDest, "package.json")
|
||||||
|
if (extras?.overwritePackageJson) {
|
||||||
|
await fs.writeTextFile(pkgJsonPath, extras.overwritePackageJson)
|
||||||
|
}
|
||||||
|
return loadExtensionManifestFromDisk(pkgJsonPath)
|
||||||
.then(async (manifest) => {
|
.then(async (manifest) => {
|
||||||
// The extension folder name will be the identifier
|
// The extension folder name will be the identifier
|
||||||
const extInstallPath = await path.join(extsDir, manifest.kunkun.identifier)
|
const extInstallPath = await path.join(extsDir, manifest.kunkun.identifier)
|
||||||
@ -62,8 +72,7 @@ export async function installTarball(tarballPath: string, extsDir: string): Prom
|
|||||||
console.error(err)
|
console.error(err)
|
||||||
throw new Error("Invalid Manifest or Extension")
|
throw new Error("Invalid Manifest or Extension")
|
||||||
}
|
}
|
||||||
console.log()
|
console.error("installTarball error", err)
|
||||||
|
|
||||||
throw new Error(err)
|
throw new Error(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -74,13 +83,18 @@ export async function installTarball(tarballPath: string, extsDir: string): Prom
|
|||||||
* @param extsDir Target directory to install the tarball
|
* @param extsDir Target directory to install the tarball
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function installTarballUrl(tarballUrl: string, extsDir: string): Promise<string> {
|
export async function installTarballUrl(
|
||||||
|
tarballUrl: string,
|
||||||
|
extsDir: string,
|
||||||
|
extras?: { overwritePackageJson?: string }
|
||||||
|
): Promise<string> {
|
||||||
const filename = await path.basename(tarballUrl)
|
const filename = await path.basename(tarballUrl)
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const tempDirPath = await path.tempDir()
|
const tempDirPath = await path.tempDir()
|
||||||
let tarballPath = await path.join(tempDirPath, filename)
|
let tarballPath = await path.join(tempDirPath, filename)
|
||||||
|
console.log("tarballPath", tarballPath)
|
||||||
await download(tarballUrl, tarballPath)
|
await download(tarballUrl, tarballPath)
|
||||||
const extInstallPath = await installTarball(tarballPath, extsDir)
|
const extInstallPath = await installTarball(tarballPath, extsDir, extras)
|
||||||
await fs.remove(tarballPath)
|
await fs.remove(tarballPath)
|
||||||
return extInstallPath
|
return extInstallPath
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,35 +48,35 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@kksh/svelte": "0.1.4",
|
"@kksh/svelte5": "0.1.11",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.416.0",
|
"lucide-svelte": "^0.469.0",
|
||||||
"mode-watcher": "^0.4.0",
|
"mode-watcher": "^0.5.0",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-variants": "^0.2.1"
|
"tailwind-variants": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.3.1",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.15.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.16.6",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.1.1",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^5.0.3",
|
"vite": "^6.0.7",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.36.0",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"globals": "^15.0.0",
|
"globals": "^15.14.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.4",
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript-eslint": "^8.0.0-alpha.20"
|
"typescript-eslint": "^8.19.1"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@import url("@kksh/svelte/themes");
|
@import url("@kksh/svelte5/themes");
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ThemeCustomizerButton, type ThemeConfig, updateTheme } from '@kksh/svelte';
|
import { ThemeCustomizerButton, type ThemeConfig, updateTheme } from '@kksh/svelte5';
|
||||||
import { ui } from '@kksh/api/ui/iframe';
|
import { ui } from '@kksh/api/ui/iframe';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { ModeWatcher } from 'mode-watcher';
|
import { ModeWatcher } from 'mode-watcher';
|
||||||
import { ThemeWrapper, updateTheme } from '@kksh/svelte';
|
import { ThemeWrapper, updateTheme } from '@kksh/svelte5';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { ui } from '@kksh/api/ui/iframe';
|
import { ui } from '@kksh/api/ui/iframe';
|
||||||
|
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
ModeToggle,
|
ModeToggle,
|
||||||
Button,
|
Button,
|
||||||
Command,
|
Command,
|
||||||
CommandFooter,
|
|
||||||
ModeWatcher,
|
ModeWatcher,
|
||||||
Separator,
|
Separator,
|
||||||
ThemeWrapper,
|
ThemeWrapper,
|
||||||
updateTheme
|
updateTheme
|
||||||
} from '@kksh/svelte';
|
} from '@kksh/svelte5';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { base } from '$app/paths';
|
import { base } from '$app/paths';
|
||||||
import { Alert, Button, ThemeWrapper } from '@kksh/svelte';
|
import { Alert, Button, ThemeWrapper } from '@kksh/svelte5';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
```bash
|
```bash
|
||||||
npx supabase gen types --lang=typescript --project-id $PROJECT_REF --schema public > src/types/database.types.ts
|
npx supabase gen types --lang=typescript --project-id $PROJECT_REF --schema public > ../api/src/supabase/database.types.ts
|
||||||
```
|
```
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
"prepare": "bun setup.ts"
|
"prepare": "bun setup.ts"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts",
|
||||||
|
"./models": "./src/models.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@supabase/supabase-js": "^2.46.1"
|
"@supabase/ssr": "^0.5.2",
|
||||||
|
"@supabase/supabase-js": "^2.47.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "latest"
|
||||||
|
@ -33,7 +33,7 @@ export class SupabaseAPI {
|
|||||||
return this.supabase
|
return this.supabase
|
||||||
.from("ext_publish")
|
.from("ext_publish")
|
||||||
.select(
|
.select(
|
||||||
"created_at, name, version, manifest, shasum, size, tarball_path, cmd_count, identifier, downloads, demo_images, api_version"
|
"created_at, name, version, manifest, shasum, size, tarball_path, cmd_count, identifier, downloads, demo_images, api_version, metadata"
|
||||||
)
|
)
|
||||||
.order("created_at", { ascending: false })
|
.order("created_at", { ascending: false })
|
||||||
.eq("identifier", identifier)
|
.eq("identifier", identifier)
|
||||||
@ -70,6 +70,28 @@ export class SupabaseAPI {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async publishExtFromJSR(payload: {
|
||||||
|
scope: string
|
||||||
|
version: string
|
||||||
|
name: string
|
||||||
|
}): Promise<void> {
|
||||||
|
return this.supabase.functions
|
||||||
|
.invoke("publish-jsr-ext", {
|
||||||
|
body: payload
|
||||||
|
})
|
||||||
|
.then(async ({ data, error }) => {
|
||||||
|
if (data && data.isValid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (error?.name === "FunctionsHttpError") {
|
||||||
|
const errorMessage = await error.context.json()
|
||||||
|
throw new Error(errorMessage.error)
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown error: ${error?.message}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
translateExtensionFilePathToUrl(tarballPath: string): string {
|
translateExtensionFilePathToUrl(tarballPath: string): string {
|
||||||
return this.supabase.storage.from("extensions").getPublicUrl(tarballPath).data.publicUrl
|
return this.supabase.storage.from("extensions").getPublicUrl(tarballPath).data.publicUrl
|
||||||
}
|
}
|
||||||
|
18
packages/supabase/src/models.ts
Normal file
18
packages/supabase/src/models.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @module @kksh/supabase/models
|
||||||
|
* This module contains some models for supabase database that cannot be code generated, such as JSON fields.
|
||||||
|
*/
|
||||||
|
import * as v from "valibot";
|
||||||
|
|
||||||
|
export enum ExtPublishSourceTypeEnum {
|
||||||
|
jsr = "jsr",
|
||||||
|
npm = "npm",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExtPublishMetadata = v.object({
|
||||||
|
source: v.optional(
|
||||||
|
v.string("Source of the extension (e.g. url to package)"),
|
||||||
|
),
|
||||||
|
sourceType: v.optional(v.enum(ExtPublishSourceTypeEnum)),
|
||||||
|
});
|
||||||
|
export type ExtPublishMetadata = v.InferOutput<typeof ExtPublishMetadata>;
|
@ -40,23 +40,23 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@kksh/svelte5": "0.1.10",
|
"@kksh/svelte5": "0.1.11",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.460.1",
|
"lucide-svelte": "^0.469.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-variants": "^0.3.0"
|
"tailwind-variants": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.1",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"svelte": "^5.2.7",
|
"svelte": "^5.16.6",
|
||||||
"svelte-check": "^4.0.9",
|
"svelte-check": "^4.1.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.7.2",
|
||||||
"vite": "^5.4.11",
|
"vite": "^6.0.7",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"tailwindcss": "^3.4.15"
|
"tailwindcss": "^3.4.17"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
@ -46,35 +46,35 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@kksh/svelte5": "0.1.10",
|
"@kksh/svelte5": "0.1.11",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.460.1",
|
"lucide-svelte": "^0.469.0",
|
||||||
"mode-watcher": "^0.5.0",
|
"mode-watcher": "^0.5.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-variants": "^0.3.0"
|
"tailwind-variants": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.3.1",
|
"@sveltejs/adapter-auto": "^3.3.1",
|
||||||
"@sveltejs/kit": "^2.8.1",
|
"@sveltejs/kit": "^2.15.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.1",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"svelte": "^5.2.7",
|
"svelte": "^5.16.6",
|
||||||
"svelte-check": "^4.0.9",
|
"svelte-check": "^4.1.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^5.4.11",
|
"vite": "^6.0.7",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.15.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.46.0",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.14.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.2.8",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript-eslint": "^8.15.0"
|
"typescript-eslint": "^8.19.1"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -33,22 +33,25 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^5.0.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/svelte": "^4.1.0",
|
"@iconify/svelte": "^4.2.0",
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
"@kksh/svelte5": "^0.1.10",
|
"@kksh/svelte5": "^0.1.12",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"bits-ui": "1.0.0-next.72",
|
"bits-ui": "1.0.0-next.77",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formsnap": "2.0.0-next.1",
|
"formsnap": "2.0.0-next.1",
|
||||||
"lucide-svelte": "^0.468.0",
|
"lucide-svelte": "^0.469.0",
|
||||||
"mode-watcher": "^0.5.0",
|
"mode-watcher": "^0.5.0",
|
||||||
"paneforge": "1.0.0-next.1",
|
"paneforge": "0.0.6",
|
||||||
"shiki": "^1.24.2",
|
"shiki": "^1.26.1",
|
||||||
"svelte-radix": "^2.0.1",
|
"svelte-radix": "^2.0.1",
|
||||||
"svelte-sonner": "^0.3.28",
|
"svelte-sonner": "^0.3.28",
|
||||||
"sveltekit-superforms": "^2.22.1",
|
"sveltekit-superforms": "^2.22.1",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-variants": "^0.3.0",
|
"tailwind-variants": "^0.3.0",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
123
packages/ui/src/components/common/ElementAlert.svelte
Normal file
123
packages/ui/src/components/common/ElementAlert.svelte
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<!-- Element Plus Style Alert (created because the original Shadcn Alert is not very good looking) -->
|
||||||
|
<script lang="ts" module>
|
||||||
|
export type AlertProps = {
|
||||||
|
title?: string
|
||||||
|
closable?: boolean
|
||||||
|
description?: string
|
||||||
|
variant?: "success" | "info" | "warning" | "error"
|
||||||
|
onClose?: () => void
|
||||||
|
class?: string
|
||||||
|
withIcon?: boolean
|
||||||
|
children?: Snippet
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
CircleAlertIcon,
|
||||||
|
CircleCheckBigIcon,
|
||||||
|
CircleHelpIcon,
|
||||||
|
CircleXIcon,
|
||||||
|
XIcon
|
||||||
|
} from "lucide-svelte"
|
||||||
|
import type { Snippet } from "svelte"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
import { cn } from "../../utils"
|
||||||
|
|
||||||
|
const config: Record<
|
||||||
|
"success" | "info" | "warning" | "error",
|
||||||
|
{
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
success: {
|
||||||
|
color: "green"
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
color: "blue"
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
color: "yellow"
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: "red"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
class: className,
|
||||||
|
variant: type = "info",
|
||||||
|
closable,
|
||||||
|
withIcon,
|
||||||
|
onClose,
|
||||||
|
children
|
||||||
|
}: AlertProps = $props()
|
||||||
|
let show = $state(true)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if show}
|
||||||
|
<div
|
||||||
|
class={cn("flex items-center gap-3 rounded-xl px-3 py-3", className, {
|
||||||
|
"bg-red-500/10": type === "error",
|
||||||
|
"bg-blue-500/10": type === "info",
|
||||||
|
"bg-yellow-500/10": type === "warning",
|
||||||
|
"bg-green-500/10": type === "success"
|
||||||
|
})}
|
||||||
|
transition:fade
|
||||||
|
>
|
||||||
|
{#if withIcon}
|
||||||
|
{#if type === "success"}
|
||||||
|
<CircleCheckBigIcon class="h-6 w-6 shrink-0 text-green-400" />
|
||||||
|
{:else if type === "info"}
|
||||||
|
<CircleHelpIcon class="h-6 w-6 shrink-0 text-blue-400" />
|
||||||
|
{:else if type === "warning"}
|
||||||
|
<CircleAlertIcon class="h-6 w-6 shrink-0 text-yellow-400" />
|
||||||
|
{:else if type === "error"}
|
||||||
|
<CircleXIcon class="h-6 w-6 shrink-0 text-red-400" />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<div class="flex grow flex-col">
|
||||||
|
{#if title}
|
||||||
|
<span
|
||||||
|
class={cn("font-semibold", {
|
||||||
|
"text-green-400": type === "success",
|
||||||
|
"text-blue-400": type === "info",
|
||||||
|
"text-yellow-400": type === "warning",
|
||||||
|
"text-red-400": type === "error"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{#if description}
|
||||||
|
<small
|
||||||
|
class={cn("text-sm", {
|
||||||
|
"text-green-400/90": type === "success",
|
||||||
|
"text-blue-400/90": type === "info",
|
||||||
|
"text-yellow-400/90": type === "warning",
|
||||||
|
"text-red-400/90": type === "error"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</small>
|
||||||
|
{/if}
|
||||||
|
{#if children}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if closable}
|
||||||
|
<XIcon
|
||||||
|
onclick={() => {
|
||||||
|
if (onClose) {
|
||||||
|
onClose()
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
show = false
|
||||||
|
}}
|
||||||
|
class="h-4 w-4 shrink-0 cursor-pointer self-start"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
@ -3,4 +3,5 @@ export { default as IconSelector } from "./IconSelector.svelte"
|
|||||||
export { default as StrikeSeparator } from "./StrikeSeparator.svelte"
|
export { default as StrikeSeparator } from "./StrikeSeparator.svelte"
|
||||||
export { default as LoadingBar } from "./LoadingBar.svelte"
|
export { default as LoadingBar } from "./LoadingBar.svelte"
|
||||||
export { default as TauriLink } from "./TauriLink.svelte"
|
export { default as TauriLink } from "./TauriLink.svelte"
|
||||||
|
export { default as ElementAlert } from "./ElementAlert.svelte"
|
||||||
export * from "./date"
|
export * from "./date"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export { default as ExtListItem } from "./ExtListItem.svelte"
|
export { default as ExtListItem } from "./ExtListItem.svelte"
|
||||||
export { default as StoreExtDetail } from "./StoreExtDetail.svelte"
|
export { default as StoreExtDetail } from "./StoreExtDetail.svelte"
|
||||||
export { default as PermissionInspector } from "./PermissionInspector.svelte"
|
export { default as PermissionInspector } from "./PermissionInspector.svelte"
|
||||||
|
export { default as JsrPackageVersionTable } from "./publish/jsr/jsr-package-version-table.svelte"
|
||||||
export * as Templates from "./templates"
|
export * as Templates from "./templates"
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Button, HoverCard, Table } from "@kksh/svelte5"
|
||||||
|
import { cn } from "../../../../utils"
|
||||||
|
|
||||||
|
type Version = {
|
||||||
|
scope: string
|
||||||
|
package?: string
|
||||||
|
version: string
|
||||||
|
yanked: boolean
|
||||||
|
rekorLogId?: string
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
class: className,
|
||||||
|
githubOwnerMismatch,
|
||||||
|
versions,
|
||||||
|
onPublish,
|
||||||
|
publishedVersions
|
||||||
|
}: {
|
||||||
|
class?: string
|
||||||
|
githubOwnerMismatch: boolean
|
||||||
|
versions: Version[]
|
||||||
|
onPublish?: (version: Version) => void
|
||||||
|
publishedVersions: string[]
|
||||||
|
} = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Table.Root class={className}>
|
||||||
|
<Table.Caption>All versions of the package</Table.Caption>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Head class="w-[100px] text-center">Version</Table.Head>
|
||||||
|
<Table.Head class="text-center">Yanked</Table.Head>
|
||||||
|
<Table.Head class="text-center">Signed by GitHub Action</Table.Head>
|
||||||
|
<Table.Head class="text-center">Publish This Version</Table.Head>
|
||||||
|
<Table.Head class="text-center">Published</Table.Head>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body class="max-h-96">
|
||||||
|
{#each versions as version, i (i)}
|
||||||
|
{@const isPublished = publishedVersions.includes(version.version)}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell class="text-center font-medium">
|
||||||
|
<a
|
||||||
|
href={`https://jsr.io/@${version.scope}/${version.package}@${version.version}`}
|
||||||
|
target="_blank"
|
||||||
|
class="text-blue-500 underline"
|
||||||
|
>
|
||||||
|
{version.version}
|
||||||
|
</a>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell
|
||||||
|
class={cn("text-center font-bold", {
|
||||||
|
"text-red-500": version.yanked,
|
||||||
|
"text-green-500": !version.yanked
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{version.yanked ? "Yes" : "No"}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="text-center">{version.rekorLogId ? "✅" : "❌"}</Table.Cell>
|
||||||
|
<Table.Cell class="text-center">
|
||||||
|
{@const disabled =
|
||||||
|
version.yanked || !version.rekorLogId || githubOwnerMismatch || isPublished}
|
||||||
|
{#if disabled}
|
||||||
|
<HoverCard.Root>
|
||||||
|
<HoverCard.Trigger>
|
||||||
|
<Button size="sm" variant="outline" disabled={true}>Publish</Button>
|
||||||
|
</HoverCard.Trigger>
|
||||||
|
<HoverCard.Content>
|
||||||
|
{#if version.yanked}
|
||||||
|
Version is yanked
|
||||||
|
{:else if !version.rekorLogId}
|
||||||
|
Version is not signed by GitHub Action
|
||||||
|
{:else if githubOwnerMismatch}
|
||||||
|
Your GitHub account is not the owner of the package
|
||||||
|
{:else if isPublished}
|
||||||
|
Version is already published
|
||||||
|
{/if}
|
||||||
|
</HoverCard.Content>
|
||||||
|
</HoverCard.Root>
|
||||||
|
{:else}
|
||||||
|
<Button size="sm" variant="outline" onclick={() => onPublish?.(version)}>
|
||||||
|
Publish
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="text-center">
|
||||||
|
{isPublished ? "✅" : "❌"}
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
</Table.Body>
|
||||||
|
<Table.Footer></Table.Footer>
|
||||||
|
</Table.Root>
|
@ -2,11 +2,13 @@
|
|||||||
import { CmdTypeEnum, IconEnum, SysCommand } from "@kksh/api/models"
|
import { CmdTypeEnum, IconEnum, SysCommand } from "@kksh/api/models"
|
||||||
import { Command } from "@kksh/svelte5"
|
import { Command } from "@kksh/svelte5"
|
||||||
import { IconMultiplexer } from "@kksh/ui"
|
import { IconMultiplexer } from "@kksh/ui"
|
||||||
import { confirm } from "@tauri-apps/plugin-dialog"
|
|
||||||
import { DraggableCommandGroup } from "../custom"
|
import { DraggableCommandGroup } from "../custom"
|
||||||
import { CmdValue } from "./types"
|
import { CmdValue } from "./types"
|
||||||
|
|
||||||
const { systemCommands }: { systemCommands: SysCommand[] } = $props()
|
const {
|
||||||
|
systemCommands,
|
||||||
|
onConfirm
|
||||||
|
}: { systemCommands: SysCommand[]; onConfirm?: (cmd: SysCommand) => Promise<boolean> } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DraggableCommandGroup heading="System Commands">
|
<DraggableCommandGroup heading="System Commands">
|
||||||
@ -15,7 +17,7 @@
|
|||||||
class="flex justify-between"
|
class="flex justify-between"
|
||||||
onSelect={async () => {
|
onSelect={async () => {
|
||||||
if (cmd.confirmRequired) {
|
if (cmd.confirmRequired) {
|
||||||
const confirmed = await confirm(`Are you sure you want to run ${cmd.name}?`)
|
const confirmed = onConfirm ? await onConfirm?.(cmd) : true
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
cmd.function()
|
cmd.function()
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
3385
pnpm-lock.yaml
generated
3385
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
vendors/tauri-plugin-system-info
vendored
2
vendors/tauri-plugin-system-info
vendored
@ -1 +1 @@
|
|||||||
Subproject commit ab47fa355ba5c6088abd2d173efb742062202a8a
|
Subproject commit 282b6df90c6e4fc596e1be33e923e13270174ee1
|
Loading…
x
Reference in New Issue
Block a user