初始化
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# koring-launcher
|
||||
|
||||
Minecraft launcher built with Tauri 2 + React 19 + TypeScript + Sidecar (Node.js/@xmcl).
|
||||
|
||||
## Quick commands
|
||||
|
||||
```bash
|
||||
pnpm tauri dev # full app dev (frontend + Rust + sidecar skip)
|
||||
pnpm dev # frontend only (vite, port 1420)
|
||||
cd sidecar && pnpm dev # sidecar dev (tsx)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Frontend** (`src/`): React 19 + Vite 7 + Tailwind v4 + shadcn/ui + Zustand stores
|
||||
- **Backend** (`src-tauri/`): Rust/Tauri 2, manages sidecar process via stdin/stdout JSON
|
||||
- **Sidecar** (`sidecar/`): Node.js/TypeScript, wraps @xmcl/* packages (MC install/launch/auth/mods)
|
||||
- IPC: Frontend → `invoke()` → Rust commands → sidecar stdin → sidecar stdout → Tauri events → Frontend
|
||||
|
||||
## Key gotchas
|
||||
|
||||
- **Sidecar binary required**: `src-tauri/binaries/koring-sidecar-<target-triple>.exe` must exist. In debug mode it's skipped automatically (`#[cfg(debug_assertions)]` in `lib.rs:setup`).
|
||||
- **Sidecar compile**: `cd sidecar && pnpm compile` (requires `bun`). Default target is ARM64 Windows. Use `pnpm compile:x64` for x86_64.
|
||||
- **@xmcl packages are Node.js only**: `@xmcl/core`, `@xmcl/installer` require `fs`/`child_process`. Only `@xmcl/user`, `@xmcl/modrinth`, `@xmcl/curseforge` work in browser.
|
||||
- **Path alias**: `@/` maps to `src/` (configured in `vite.config.ts` and `tsconfig.json`).
|
||||
- **Tauri commands**: All Rust commands are in `src-tauri/src/commands/mod.rs`. They forward to sidecar via `SidecarManager`.
|
||||
- **State management**: Zustand stores in `src/stores/` (install, auth, launch, mods, instance).
|
||||
|
||||
## Build & bundle
|
||||
|
||||
```bash
|
||||
pnpm tauri build # production build (requires sidecar binary in binaries/)
|
||||
cd sidecar && pnpm compile # compile sidecar to exe first
|
||||
```
|
||||
|
||||
The `externalBin` in `tauri.conf.json` expects `binaries/koring-sidecar-<target-triple>.exe`.
|
||||
|
||||
## Rust notes
|
||||
|
||||
- `src-tauri/src/lib.rs`: app entry, registers plugins and commands
|
||||
- `src-tauri/src/sidecar.rs`: `SidecarManager` — spawns sidecar, reads stdout, emits Tauri events
|
||||
- `src-tauri/src/commands/mod.rs`: all `#[tauri::command]` handlers
|
||||
- `uuid` crate used for request IDs
|
||||
@@ -0,0 +1,7 @@
|
||||
# Tauri + React + Typescript
|
||||
|
||||
This template should help get you started developing with Tauri, React and Typescript in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "base-nova",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"rtl": false,
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"menuColor": "default",
|
||||
"menuAccent": "subtle",
|
||||
"registries": {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri + React + Typescript</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "koring-launcher",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"dev:t": "tauri dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@fontsource-variable/geist": "^5.2.9",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.21.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"shadcn": "^4.11.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"tailwindcss": "^4.3.1",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 416 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 393 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg viewBox="0 0 1998.69 510.32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1998.692383" height="510.320312" fill="none" customFrame="#000000">
|
||||
<g id="组合 6">
|
||||
<g id="Koring Launcher">
|
||||
<path id="koring4" d="M694.086 218.401C694.18 218.307 694.227 218.166 694.227 217.978L694.227 142.033L711.422 121.365L775.801 218.26C775.801 218.354 775.825 218.424 775.872 218.471C775.919 218.518 775.989 218.541 776.083 218.541L820.636 218.541C820.683 218.541 820.718 218.53 820.741 218.506C820.765 218.483 820.776 218.448 820.776 218.401L820.776 217.978L736.608 91.0916L807.826 5.48995L807.826 5.06765C807.826 5.02072 807.814 4.98553 807.791 4.96207C807.767 4.93861 807.732 4.92688 807.685 4.92688L759.543 4.92688C759.524 4.92688 759.507 4.93063 759.492 4.93814C759.432 4.96817 759.402 5.05826 759.402 5.20841L694.227 84.925L694.227 5.48995C694.227 5.30226 694.18 5.16149 694.086 5.06765C693.992 4.9738 693.851 4.92688 693.664 4.92688L656.149 4.92688C655.961 4.92688 655.821 4.9738 655.727 5.06765C655.633 5.16149 655.586 5.30226 655.586 5.48995L655.586 217.978C655.586 218.166 655.633 218.307 655.727 218.401C655.821 218.494 655.961 218.541 656.149 218.541L693.664 218.541C693.851 218.541 693.992 218.494 694.086 218.401Z" fill-rule="evenodd" />
|
||||
<path id="koring5" d="M873.425 218.412C880.734 220.61 888.373 221.709 896.343 221.709C904.457 221.709 912.22 220.57 919.634 218.292C925.383 216.526 930.922 214.075 936.251 210.94C942.592 207.184 948.242 202.756 953.202 197.656C957.784 192.944 961.776 187.659 965.178 181.801C968.587 175.971 971.18 169.911 972.96 163.62C974.904 156.747 975.877 149.599 975.877 142.175C975.877 133.766 974.669 125.738 972.255 118.091C970.519 112.596 968.161 107.298 965.178 102.197C961.636 96.0971 957.454 90.6365 952.633 85.8152C947.811 80.994 942.351 76.812 936.251 73.2694C931.005 70.2027 925.556 67.7953 919.902 66.0472C912.407 63.7298 904.554 62.571 896.343 62.571C889.068 62.571 882.05 63.5049 875.29 65.3727C868.841 67.1548 862.626 69.787 856.647 73.2694C850.788 76.6718 845.503 80.6639 840.792 85.2458C835.692 90.2053 831.264 95.8557 827.508 102.197C824.457 107.382 822.054 112.771 820.299 118.364C817.926 125.928 816.739 133.866 816.739 142.175C816.739 149.69 817.729 156.923 819.708 163.874C821.474 170.074 824.027 176.049 827.367 181.801C830.838 187.738 834.909 193.086 839.58 197.845C844.507 202.865 850.102 207.23 856.365 210.94C861.836 214.147 867.523 216.638 873.425 218.412ZM917.087 182.766C911.077 186.064 904.163 187.713 896.343 187.713C888.432 187.713 881.468 186.025 875.451 182.65C875.044 182.422 874.641 182.185 874.243 181.942C872.204 180.685 870.314 179.266 868.571 177.683C864.977 174.422 862.011 170.469 859.673 165.824C856.248 158.879 854.535 150.996 854.535 142.175C854.535 133.119 856.201 125.189 859.533 118.385C861.547 114.301 864.086 110.751 867.151 107.734C869.227 105.69 871.544 103.892 874.102 102.338C874.647 102.009 875.2 101.694 875.76 101.394C881.758 98.1754 888.619 96.5664 896.343 96.5664C904.163 96.5664 911.077 98.2153 917.087 101.513C917.568 101.778 918.044 102.052 918.514 102.338C921.072 103.892 923.389 105.69 925.465 107.734C928.53 110.751 931.069 114.301 933.083 118.385C936.462 125.189 938.151 133.119 938.151 142.175C938.151 150.996 936.462 158.879 933.083 165.824C931.014 170.049 928.39 173.702 925.212 176.782C923.201 178.731 920.969 180.451 918.514 181.942C918.044 182.227 917.568 182.502 917.087 182.766Z" fill-rule="evenodd" />
|
||||
<path id="koring6" d="M1005.89 171.243L1005.89 66.3014C1005.89 66.1137 1005.94 65.9729 1006.03 65.8791C1006.13 65.7852 1006.27 65.7383 1006.45 65.7383L1042.21 65.7383C1042.4 65.7383 1042.54 65.7852 1042.63 65.8791C1042.72 65.9729 1042.77 66.1137 1042.77 66.3014L1042.77 79.6148C1043.72 78.8169 1044.68 78.039 1045.66 77.2812C1050.99 73.1562 1056.66 69.8988 1062.68 67.5089C1070.97 64.217 1079.91 62.571 1089.51 62.571C1089.69 62.571 1089.83 62.618 1089.93 62.7118C1090.02 62.8057 1090.07 62.9464 1090.07 63.1341L1090.07 97.7629C1090.07 97.9506 1090.02 98.0913 1089.93 98.1852C1089.83 98.279 1089.69 98.3259 1089.51 98.3259C1078.53 98.3259 1069.61 99.8275 1062.76 102.831C1059.69 104.164 1056.98 105.894 1054.63 108.021C1051.76 110.608 1049.43 113.781 1047.63 117.541C1044.39 124.344 1042.77 133.612 1042.77 145.342L1042.77 217.978C1042.77 218.166 1042.72 218.307 1042.63 218.4C1042.54 218.494 1042.4 218.541 1042.21 218.541L1006.45 218.541C1006.27 218.541 1006.13 218.494 1006.03 218.4C1005.94 218.307 1005.89 218.166 1005.89 217.978L1005.89 171.243Z" fill-rule="evenodd" />
|
||||
<path id="koring7" d="M1152.16 35.3326C1152.26 35.2388 1152.3 35.098 1152.3 34.9103L1152.3 0.56307C1152.3 0.37538 1152.26 0.234612 1152.16 0.140767C1152.07 0.0469225 1151.93 0 1151.74 0L1115.98 0C1115.8 0 1115.66 0.0469225 1115.56 0.140767C1115.47 0.234612 1115.42 0.37538 1115.42 0.56307L1115.42 34.9103C1115.42 35.098 1115.47 35.2388 1115.56 35.3326C1115.66 35.4265 1115.8 35.4734 1115.98 35.4734L1151.74 35.4734C1151.93 35.4734 1152.07 35.4265 1152.16 35.3326ZM1152.16 218.401C1152.26 218.307 1152.3 218.166 1152.3 217.978L1152.3 66.3015C1152.3 66.1138 1152.26 65.973 1152.16 65.8792C1152.07 65.7853 1151.93 65.7384 1151.74 65.7384L1115.98 65.7384C1115.8 65.7384 1115.66 65.7853 1115.56 65.8792C1115.47 65.973 1115.42 66.1138 1115.42 66.3015L1115.42 217.978C1115.42 218.166 1115.47 218.307 1115.56 218.401C1115.66 218.494 1115.8 218.541 1115.98 218.541L1151.74 218.541C1151.93 218.541 1152.07 218.494 1152.16 218.401Z" fill-rule="evenodd" />
|
||||
<path id="koring8" d="M1187.8 171.243L1187.8 66.3014C1187.8 66.1137 1187.85 65.9729 1187.94 65.8791C1188.04 65.7852 1188.18 65.7383 1188.37 65.7383L1224.12 65.7383C1224.31 65.7383 1224.45 65.7852 1224.54 65.8791C1224.64 65.9729 1224.68 66.1137 1224.68 66.3014L1224.68 80.058C1225.72 79.1539 1226.78 78.2752 1227.85 77.422C1235.08 71.6843 1242.81 67.6092 1251.02 65.1966C1256.97 63.4462 1263.19 62.571 1269.66 62.571C1273.38 62.571 1277 62.972 1280.54 63.774C1286.33 65.0882 1291.88 67.4791 1297.18 70.9467C1305.72 76.5305 1312.64 84.6481 1317.94 95.2994C1323.24 105.951 1325.9 118.385 1325.9 132.603L1325.9 217.978C1325.9 218.166 1325.85 218.307 1325.75 218.4C1325.66 218.494 1325.52 218.541 1325.33 218.541L1289.58 218.541C1289.43 218.541 1289.29 218.511 1289.17 218.451C1289.14 218.436 1289.11 218.419 1289.08 218.4C1288.99 218.307 1288.94 218.166 1288.94 217.978L1288.94 132.603C1288.94 124.72 1287.51 118.245 1284.65 113.177C1281.96 108.37 1278.57 104.765 1274.47 102.362C1274.2 102.208 1273.94 102.06 1273.67 101.916C1270.61 100.255 1267.46 99.1709 1264.22 98.6625C1262.79 98.4381 1261.34 98.3259 1259.88 98.3259C1252.93 98.3259 1246.85 99.7805 1241.65 102.69C1236.48 105.552 1232.36 110.455 1229.26 117.4C1226.21 124.298 1224.68 133.612 1224.68 145.342L1224.68 217.978C1224.68 218.166 1224.64 218.307 1224.54 218.4C1224.45 218.494 1224.31 218.541 1224.12 218.541L1188.37 218.541C1188.18 218.541 1188.04 218.494 1187.94 218.4C1187.85 218.307 1187.8 218.166 1187.8 217.978L1187.8 171.243Z" fill-rule="evenodd" />
|
||||
<path id="koring9" d="M1403.27 216.41C1412.02 219.942 1421.48 221.709 1431.65 221.709C1441.75 221.709 1450.81 219.66 1458.82 215.564C1462.11 213.877 1465.23 211.843 1468.18 209.462C1471 207.176 1473.62 204.729 1476.05 202.121C1475.88 211.676 1474.05 219.871 1470.57 226.706C1468.27 231.313 1465.32 235.196 1461.74 238.353C1459.71 240.148 1457.46 241.708 1455.01 243.035C1448.3 246.695 1440.42 248.525 1431.37 248.525C1420.25 248.525 1411.02 246.554 1403.71 242.612C1396.43 238.671 1389.82 232.571 1383.86 224.313C1383.86 224.261 1383.85 224.213 1383.83 224.168C1383.78 224.051 1383.67 223.958 1383.51 223.89C1383.44 223.859 1383.38 223.843 1383.32 223.843C1383.19 223.843 1383.07 223.906 1382.94 224.031L1354.15 244.231C1353.97 244.419 1353.8 244.56 1353.66 244.654C1353.64 244.671 1353.63 244.691 1353.63 244.712C1353.6 244.802 1353.68 244.924 1353.87 245.076C1356.94 249.181 1360.28 253.016 1363.89 256.582C1370.42 263.041 1377.82 268.613 1386.11 273.3C1399.01 280.62 1414.1 284.28 1431.37 284.28C1440.3 284.28 1448.81 282.967 1456.9 280.342C1462.23 278.612 1467.37 276.312 1472.33 273.44C1477.7 270.349 1482.6 266.744 1487.02 262.626C1492.86 257.181 1497.86 250.838 1502.03 243.598C1503.39 241.242 1504.63 238.83 1505.73 236.361C1510.59 225.55 1513.01 213.651 1513.01 200.664L1513.01 66.3014C1513.01 66.1859 1512.98 66.0881 1512.93 66.0082C1512.9 65.9582 1512.85 65.9152 1512.8 65.8791C1512.71 65.7852 1512.57 65.7383 1512.38 65.7383L1476.62 65.7383C1476.43 65.7383 1476.29 65.7852 1476.2 65.8791C1476.11 65.9729 1476.06 66.1137 1476.06 66.3014L1476.06 82.1781C1473.63 79.5605 1471.01 77.1071 1468.18 74.8178C1465.23 72.4364 1462.11 70.4024 1458.82 68.7156C1450.81 64.6192 1441.75 62.571 1431.65 62.571C1421.59 62.571 1412.23 64.3091 1403.57 67.7851C1399.82 69.2882 1396.21 71.1163 1392.73 73.2694C1388.07 76.1355 1383.83 79.4202 1379.98 83.1232C1374.32 88.5744 1369.54 94.9323 1365.63 102.197C1363.56 106.056 1361.83 110.029 1360.42 114.113C1357.37 122.941 1355.84 132.295 1355.84 142.175C1355.84 151.829 1357.31 160.988 1360.24 169.651C1361.68 173.915 1363.48 178.059 1365.63 182.083C1369.5 189.277 1374.23 195.59 1379.82 201.022C1383.71 204.802 1388.01 208.154 1392.73 211.081C1396.12 213.163 1399.63 214.939 1403.27 216.41ZM1458.18 182.786C1451.47 186.634 1443.59 188.558 1434.53 188.558C1430.51 188.558 1426.68 187.954 1423.06 186.746C1420.14 185.772 1417.35 184.405 1414.68 182.646C1413.61 181.933 1412.57 181.171 1411.58 180.36C1407.06 176.679 1403.38 171.974 1400.54 166.246C1397.07 159.208 1395.33 151.184 1395.33 142.175C1395.33 133.119 1397.07 125.048 1400.54 117.963C1403.38 112.12 1407.09 107.348 1411.67 103.647C1412.68 102.83 1413.73 102.065 1414.83 101.352C1418.21 99.1475 1421.76 97.5593 1425.47 96.5879C1428.39 95.8228 1431.41 95.4402 1434.53 95.4402C1443.59 95.4402 1451.47 97.364 1458.18 101.212C1460.99 102.813 1463.53 104.699 1465.8 106.87C1468.98 109.914 1471.63 113.518 1473.74 117.681C1477.4 124.767 1479.23 132.931 1479.23 142.175C1479.23 151.372 1477.4 159.489 1473.74 166.528C1471.8 170.284 1469.4 173.587 1466.55 176.436C1464.1 178.889 1461.31 181.006 1458.18 182.786Z" fill-rule="evenodd" />
|
||||
<path id="koring10" d="M655.727 506.692C655.821 506.786 655.961 506.833 656.149 506.833L783.895 506.833C784.083 506.833 784.224 506.786 784.318 506.692C784.411 506.598 784.458 506.457 784.458 506.27L784.458 469.952C784.458 469.764 784.411 469.623 784.318 469.529C784.224 469.435 784.083 469.389 783.895 469.389L694.227 469.389L694.227 293.781C694.227 293.594 694.18 293.453 694.086 293.359C693.992 293.265 693.851 293.218 693.664 293.218L656.149 293.218C655.961 293.218 655.821 293.265 655.727 293.359C655.633 293.453 655.586 293.594 655.586 293.781L655.586 506.27C655.586 506.457 655.633 506.598 655.727 506.692Z" fill-rule="evenodd" />
|
||||
<path id="koring11" d="M847.808 504.701C856.553 508.234 866.011 510 876.182 510C886.286 510 895.342 507.952 903.351 503.855C906.649 502.169 909.769 500.135 912.711 497.753C915.54 495.464 918.168 493.012 920.594 490.398L920.594 506.27C920.594 506.457 920.641 506.598 920.735 506.692C920.829 506.786 920.97 506.833 921.157 506.833L957.827 506.833C958.015 506.833 958.156 506.786 958.249 506.692C958.343 506.598 958.39 506.457 958.39 506.27L958.39 354.593C958.39 354.405 958.343 354.264 958.249 354.171C958.156 354.077 958.015 354.03 957.827 354.03L921.157 354.03C920.97 354.03 920.829 354.077 920.735 354.171C920.641 354.264 920.594 354.405 920.594 354.593L920.594 370.47C918.168 367.852 915.54 365.399 912.711 363.109C909.769 360.728 906.649 358.694 903.351 357.007C895.342 352.911 886.286 350.863 876.182 350.863C866.126 350.863 856.767 352.601 848.105 356.077C844.36 357.58 840.745 359.408 837.26 361.561C832.61 364.427 828.362 367.712 824.518 371.415C818.859 376.866 814.074 383.224 810.162 390.489C808.099 394.348 806.362 398.32 804.951 402.405C801.903 411.232 800.379 420.586 800.379 430.466C800.379 440.121 801.844 449.279 804.773 457.943C806.215 462.207 808.011 466.35 810.162 470.374C814.036 477.568 818.767 483.882 824.354 489.314C828.241 493.093 832.543 496.446 837.26 499.372C840.652 501.454 844.169 503.231 847.808 504.701ZM902.717 471.078C896.007 474.925 888.124 476.849 879.068 476.849C875.042 476.849 871.217 476.245 867.593 475.038C864.671 474.064 861.88 472.697 859.22 470.937C858.143 470.225 857.107 469.463 856.111 468.651C851.597 464.97 847.917 460.266 845.073 454.538C841.6 447.499 839.864 439.476 839.864 430.466C839.864 421.41 841.6 413.34 845.073 406.254C847.917 400.412 851.627 395.64 856.204 391.938C857.214 391.121 858.266 390.357 859.36 389.644C862.747 387.439 866.294 385.851 870.003 384.879C872.925 384.114 875.946 383.732 879.068 383.732C888.124 383.732 896.007 385.656 902.717 389.503C905.529 391.105 908.069 392.991 910.337 395.161C913.518 398.206 916.162 401.809 918.272 405.973C921.932 413.058 923.761 421.223 923.761 430.466C923.761 439.663 921.932 447.781 918.272 454.819C916.33 458.576 913.935 461.879 911.086 464.728C908.633 467.181 905.843 469.298 902.717 471.078Z" fill-rule="evenodd" />
|
||||
<path id="koring12" d="M1104.48 354.171C1104.38 354.264 1104.34 354.405 1104.34 354.593L1104.34 431.311C1104.34 441.306 1103.07 449.47 1100.54 455.805C1098.52 460.931 1095.65 464.966 1091.92 467.91C1091.04 468.604 1090.11 469.238 1089.13 469.811C1084.07 472.767 1077.68 474.245 1069.99 474.245C1062.53 474.245 1056.52 472.814 1051.97 469.952C1051.68 469.764 1051.39 469.569 1051.11 469.367C1047.04 466.444 1044.02 462.064 1042.05 456.227C1039.94 449.986 1038.88 441.681 1038.88 431.311L1038.88 354.593C1038.88 354.405 1038.83 354.264 1038.74 354.171C1038.65 354.077 1038.5 354.03 1038.32 354.03L1002.56 354.03C1002.37 354.03 1002.23 354.077 1002.14 354.171C1002.05 354.264 1002 354.405 1002 354.593L1002 431.311C1002 446.467 1004.67 460.004 1010.02 471.922C1013.91 480.507 1019.04 487.753 1025.41 493.66C1027.88 495.954 1030.54 498.045 1033.39 499.935C1034.04 500.366 1034.71 500.783 1035.38 501.186C1045.15 507.062 1056.68 510 1069.99 510C1080.89 510 1090.86 507.949 1099.9 503.847C1102.34 502.739 1104.71 501.482 1107.01 500.076C1111.02 497.622 1114.7 494.815 1118.04 491.655C1123.77 486.234 1128.51 479.774 1132.28 472.274C1138.24 460.356 1141.22 446.702 1141.22 431.311L1141.22 354.593C1141.22 354.405 1141.17 354.264 1141.08 354.171C1140.98 354.077 1140.84 354.03 1140.65 354.03L1104.9 354.03C1104.71 354.03 1104.57 354.077 1104.48 354.171Z" fill-rule="evenodd" />
|
||||
<path id="koring13" d="M1184.85 459.535L1184.85 354.593C1184.85 354.405 1184.89 354.264 1184.99 354.171C1185.08 354.077 1185.22 354.03 1185.41 354.03L1221.17 354.03C1221.35 354.03 1221.49 354.077 1221.59 354.171C1221.68 354.264 1221.73 354.405 1221.73 354.593L1221.73 368.349C1222.76 367.445 1223.82 366.567 1224.9 365.714C1232.13 359.976 1239.85 355.901 1248.06 353.488C1254.02 351.738 1260.23 350.863 1266.7 350.863C1270.42 350.863 1274.05 351.264 1277.58 352.066C1283.37 353.38 1288.92 355.771 1294.22 359.238C1302.76 364.822 1309.68 372.94 1314.99 383.591C1320.29 394.242 1322.94 406.677 1322.94 420.894L1322.94 506.27C1322.94 506.457 1322.89 506.598 1322.8 506.692C1322.71 506.786 1322.57 506.833 1322.38 506.833L1286.62 506.833C1286.47 506.833 1286.34 506.803 1286.22 506.743C1286.19 506.728 1286.16 506.711 1286.13 506.692C1286.04 506.598 1285.99 506.457 1285.99 506.27L1285.99 420.894C1285.99 413.011 1284.56 406.536 1281.7 401.468C1279.01 396.662 1275.61 393.057 1271.51 390.654C1271.25 390.5 1270.98 390.351 1270.72 390.207C1267.66 388.547 1264.51 387.462 1261.26 386.954C1259.83 386.73 1258.39 386.617 1256.92 386.617C1249.98 386.617 1243.9 388.072 1238.69 390.981C1233.53 393.844 1229.4 398.747 1226.3 405.691C1223.25 412.589 1221.73 421.903 1221.73 433.634L1221.73 506.27C1221.73 506.457 1221.68 506.598 1221.59 506.692C1221.49 506.786 1221.35 506.833 1221.17 506.833L1185.41 506.833C1185.22 506.833 1185.08 506.786 1184.99 506.692C1184.89 506.598 1184.85 506.457 1184.85 506.27L1184.85 459.535Z" fill-rule="evenodd" />
|
||||
<path id="koring14" d="M1479.93 452.497L1479.51 452.919C1474.16 460.239 1468.51 465.635 1462.55 469.107C1462.44 469.167 1462.34 469.226 1462.24 469.284C1456.35 472.592 1449.53 474.245 1441.78 474.245C1433.15 474.245 1425.6 472.486 1419.12 468.966C1416.36 467.433 1413.86 465.617 1411.62 463.52C1408.66 460.74 1406.16 457.464 1404.13 453.693C1400.61 447.03 1398.85 439.288 1398.85 430.466C1398.85 421.598 1400.61 413.856 1404.13 407.24C1406.16 403.469 1408.66 400.193 1411.62 397.413C1413.86 395.316 1416.36 393.5 1419.12 391.967C1425.6 388.401 1433.15 386.617 1441.78 386.617C1450.04 386.617 1457.06 388.26 1462.83 391.544C1468.6 394.782 1473.69 399.967 1478.1 407.099C1478.1 407.193 1478.13 407.263 1478.17 407.31C1478.22 407.357 1478.29 407.381 1478.38 407.381L1478.95 407.381L1509.21 389.222C1509.29 389.222 1509.35 389.207 1509.41 389.177C1509.5 389.132 1509.58 389.053 1509.63 388.94C1509.67 388.878 1509.68 388.815 1509.68 388.752C1509.68 388.627 1509.62 388.502 1509.49 388.377C1506.23 383.054 1502.44 378.245 1498.15 373.949C1493.13 368.93 1487.41 364.613 1480.99 360.998C1476.72 358.577 1472.3 356.59 1467.73 355.036C1459.56 352.254 1450.91 350.863 1441.78 350.863C1433.72 350.863 1425.99 351.981 1418.6 354.217C1412.79 355.974 1407.2 358.422 1401.81 361.561C1395.95 364.963 1390.66 368.955 1385.95 373.537C1380.85 378.497 1376.42 384.147 1372.67 390.489C1369.62 395.674 1367.21 401.063 1365.46 406.655C1363.09 414.22 1361.9 422.157 1361.9 430.466C1361.9 438.58 1363.04 446.344 1365.31 453.758C1367.08 459.507 1369.53 465.046 1372.67 470.374C1376.38 476.637 1380.74 482.232 1385.76 487.16C1390.52 491.83 1395.87 495.901 1401.81 499.372C1407.11 502.442 1412.62 504.846 1418.33 506.586C1425.8 508.862 1433.62 510 1441.78 510C1450.27 510 1458.35 508.755 1466.03 506.266C1471.05 504.64 1475.89 502.483 1480.57 499.794C1489.18 494.804 1496.63 488.781 1502.93 481.725C1505.28 479.094 1507.47 476.319 1509.49 473.401L1509.49 472.978C1509.49 472.884 1509.4 472.744 1509.21 472.556L1480.42 452.637C1480.28 452.495 1480.15 452.434 1480.04 452.453C1480 452.459 1479.97 452.474 1479.93 452.497Z" fill-rule="evenodd" />
|
||||
<path id="koring15" d="M1578.64 506.692C1578.73 506.598 1578.78 506.457 1578.78 506.27L1578.78 433.634C1578.78 421.903 1580.3 412.589 1583.35 405.691C1586.45 398.747 1590.58 393.844 1595.74 390.981C1600.95 388.072 1607.02 386.617 1613.97 386.617C1615.43 386.617 1616.88 386.73 1618.31 386.954C1621.55 387.462 1624.7 388.547 1627.76 390.207C1628.03 390.351 1628.3 390.5 1628.56 390.654C1632.66 393.057 1636.05 396.662 1638.74 401.468C1641.61 406.536 1643.04 413.011 1643.04 420.894L1643.04 506.27C1643.04 506.457 1643.08 506.598 1643.18 506.692C1643.21 506.711 1643.24 506.728 1643.27 506.743C1643.39 506.803 1643.52 506.833 1643.67 506.833L1679.43 506.833C1679.61 506.833 1679.75 506.786 1679.85 506.692C1679.94 506.598 1679.99 506.457 1679.99 506.27L1679.99 420.894C1679.99 406.677 1677.34 394.242 1672.04 383.591C1666.73 372.94 1659.81 364.822 1651.27 359.238C1645.97 355.771 1640.42 353.38 1634.63 352.066C1631.09 351.264 1627.47 350.863 1623.75 350.863C1617.28 350.863 1611.07 351.738 1605.11 353.488C1596.9 355.901 1589.18 359.976 1581.94 365.714C1580.87 366.567 1579.81 367.445 1578.78 368.349L1578.78 288.855C1578.78 288.667 1578.73 288.526 1578.64 288.432C1578.54 288.338 1578.4 288.292 1578.21 288.292L1542.46 288.292C1542.27 288.292 1542.13 288.338 1542.04 288.432C1541.94 288.526 1541.9 288.667 1541.9 288.855L1541.9 506.27C1541.9 506.457 1541.94 506.598 1542.04 506.692C1542.13 506.786 1542.27 506.833 1542.46 506.833L1578.21 506.833C1578.4 506.833 1578.54 506.786 1578.64 506.692Z" fill-rule="evenodd" />
|
||||
<path id="koring16" d="M1874.78 439.828C1874.87 439.734 1874.92 439.593 1874.92 439.405C1875.29 420.167 1872.01 403.885 1865.06 390.559C1859.21 379.203 1851.64 370.317 1842.37 363.901C1840.72 362.762 1839.02 361.7 1837.26 360.716C1833.16 358.394 1828.93 356.482 1824.55 354.98C1816.54 352.235 1808.06 350.863 1799.11 350.863C1785.88 350.863 1773.83 353.395 1762.95 358.46C1761.18 359.285 1759.44 360.178 1757.73 361.139C1751.2 364.779 1745.44 369.164 1740.44 374.295C1736.1 378.755 1732.34 383.777 1729.15 389.362C1728.12 391.186 1727.17 393.046 1726.29 394.941C1721.39 405.535 1718.95 417.259 1718.95 430.115C1718.95 442.923 1721.36 454.633 1726.19 465.243C1727.09 467.227 1728.08 469.171 1729.15 471.078C1731.96 476.039 1735.24 480.567 1738.99 484.661C1744.4 490.557 1750.78 495.555 1758.15 499.654C1770.68 506.551 1785.3 510 1802 510C1804.68 510 1807.34 509.861 1809.99 509.582C1819.87 508.542 1829.55 505.561 1839.02 500.639C1844.64 497.72 1849.83 494.283 1854.58 490.328C1860 485.826 1864.85 480.653 1869.15 474.808L1869.15 474.386C1869.15 474.292 1869.05 474.151 1868.86 473.964L1840.64 454.678L1840.22 454.678C1840.12 454.678 1839.96 454.772 1839.73 454.96C1834.56 462.045 1828.82 467.465 1822.48 471.219C1819.18 473.175 1815.75 474.621 1812.18 475.558C1808.9 476.419 1805.51 476.849 1802 476.849C1791.25 476.849 1782.32 475.066 1775.18 471.5C1771.04 469.429 1767.52 466.797 1764.61 463.603C1762.51 461.296 1760.73 458.697 1759.28 455.805C1757.02 451.252 1755.5 445.973 1754.73 439.968L1874.35 439.968C1874.54 439.968 1874.68 439.921 1874.78 439.828ZM1835.85 407.803C1836.16 408.513 1836.45 409.241 1836.73 409.985L1757.23 409.985C1757.83 408.294 1758.51 406.675 1759.28 405.128C1761.32 401.038 1763.91 397.533 1767.05 394.615C1769.24 392.579 1771.69 390.828 1774.41 389.362C1781.07 385.796 1789.31 384.013 1799.11 384.013C1807.41 384.013 1814.65 385.826 1820.82 389.451C1820.93 389.515 1821.04 389.579 1821.14 389.644C1827.39 393.398 1832.29 399.451 1835.85 407.803Z" fill-rule="evenodd" />
|
||||
<path id="koring17" d="M1914.51 459.535L1914.51 354.593C1914.51 354.405 1914.56 354.264 1914.65 354.171C1914.75 354.077 1914.89 354.03 1915.08 354.03L1950.83 354.03C1951.02 354.03 1951.16 354.077 1951.25 354.171C1951.35 354.264 1951.39 354.405 1951.39 354.593L1951.39 367.906C1952.34 367.108 1953.3 366.331 1954.28 365.573C1959.61 361.448 1965.28 358.19 1971.3 355.8C1979.59 352.509 1988.53 350.863 1998.13 350.863C1998.32 350.863 1998.46 350.909 1998.55 351.003C1998.65 351.097 1998.69 351.238 1998.69 351.426L1998.69 386.054C1998.69 386.242 1998.65 386.383 1998.55 386.477C1998.46 386.571 1998.32 386.617 1998.13 386.617C1987.15 386.617 1978.23 388.119 1971.38 391.122C1968.31 392.456 1965.6 394.186 1963.25 396.312C1960.39 398.899 1958.05 402.072 1956.25 405.832C1953.01 412.636 1951.39 421.903 1951.39 433.634L1951.39 506.27C1951.39 506.457 1951.35 506.598 1951.25 506.692C1951.16 506.786 1951.02 506.833 1950.83 506.833L1915.08 506.833C1914.89 506.833 1914.75 506.786 1914.65 506.692C1914.56 506.598 1914.51 506.457 1914.51 506.27L1914.51 459.535Z" fill-rule="evenodd" />
|
||||
</g>
|
||||
<path id="logo(边框)" d="M255 0.320312C395.833 0.320312 510 114.488 510 255.32C510 396.153 395.833 510.32 255 510.32C114.167 510.32 0 396.153 0 255.32C0 114.488 114.167 0.320312 255 0.320312ZM255 107.064C243.063 107.064 231.451 108.422 220.167 111.136L220.166 111.136C212.397 113.005 204.784 115.516 197.325 118.671C189.403 122.022 181.881 125.994 174.76 130.587L174.759 130.587C165.949 136.27 157.751 142.904 150.167 150.488C142.583 158.072 135.95 166.269 130.268 175.079C125.674 182.2 121.702 189.723 118.351 197.645C115.196 205.105 112.684 212.719 110.816 220.487L110.815 220.488C108.101 231.772 106.744 243.383 106.744 255.32C106.744 267.258 108.101 278.869 110.816 290.154L110.816 290.155C112.684 297.923 115.196 305.537 118.351 312.995C121.702 320.918 125.674 328.44 130.267 335.561L130.268 335.562C135.95 344.372 142.583 352.569 150.167 360.153C157.751 367.737 165.949 374.37 174.759 380.053C181.88 384.646 189.402 388.618 197.325 391.969C204.784 395.124 212.398 397.636 220.167 399.505C231.451 402.219 243.062 403.576 255 403.576C266.938 403.576 278.549 402.219 289.833 399.505C297.602 397.636 305.216 395.124 312.675 391.969C320.598 388.618 328.12 384.646 335.242 380.052C344.052 374.37 352.249 367.737 359.833 360.153C367.416 352.569 374.049 344.372 379.732 335.562C384.326 328.441 388.298 320.918 391.649 312.995C394.804 305.536 397.316 297.922 399.184 290.154C401.899 278.869 403.256 267.258 403.256 255.32C403.256 243.383 401.899 231.772 399.184 220.487C397.316 212.718 394.804 205.104 391.649 197.645C388.298 189.723 384.326 182.201 379.733 175.08C374.051 166.269 367.417 158.072 359.833 150.488C352.249 142.904 344.052 136.271 335.243 130.589L335.242 130.588C328.12 125.995 320.598 122.022 312.675 118.671C305.216 115.516 297.602 113.004 289.833 111.136L289.832 111.136C278.548 108.422 266.937 107.064 255 107.064Z" fill-rule="evenodd" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 365 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
|
||||
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "koring-sidecar",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"koring-sidecar": "dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsx src/index.ts",
|
||||
"build": "tsc",
|
||||
"compile": "bun build --compile --target=bun-windows-arm64 src/index.ts --outfile bin/koring-sidecar-aarch64-pc-windows-msvc.exe",
|
||||
"compile:x64": "bun build --compile --target=bun-windows-x64 src/index.ts --outfile bin/koring-sidecar-x86_64-pc-windows-msvc.exe"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmcl/core": "^2.15.1",
|
||||
"@xmcl/installer": "^6.1.2",
|
||||
"@xmcl/user": "^4.3.0",
|
||||
"@xmcl/modrinth": "^2.5.0",
|
||||
"@xmcl/curseforge": "^2.2.0",
|
||||
"@xmcl/nbt": "^3.0.2",
|
||||
"@xmcl/system": "^2.2.9",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "~5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,718 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@xmcl/core':
|
||||
specifier: ^2.15.1
|
||||
version: 2.15.1(yauzl@2.10.0)
|
||||
'@xmcl/curseforge':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
'@xmcl/installer':
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2
|
||||
'@xmcl/modrinth':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
'@xmcl/nbt':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
'@xmcl/system':
|
||||
specifier: ^2.2.9
|
||||
version: 2.2.9
|
||||
'@xmcl/user':
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.1
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.21
|
||||
'@types/uuid':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
tsx:
|
||||
specifier: ^4.19.0
|
||||
version: 4.22.4
|
||||
typescript:
|
||||
specifier: ~5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.28.1':
|
||||
resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.28.1':
|
||||
resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.28.1':
|
||||
resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.28.1':
|
||||
resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.28.1':
|
||||
resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.28.1':
|
||||
resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.28.1':
|
||||
resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.28.1':
|
||||
resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.28.1':
|
||||
resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.28.1':
|
||||
resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.28.1':
|
||||
resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.28.1':
|
||||
resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.28.1':
|
||||
resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.28.1':
|
||||
resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.28.1':
|
||||
resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.28.1':
|
||||
resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.28.1':
|
||||
resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.28.1':
|
||||
resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/http-cache-semantics@4.2.0':
|
||||
resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==}
|
||||
|
||||
'@types/node@22.19.21':
|
||||
resolution: {integrity: sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==}
|
||||
|
||||
'@types/uuid@10.0.0':
|
||||
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
'@xmcl/asm@1.0.1':
|
||||
resolution: {integrity: sha512-7vCVgm1E1IZ2cujiitFk9550Vgu2XAOn1ff90di638fMmTK0XkFMXKsSR/nGZmYKt+XiTMI/0B3TvreqbVjOug==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
'@xmcl/bytebuffer@0.1.1':
|
||||
resolution: {integrity: sha512-Mja7XzIjXgXX/mDNqkVO6X3yltIYo18RYTo3J4UA8Di2TMZp90NRbLJ8PFRWAyDo/R+kNyErvfXMge2eB/q9pg==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
'@xmcl/core@2.15.1':
|
||||
resolution: {integrity: sha512-ldVWtFGRTnQ836oRnex3YiwCogQmy2XdKfdYz9uAoEbXofMrH/Yq/uEK593iQ9iVJa8Rlfik+LjzGAfsYzR1SQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@xmcl/curseforge@2.2.0':
|
||||
resolution: {integrity: sha512-W1b92Z1IYItIyplq2poWP1NL47buE4LOHWEU+duHCj0xWHI5y4ya2pIyXQhNzsBCSJ+ZZBXzPx/27hBl2PUwcA==}
|
||||
engines: {node: '>=16.4'}
|
||||
|
||||
'@xmcl/file-transfer@2.0.3':
|
||||
resolution: {integrity: sha512-IzS1EsmirFF7fHQyJ3Otpu8W7l1vD4qzAlJtFDpkCrMRhfG99smgTiprhlfPzK8XklPe3cq8qKoiEO3v11VI9w==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@xmcl/forge-site-parser@2.0.9':
|
||||
resolution: {integrity: sha512-OHKG2KYE+F6TSeOQmymuGoqEifxbJb3w3X/hmxMNeqtewiYukJldPmKO559ZFnZnOuMQEnr+X0dMbTQwWs5dFg==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
'@xmcl/installer@6.1.2':
|
||||
resolution: {integrity: sha512-q0meO1I4oyL0jCd8mfRD8D92ODgTbg+sQvkfilWwG1115EBd1KNBzqFRKYXzkmEGwjrcCdhbA5Q4ECpJ87Ro0Q==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@xmcl/modrinth@2.5.0':
|
||||
resolution: {integrity: sha512-OpPpBemZ/yYvphgLVXGV+jESR9n8OceEsMaMvxuFZrIpi9LdYTa2oyVlnZHD7Dte+S+ttD8Y2AUncSJUNFRgNg==}
|
||||
engines: {node: '>=16.4'}
|
||||
|
||||
'@xmcl/nbt@3.0.2':
|
||||
resolution: {integrity: sha512-RLv3bkre7wKHZzzaHYn3VUEa4B2HT1wYiP6gqaoxws/4CYT4IUGFO5YJAU/1IKVTUd7wMNQDXorJ9U/LGERPgQ==}
|
||||
engines: {node: '>=16.4'}
|
||||
|
||||
'@xmcl/system@2.2.9':
|
||||
resolution: {integrity: sha512-dY9G3SB0G9XYDT6ie/6sdYTMCuSlfTWUDkNOwfrYE8LIh3Cii2u+IcQ8bAT9XAxIcSG6qoe8KP0WOUC/Vn5HjA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@xmcl/task@4.1.1':
|
||||
resolution: {integrity: sha512-UdTf37uBG26hx3UW8oDM5TFTodV0CMTgUKOQu5XGMc2iVEKXuC5rUgVMf6Av7aDAxbgb5LedK/5Ik1lDP9CRRA==}
|
||||
|
||||
'@xmcl/unzip@2.1.2':
|
||||
resolution: {integrity: sha512-Lm/eg/e0/p+sfj/RT2QDpsBAf39DZqQ3+XvX1JXZPb64wnjwOf8CGU1WPv6BseEcJ5CMOpm0s2NyrEQD04y0UQ==}
|
||||
engines: {node: '>=16'}
|
||||
peerDependencies:
|
||||
yauzl: ^2.10.0
|
||||
|
||||
'@xmcl/user@4.3.0':
|
||||
resolution: {integrity: sha512-SHUI86qipZ+Eva1bO2xyKZFelQ8vO/e7Gatu6lEIPc8pkfGRfuAQuC+w+NmxYFUk3uNeDEKzkq6lP+DNJqUcEQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
css-select@5.2.2:
|
||||
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
|
||||
|
||||
css-what@6.2.2:
|
||||
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
esbuild@0.28.1:
|
||||
resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fd-slicer@1.1.0:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
http-cache-semantics@4.2.0:
|
||||
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
||||
|
||||
immediate@3.0.6:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
|
||||
jszip@3.10.1:
|
||||
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||
|
||||
lie@3.3.0:
|
||||
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
||||
|
||||
node-html-parser@6.1.13:
|
||||
resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==}
|
||||
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
pako@2.1.0:
|
||||
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
|
||||
setimmediate@1.0.5:
|
||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
|
||||
tsx@4.22.4:
|
||||
resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@7.1.1:
|
||||
resolution: {integrity: sha512-WZkQ6eH9f5ZT93gaIffsbUaDpBwjbpvmMbfaEhOnbdUneurTESeRxwPGwjI28mRFESH3W3e8Togijh37ptOQqA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
undici@7.2.3:
|
||||
resolution: {integrity: sha512-2oSLHaDalSt2/O/wHA9M+/ZPAOcU2yrSP/cdBYJ+YxZskiPYDSqHbysLSlD7gq3JMqOoJI5O31RVU3BxX/MnAA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
uuid@11.1.1:
|
||||
resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==}
|
||||
hasBin: true
|
||||
|
||||
uuid@9.0.1:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
hasBin: true
|
||||
|
||||
yauzl@2.10.0:
|
||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||
|
||||
yazl@2.5.1:
|
||||
resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@types/http-cache-semantics@4.2.0': {}
|
||||
|
||||
'@types/node@22.19.21':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/uuid@10.0.0': {}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 22.19.21
|
||||
|
||||
'@xmcl/asm@1.0.1': {}
|
||||
|
||||
'@xmcl/bytebuffer@0.1.1': {}
|
||||
|
||||
'@xmcl/core@2.15.1(yauzl@2.10.0)':
|
||||
dependencies:
|
||||
'@xmcl/unzip': 2.1.2(yauzl@2.10.0)
|
||||
transitivePeerDependencies:
|
||||
- yauzl
|
||||
|
||||
'@xmcl/curseforge@2.2.0':
|
||||
dependencies:
|
||||
undici: 7.1.1
|
||||
|
||||
'@xmcl/file-transfer@2.0.3':
|
||||
dependencies:
|
||||
'@types/http-cache-semantics': 4.2.0
|
||||
http-cache-semantics: 4.2.0
|
||||
undici: 7.2.3
|
||||
|
||||
'@xmcl/forge-site-parser@2.0.9':
|
||||
dependencies:
|
||||
node-html-parser: 6.1.13
|
||||
|
||||
'@xmcl/installer@6.1.2':
|
||||
dependencies:
|
||||
'@xmcl/asm': 1.0.1
|
||||
'@xmcl/core': 2.15.1(yauzl@2.10.0)
|
||||
'@xmcl/file-transfer': 2.0.3
|
||||
'@xmcl/forge-site-parser': 2.0.9
|
||||
'@xmcl/task': 4.1.1
|
||||
'@xmcl/unzip': 2.1.2(yauzl@2.10.0)
|
||||
undici: 7.2.3
|
||||
yauzl: 2.10.0
|
||||
yazl: 2.5.1
|
||||
|
||||
'@xmcl/modrinth@2.5.0':
|
||||
dependencies:
|
||||
undici: 7.2.3
|
||||
|
||||
'@xmcl/nbt@3.0.2':
|
||||
dependencies:
|
||||
'@xmcl/bytebuffer': 0.1.1
|
||||
pako: 2.1.0
|
||||
|
||||
'@xmcl/system@2.2.9':
|
||||
dependencies:
|
||||
'@xmcl/unzip': 2.1.2(yauzl@2.10.0)
|
||||
jszip: 3.10.1
|
||||
yauzl: 2.10.0
|
||||
|
||||
'@xmcl/task@4.1.1': {}
|
||||
|
||||
'@xmcl/unzip@2.1.2(yauzl@2.10.0)':
|
||||
dependencies:
|
||||
'@types/yauzl': 2.10.3
|
||||
yauzl: 2.10.0
|
||||
|
||||
'@xmcl/user@4.3.0':
|
||||
dependencies:
|
||||
undici: 7.2.3
|
||||
uuid: 9.0.1
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
buffer-crc32@0.2.13: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
css-select@5.2.2:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.2.2
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
nth-check: 2.1.1
|
||||
|
||||
css-what@6.2.2: {}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
|
||||
domhandler@5.0.3:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domutils@3.2.2:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
esbuild@0.28.1:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.28.1
|
||||
'@esbuild/android-arm': 0.28.1
|
||||
'@esbuild/android-arm64': 0.28.1
|
||||
'@esbuild/android-x64': 0.28.1
|
||||
'@esbuild/darwin-arm64': 0.28.1
|
||||
'@esbuild/darwin-x64': 0.28.1
|
||||
'@esbuild/freebsd-arm64': 0.28.1
|
||||
'@esbuild/freebsd-x64': 0.28.1
|
||||
'@esbuild/linux-arm': 0.28.1
|
||||
'@esbuild/linux-arm64': 0.28.1
|
||||
'@esbuild/linux-ia32': 0.28.1
|
||||
'@esbuild/linux-loong64': 0.28.1
|
||||
'@esbuild/linux-mips64el': 0.28.1
|
||||
'@esbuild/linux-ppc64': 0.28.1
|
||||
'@esbuild/linux-riscv64': 0.28.1
|
||||
'@esbuild/linux-s390x': 0.28.1
|
||||
'@esbuild/linux-x64': 0.28.1
|
||||
'@esbuild/netbsd-arm64': 0.28.1
|
||||
'@esbuild/netbsd-x64': 0.28.1
|
||||
'@esbuild/openbsd-arm64': 0.28.1
|
||||
'@esbuild/openbsd-x64': 0.28.1
|
||||
'@esbuild/openharmony-arm64': 0.28.1
|
||||
'@esbuild/sunos-x64': 0.28.1
|
||||
'@esbuild/win32-arm64': 0.28.1
|
||||
'@esbuild/win32-ia32': 0.28.1
|
||||
'@esbuild/win32-x64': 0.28.1
|
||||
|
||||
fd-slicer@1.1.0:
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
http-cache-semantics@4.2.0: {}
|
||||
|
||||
immediate@3.0.6: {}
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
isarray@1.0.0: {}
|
||||
|
||||
jszip@3.10.1:
|
||||
dependencies:
|
||||
lie: 3.3.0
|
||||
pako: 1.0.11
|
||||
readable-stream: 2.3.8
|
||||
setimmediate: 1.0.5
|
||||
|
||||
lie@3.3.0:
|
||||
dependencies:
|
||||
immediate: 3.0.6
|
||||
|
||||
node-html-parser@6.1.13:
|
||||
dependencies:
|
||||
css-select: 5.2.2
|
||||
he: 1.2.0
|
||||
|
||||
nth-check@2.1.1:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
|
||||
pako@1.0.11: {}
|
||||
|
||||
pako@2.1.0: {}
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
core-util-is: 1.0.3
|
||||
inherits: 2.0.4
|
||||
isarray: 1.0.0
|
||||
process-nextick-args: 2.0.1
|
||||
safe-buffer: 5.1.2
|
||||
string_decoder: 1.1.1
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
safe-buffer@5.1.2: {}
|
||||
|
||||
setimmediate@1.0.5: {}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
|
||||
tsx@4.22.4:
|
||||
dependencies:
|
||||
esbuild: 0.28.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
typescript@5.8.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@7.1.1: {}
|
||||
|
||||
undici@7.2.3: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
uuid@11.1.1: {}
|
||||
|
||||
uuid@9.0.1: {}
|
||||
|
||||
yauzl@2.10.0:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
fd-slicer: 1.1.0
|
||||
|
||||
yazl@2.5.1:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
@@ -0,0 +1,146 @@
|
||||
import { MicrosoftAuthenticator, offline } from "@xmcl/user";
|
||||
|
||||
export interface AuthResult {
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken: string;
|
||||
expiresAt?: number;
|
||||
xboxProfile?: {
|
||||
gamertag: string;
|
||||
displayPicRaw: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function microsoftLoginStart(clientId: string, redirectUri?: string) {
|
||||
const state = Math.random().toString(36).substring(2, 15);
|
||||
const scope = "XboxLive.signin offline_access";
|
||||
const redirect = redirectUri || "https://login.live.com/oauth20_desktop.srf";
|
||||
|
||||
const authUrl = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?` +
|
||||
`client_id=${clientId}` +
|
||||
`&response_type=code` +
|
||||
`&scope=${encodeURIComponent(scope)}` +
|
||||
`&state=${state}` +
|
||||
`&redirect_uri=${encodeURIComponent(redirect)}`;
|
||||
|
||||
return { state, authUrl };
|
||||
}
|
||||
|
||||
export async function microsoftLoginCallback(
|
||||
code: string,
|
||||
clientId: string,
|
||||
redirectUri?: string
|
||||
): Promise<AuthResult> {
|
||||
const redirect = redirectUri || "https://login.live.com/oauth20_desktop.srf";
|
||||
|
||||
// Step 1: Exchange code for MS access token
|
||||
const tokenResponse = await fetch(
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
client_id: clientId,
|
||||
code,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: redirect,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
const err = await tokenResponse.text();
|
||||
throw new Error(`MS token exchange failed: ${err}`);
|
||||
}
|
||||
|
||||
const tokenData = await tokenResponse.json() as { access_token: string };
|
||||
const msAccessToken = tokenData.access_token;
|
||||
|
||||
// Step 2+3: Xbox + Minecraft auth
|
||||
const authenticator = new MicrosoftAuthenticator({ fetch });
|
||||
const { liveXstsResponse, minecraftXstsResponse } =
|
||||
await authenticator.acquireXBoxToken(msAccessToken);
|
||||
|
||||
const mcResponse = await authenticator.loginMinecraftWithXBox(
|
||||
minecraftXstsResponse.DisplayClaims.xui[0].uhs,
|
||||
minecraftXstsResponse.Token
|
||||
);
|
||||
|
||||
// Get Xbox profile
|
||||
let xboxProfile;
|
||||
try {
|
||||
const profile = await authenticator.getXboxGameProfile(
|
||||
liveXstsResponse.DisplayClaims.xui[0].xid,
|
||||
liveXstsResponse.DisplayClaims.xui[0].uhs,
|
||||
liveXstsResponse.Token
|
||||
);
|
||||
const settings = profile.profileUsers[0]?.settings;
|
||||
if (settings) {
|
||||
xboxProfile = {
|
||||
gamertag: settings.find(s => s.id === "Gamertag")?.value || "",
|
||||
displayPicRaw: settings.find(s => s.id === "PublicGamerpic")?.value || "",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Xbox profile is optional
|
||||
}
|
||||
|
||||
return {
|
||||
username: mcResponse.username,
|
||||
uuid: mcResponse.username,
|
||||
accessToken: mcResponse.access_token,
|
||||
expiresAt: mcResponse.expires_in
|
||||
? Date.now() + mcResponse.expires_in * 1000
|
||||
: undefined,
|
||||
xboxProfile,
|
||||
};
|
||||
}
|
||||
|
||||
export function offlineLogin(username: string, uuid?: string): AuthResult {
|
||||
const user = offline(username, uuid);
|
||||
return {
|
||||
username: user.selectedProfile.name,
|
||||
uuid: user.selectedProfile.id,
|
||||
accessToken: user.accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
export async function validateMinecraftToken(accessToken: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://api.minecraftservices.com/minecraft/profile",
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshMicrosoftToken(
|
||||
refreshToken: string,
|
||||
clientId: string
|
||||
) {
|
||||
const response = await fetch(
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
client_id: clientId,
|
||||
refresh_token: refreshToken,
|
||||
grant_type: "refresh_token",
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to refresh MS token");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
getVersionList,
|
||||
install,
|
||||
installForge,
|
||||
installFabric,
|
||||
installDependencies,
|
||||
getForgeVersionList,
|
||||
getFabricLoaders,
|
||||
getQuiltLoaders,
|
||||
installQuiltVersion,
|
||||
} from "@xmcl/installer";
|
||||
import { sendProgress } from "../protocol/transport.js";
|
||||
|
||||
function progressCallback(requestId: string) {
|
||||
return (total: number, cur: number, message?: string) => {
|
||||
sendProgress(requestId, {
|
||||
stage: message || "downloading",
|
||||
current: cur,
|
||||
total,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export async function getVersionListAsync(type: string = "all") {
|
||||
const manifest = await getVersionList();
|
||||
if (type === "all") return manifest;
|
||||
const filtered = manifest.versions.filter((v) => v.type === type);
|
||||
return { latest: manifest.latest, versions: filtered };
|
||||
}
|
||||
|
||||
export async function installMinecraft(
|
||||
requestId: string,
|
||||
version: string,
|
||||
gamePath: string,
|
||||
javaPath?: string,
|
||||
_downloadThreads?: number
|
||||
) {
|
||||
await install({ id: version, url: "" }, gamePath, {
|
||||
java: javaPath,
|
||||
progress: progressCallback(requestId),
|
||||
} as any);
|
||||
|
||||
return { version, gamePath };
|
||||
}
|
||||
|
||||
export async function installForgeLoader(
|
||||
requestId: string,
|
||||
mcVersion: string,
|
||||
gamePath: string,
|
||||
javaPath?: string,
|
||||
forgeVersion?: string
|
||||
) {
|
||||
const version = forgeVersion || mcVersion;
|
||||
|
||||
await installForge({
|
||||
mcversion: mcVersion,
|
||||
version: version,
|
||||
}, gamePath, {
|
||||
java: javaPath,
|
||||
progress: progressCallback(requestId),
|
||||
} as any);
|
||||
|
||||
return { loaderType: "forge", mcVersion, forgeVersion: version };
|
||||
}
|
||||
|
||||
export async function installFabricLoader(
|
||||
requestId: string,
|
||||
mcVersion: string,
|
||||
gamePath: string,
|
||||
loaderVersion?: string
|
||||
) {
|
||||
const loaders = await getFabricLoaders();
|
||||
const targetLoader = loaderVersion
|
||||
? loaders.find((l) => l.version === loaderVersion)
|
||||
: loaders[loaders.length - 1];
|
||||
|
||||
if (!targetLoader) {
|
||||
throw new Error(`Fabric loader not found: ${loaderVersion}`);
|
||||
}
|
||||
|
||||
await installFabric({
|
||||
mcVersion,
|
||||
loaderVersion: targetLoader.version,
|
||||
gamePath,
|
||||
progress: progressCallback(requestId),
|
||||
} as any);
|
||||
|
||||
return { loaderType: "fabric", mcVersion, loaderVersion: targetLoader.version };
|
||||
}
|
||||
|
||||
export async function installQuiltLoader(
|
||||
requestId: string,
|
||||
mcVersion: string,
|
||||
gamePath: string,
|
||||
loaderVersion?: string
|
||||
) {
|
||||
const loaders = await getQuiltLoaders();
|
||||
const targetLoader = loaderVersion
|
||||
? loaders.find((l) => l.version === loaderVersion)
|
||||
: loaders[loaders.length - 1];
|
||||
|
||||
if (!targetLoader) {
|
||||
throw new Error(`Quilt loader not found: ${loaderVersion}`);
|
||||
}
|
||||
|
||||
await installQuiltVersion({
|
||||
minecraftVersion: mcVersion,
|
||||
version: targetLoader.version,
|
||||
minecraft: gamePath,
|
||||
});
|
||||
|
||||
return { loaderType: "quilt", mcVersion, loaderVersion: targetLoader.version };
|
||||
}
|
||||
|
||||
export async function getForgeVersions(mcVersion?: string) {
|
||||
const list = await getForgeVersionList();
|
||||
if (mcVersion) {
|
||||
const versions = list.versions.filter((v) => v.mcversion === mcVersion);
|
||||
return { mcVersion, versions };
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export async function getFabricVersions(mcVersion?: string) {
|
||||
const loaders = await getFabricLoaders();
|
||||
if (mcVersion) {
|
||||
return { mcVersion, loaders };
|
||||
}
|
||||
return { loaders };
|
||||
}
|
||||
|
||||
export async function ensureDependencies(
|
||||
requestId: string,
|
||||
gamePath: string,
|
||||
versionId: string
|
||||
) {
|
||||
const { Version } = await import("@xmcl/core");
|
||||
const resolved = await Version.parse(gamePath, versionId);
|
||||
await installDependencies(resolved, {
|
||||
progress: progressCallback(requestId),
|
||||
} as any);
|
||||
return { version: versionId };
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
launch as xmclLaunch,
|
||||
diagnose as xmclDiagnose,
|
||||
createMinecraftProcessWatcher,
|
||||
Version,
|
||||
type LaunchOption,
|
||||
} from "@xmcl/core";
|
||||
import { sendEvent } from "../protocol/transport.js";
|
||||
|
||||
export async function launchMinecraft(requestId: string, options: {
|
||||
gamePath: string;
|
||||
javaPath: string;
|
||||
version: string;
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
jvmArgs?: string[];
|
||||
gameArgs?: string[];
|
||||
server?: { ip: string; port?: number };
|
||||
detached?: boolean;
|
||||
}) {
|
||||
const launchOption: LaunchOption = {
|
||||
gamePath: options.gamePath,
|
||||
javaPath: options.javaPath,
|
||||
version: options.version,
|
||||
gameProfile: {
|
||||
name: options.username,
|
||||
id: options.uuid,
|
||||
},
|
||||
accessToken: options.accessToken || "",
|
||||
extraExecOption: options.detached ? { detached: true } : undefined,
|
||||
minMemory: options.memory?.min ? parseInt(options.memory.min) : undefined,
|
||||
maxMemory: options.memory?.max ? parseInt(options.memory.max) : undefined,
|
||||
extraJVMArgs: options.jvmArgs,
|
||||
extraMCArgs: options.gameArgs,
|
||||
server: options.server,
|
||||
};
|
||||
|
||||
const proc = await xmclLaunch(launchOption);
|
||||
|
||||
const watcher = createMinecraftProcessWatcher(proc);
|
||||
|
||||
watcher.on("error", (error: any) => {
|
||||
sendEvent(requestId, { event: "error", message: error.message || String(error) });
|
||||
});
|
||||
|
||||
watcher.on("minecraft-exit", (event) => {
|
||||
sendEvent(requestId, { event: "exit", code: event.code, signal: event.signal });
|
||||
});
|
||||
|
||||
watcher.on("minecraft-window-ready", () => {
|
||||
sendEvent(requestId, { event: "window-ready" });
|
||||
});
|
||||
|
||||
proc.on("error", (err) => {
|
||||
sendEvent(requestId, { event: "process-error", message: err.message });
|
||||
});
|
||||
|
||||
return {
|
||||
pid: proc.pid,
|
||||
version: options.version,
|
||||
username: options.username,
|
||||
};
|
||||
}
|
||||
|
||||
export async function diagnoseVersion(gamePath: string, versionId: string) {
|
||||
const report = await xmclDiagnose(versionId, gamePath);
|
||||
return report;
|
||||
}
|
||||
|
||||
export async function parseVersion(gamePath: string, versionId: string) {
|
||||
const resolved = await Version.parse(gamePath, versionId);
|
||||
return {
|
||||
id: resolved.id,
|
||||
type: resolved.type,
|
||||
mainClass: resolved.mainClass,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import { ModrinthV2Client, type SearchResultHit, type ProjectVersion, type Project } from "@xmcl/modrinth";
|
||||
import { CurseforgeV1Client } from "@xmcl/curseforge";
|
||||
import { sendProgress } from "../protocol/transport.js";
|
||||
|
||||
const modrinth = new ModrinthV2Client();
|
||||
const curseforge = new CurseforgeV1Client("");
|
||||
|
||||
export interface ModSearchResult {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
downloads: number;
|
||||
iconUrl?: string;
|
||||
categories?: string[];
|
||||
versions?: string[];
|
||||
loaders?: string[];
|
||||
source: "modrinth" | "curseforge";
|
||||
}
|
||||
|
||||
export interface ModVersionResult {
|
||||
id: string;
|
||||
name: string;
|
||||
versionNumber: string;
|
||||
gameVersions: string[];
|
||||
loaders: string[];
|
||||
files: {
|
||||
filename: string;
|
||||
url: string;
|
||||
size: number;
|
||||
primary: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function searchMods(
|
||||
query?: string,
|
||||
gameVersion?: string,
|
||||
loader?: string,
|
||||
limit: number = 20,
|
||||
offset: number = 0,
|
||||
source: "modrinth" | "curseforge" = "modrinth"
|
||||
): Promise<ModSearchResult[]> {
|
||||
if (source === "modrinth") {
|
||||
const facets: string[] = [];
|
||||
if (gameVersion) facets.push(`versions:${gameVersion}`);
|
||||
if (loader) facets.push(`categories:${loader}`);
|
||||
|
||||
const result = await modrinth.searchProjects({
|
||||
query: query || "",
|
||||
limit,
|
||||
offset,
|
||||
facets: facets.length > 0 ? JSON.stringify([facets]) : undefined,
|
||||
});
|
||||
|
||||
return result.hits.map((hit: SearchResultHit) => ({
|
||||
id: hit.project_id,
|
||||
slug: hit.slug,
|
||||
name: hit.title,
|
||||
description: hit.description,
|
||||
downloads: hit.downloads,
|
||||
iconUrl: hit.icon_url,
|
||||
categories: hit.categories,
|
||||
versions: hit.versions,
|
||||
source: "modrinth" as const,
|
||||
}));
|
||||
}
|
||||
|
||||
// CurseForge
|
||||
const result = await curseforge.searchMods({
|
||||
gameId: 432,
|
||||
classId: 6,
|
||||
searchFilter: query,
|
||||
gameVersion,
|
||||
pageSize: limit,
|
||||
index: offset,
|
||||
});
|
||||
|
||||
return result.data.map((mod) => ({
|
||||
id: String(mod.id),
|
||||
slug: mod.slug,
|
||||
name: mod.name,
|
||||
description: mod.summary,
|
||||
downloads: mod.downloadCount,
|
||||
iconUrl: mod.logo?.thumbnailUrl,
|
||||
source: "curseforge" as const,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getModDetail(
|
||||
projectId: string,
|
||||
source: "modrinth" | "curseforge"
|
||||
) {
|
||||
if (source === "modrinth") {
|
||||
const project = await modrinth.getProject(projectId);
|
||||
return {
|
||||
id: project.id,
|
||||
slug: project.slug,
|
||||
name: project.title,
|
||||
description: project.description,
|
||||
body: project.body,
|
||||
downloads: project.downloads,
|
||||
iconUrl: project.icon_url,
|
||||
categories: project.categories,
|
||||
versions: project.versions,
|
||||
source: "modrinth" as const,
|
||||
};
|
||||
}
|
||||
|
||||
const mod = await curseforge.getMod(Number(projectId));
|
||||
return {
|
||||
id: String(mod.id),
|
||||
slug: mod.slug,
|
||||
name: mod.name,
|
||||
description: mod.summary,
|
||||
body: mod.summary,
|
||||
downloads: mod.downloadCount,
|
||||
iconUrl: mod.logo?.thumbnailUrl,
|
||||
source: "curseforge" as const,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getModVersions(
|
||||
projectId: string,
|
||||
gameVersion?: string,
|
||||
loader?: string,
|
||||
source: "modrinth" | "curseforge" = "modrinth"
|
||||
): Promise<ModVersionResult[]> {
|
||||
if (source === "modrinth") {
|
||||
const versions = await modrinth.getProjectVersions(projectId, {
|
||||
loaders: loader ? [loader] : undefined,
|
||||
gameVersions: gameVersion ? [gameVersion] : undefined,
|
||||
});
|
||||
|
||||
return versions.map((v: ProjectVersion) => ({
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
versionNumber: v.version_number,
|
||||
gameVersions: v.game_versions,
|
||||
loaders: v.loaders,
|
||||
files: v.files.map((f) => ({
|
||||
filename: f.filename,
|
||||
url: f.url,
|
||||
size: f.size,
|
||||
primary: f.primary,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
const versions = await curseforge.getModFiles({
|
||||
modId: Number(projectId),
|
||||
gameVersion,
|
||||
});
|
||||
|
||||
return versions.data.map((v) => ({
|
||||
id: String(v.id),
|
||||
name: v.displayName,
|
||||
versionNumber: v.fileName,
|
||||
gameVersions: v.gameVersions,
|
||||
loaders: [],
|
||||
files: [
|
||||
{
|
||||
filename: v.fileName,
|
||||
url: v.downloadUrl || "",
|
||||
size: v.fileLength,
|
||||
primary: true,
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
export async function installMod(
|
||||
requestId: string,
|
||||
projectId: string,
|
||||
versionId: string | undefined,
|
||||
gamePath: string,
|
||||
source: "modrinth" | "curseforge"
|
||||
) {
|
||||
if (source === "modrinth") {
|
||||
const versions = await modrinth.getProjectVersions(projectId);
|
||||
const version = versionId
|
||||
? versions.find((v: ProjectVersion) => v.id === versionId)
|
||||
: versions[0];
|
||||
|
||||
if (!version) {
|
||||
throw new Error("No version found");
|
||||
}
|
||||
|
||||
const primaryFile = version.files.find((f) => f.primary) || version.files[0];
|
||||
|
||||
if (!primaryFile) {
|
||||
throw new Error("No file found in version");
|
||||
}
|
||||
|
||||
// Download the mod file
|
||||
const modsDir = `${gamePath}/mods`;
|
||||
const { ensureDir } = await import("../utils/paths.js");
|
||||
ensureDir(modsDir);
|
||||
|
||||
const response = await fetch(primaryFile.url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download mod: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
const { writeFileSync } = await import("fs");
|
||||
const { join } = await import("path");
|
||||
const filePath = join(modsDir, primaryFile.filename);
|
||||
writeFileSync(filePath, buffer);
|
||||
|
||||
sendProgress(requestId, {
|
||||
stage: "mod-installed",
|
||||
current: 1,
|
||||
total: 1,
|
||||
message: primaryFile.filename,
|
||||
});
|
||||
|
||||
return {
|
||||
projectId,
|
||||
versionId: version.id,
|
||||
filename: primaryFile.filename,
|
||||
path: filePath,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("CurseForge mod install not yet implemented");
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { sendResult, sendError } from "../protocol/transport.js";
|
||||
import type {
|
||||
MicrosoftLoginStartRequest,
|
||||
MicrosoftLoginCallbackRequest,
|
||||
OfflineLoginRequest,
|
||||
ValidateTokenRequest,
|
||||
} from "../protocol/types.js";
|
||||
import * as auth from "../core/auth.js";
|
||||
|
||||
export async function handleMicrosoftLoginStart(
|
||||
id: string,
|
||||
payload: MicrosoftLoginStartRequest
|
||||
) {
|
||||
try {
|
||||
const result = await auth.microsoftLoginStart(
|
||||
payload.client_id,
|
||||
payload.redirect_uri
|
||||
);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMicrosoftLoginCallback(
|
||||
id: string,
|
||||
payload: MicrosoftLoginCallbackRequest
|
||||
) {
|
||||
try {
|
||||
const result = await auth.microsoftLoginCallback(
|
||||
payload.code,
|
||||
payload.client_id
|
||||
);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleOfflineLogin(id: string, payload: OfflineLoginRequest) {
|
||||
try {
|
||||
const result = auth.offlineLogin(payload.username);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleValidateToken(id: string, payload: ValidateTokenRequest) {
|
||||
try {
|
||||
const valid = await auth.validateMinecraftToken(payload.accessToken);
|
||||
sendResult(id, { valid });
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { sendResult, sendError } from "../protocol/transport.js";
|
||||
import type {
|
||||
SetBackgroundImageRequest,
|
||||
SetBackgroundColorRequest,
|
||||
SetBackgroundBlurRequest,
|
||||
SetBackgroundOpacityRequest,
|
||||
SetBackgroundAnimationRequest,
|
||||
SetBackgroundThemeRequest,
|
||||
BackgroundConfig,
|
||||
} from "../protocol/types.js";
|
||||
|
||||
// In-memory background config (persisted to config file in production)
|
||||
let config: BackgroundConfig = {
|
||||
type: "color",
|
||||
color: "#1a1a2e",
|
||||
blur: 0,
|
||||
opacity: 1,
|
||||
animation: "none",
|
||||
animationSpeed: 1,
|
||||
theme: "dark",
|
||||
};
|
||||
|
||||
export async function handleSetBackgroundImage(id: string, payload: SetBackgroundImageRequest) {
|
||||
try {
|
||||
config.type = "image";
|
||||
config.image = payload.url;
|
||||
if (payload.blur !== undefined) config.blur = payload.blur;
|
||||
if (payload.opacity !== undefined) config.opacity = payload.opacity;
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSetBackgroundColor(id: string, payload: SetBackgroundColorRequest) {
|
||||
try {
|
||||
config.type = "color";
|
||||
config.color = payload.color;
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSetBackgroundBlur(id: string, payload: SetBackgroundBlurRequest) {
|
||||
try {
|
||||
config.blur = Math.max(0, Math.min(20, payload.blur));
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSetBackgroundOpacity(id: string, payload: SetBackgroundOpacityRequest) {
|
||||
try {
|
||||
config.opacity = Math.max(0, Math.min(1, payload.opacity));
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSetBackgroundAnimation(id: string, payload: SetBackgroundAnimationRequest) {
|
||||
try {
|
||||
config.animation = payload.type;
|
||||
if (payload.speed !== undefined) config.animationSpeed = payload.speed;
|
||||
if (payload.type === "gradient" || payload.type === "particles") {
|
||||
config.type = payload.type;
|
||||
}
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetBackground(id: string) {
|
||||
try {
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSetBackgroundTheme(id: string, payload: SetBackgroundThemeRequest) {
|
||||
try {
|
||||
config.theme = payload.theme;
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleResetBackground(id: string) {
|
||||
try {
|
||||
config = {
|
||||
type: "color",
|
||||
color: "#1a1a2e",
|
||||
blur: 0,
|
||||
opacity: 1,
|
||||
animation: "none",
|
||||
animationSpeed: 1,
|
||||
theme: "dark",
|
||||
};
|
||||
sendResult(id, config);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { sendProgress, sendResult, sendError } from "../protocol/transport.js";
|
||||
import type {
|
||||
InstallMinecraftRequest,
|
||||
InstallModLoaderRequest,
|
||||
GetVersionListRequest,
|
||||
GetForgeVersionListRequest,
|
||||
GetFabricVersionListRequest,
|
||||
} from "../protocol/types.js";
|
||||
import * as installer from "../core/installer.js";
|
||||
|
||||
export async function handleInstallMinecraft(id: string, payload: InstallMinecraftRequest) {
|
||||
try {
|
||||
sendProgress(id, { stage: "starting", current: 0, total: 1 });
|
||||
const result = await installer.installMinecraft(
|
||||
id,
|
||||
payload.version,
|
||||
payload.gamePath,
|
||||
payload.javaPath,
|
||||
payload.downloadThreads
|
||||
);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleInstallModLoader(id: string, payload: InstallModLoaderRequest) {
|
||||
try {
|
||||
sendProgress(id, { stage: "starting", current: 0, total: 1 });
|
||||
let result;
|
||||
|
||||
switch (payload.loaderType) {
|
||||
case "forge":
|
||||
result = await installer.installForgeLoader(
|
||||
id,
|
||||
payload.mcVersion,
|
||||
payload.gamePath,
|
||||
payload.javaPath,
|
||||
payload.loaderVersion
|
||||
);
|
||||
break;
|
||||
case "fabric":
|
||||
result = await installer.installFabricLoader(
|
||||
id,
|
||||
payload.mcVersion,
|
||||
payload.gamePath,
|
||||
payload.loaderVersion
|
||||
);
|
||||
break;
|
||||
case "quilt":
|
||||
result = await installer.installQuiltLoader(
|
||||
id,
|
||||
payload.mcVersion,
|
||||
payload.gamePath,
|
||||
payload.loaderVersion
|
||||
);
|
||||
break;
|
||||
case "neoforge":
|
||||
throw new Error("NeoForge not yet supported");
|
||||
default:
|
||||
throw new Error(`Unknown loader type: ${payload.loaderType}`);
|
||||
}
|
||||
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetVersionList(id: string, payload: GetVersionListRequest) {
|
||||
try {
|
||||
const result = await installer.getVersionListAsync(payload.type || "all");
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetForgeVersionList(id: string, payload: GetForgeVersionListRequest) {
|
||||
try {
|
||||
const result = await installer.getForgeVersions(payload.mcVersion);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetFabricVersionList(id: string, payload: GetFabricVersionListRequest) {
|
||||
try {
|
||||
const result = await installer.getFabricVersions(payload.mcVersion);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { existsSync, readdirSync, rmSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { sendResult, sendError } from "../protocol/transport.js";
|
||||
import type {
|
||||
CreateInstanceRequest,
|
||||
ListInstancesRequest,
|
||||
DeleteInstanceRequest,
|
||||
GetInstanceInfoRequest,
|
||||
} from "../protocol/types.js";
|
||||
import {
|
||||
ensureDir,
|
||||
getInstancePath,
|
||||
readInstanceConfig,
|
||||
writeInstanceConfig,
|
||||
type InstanceConfig,
|
||||
} from "../utils/paths.js";
|
||||
|
||||
export async function handleCreateInstance(id: string, payload: CreateInstanceRequest) {
|
||||
try {
|
||||
const instancePath = getInstancePath(payload.instancesPath, payload.name);
|
||||
|
||||
if (existsSync(instancePath)) {
|
||||
throw new Error(`Instance "${payload.name}" already exists`);
|
||||
}
|
||||
|
||||
ensureDir(instancePath);
|
||||
|
||||
const config: InstanceConfig = {
|
||||
name: payload.name,
|
||||
mcVersion: payload.mcVersion,
|
||||
loaderType: payload.loaderType,
|
||||
loaderVersion: payload.loaderVersion,
|
||||
javaPath: payload.javaPath,
|
||||
memory: payload.memory,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
writeInstanceConfig(instancePath, config);
|
||||
|
||||
// Create standard directories
|
||||
ensureDir(join(instancePath, "mods"));
|
||||
ensureDir(join(instancePath, "resourcepacks"));
|
||||
ensureDir(join(instancePath, "saves"));
|
||||
ensureDir(join(instancePath, "config"));
|
||||
|
||||
sendResult(id, {
|
||||
name: payload.name,
|
||||
path: instancePath,
|
||||
config,
|
||||
});
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleListInstances(id: string, payload: ListInstancesRequest) {
|
||||
try {
|
||||
const instancesPath = payload.instancesPath;
|
||||
|
||||
if (!existsSync(instancesPath)) {
|
||||
ensureDir(instancesPath);
|
||||
sendResult(id, []);
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = readdirSync(instancesPath, { withFileTypes: true });
|
||||
const instances = entries
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => {
|
||||
const config = readInstanceConfig(join(instancesPath, e.name));
|
||||
return {
|
||||
name: e.name,
|
||||
path: join(instancesPath, e.name),
|
||||
config,
|
||||
};
|
||||
})
|
||||
.filter((i) => i.config !== null);
|
||||
|
||||
sendResult(id, instances);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDeleteInstance(id: string, payload: DeleteInstanceRequest) {
|
||||
try {
|
||||
const instancePath = getInstancePath(payload.instancesPath, payload.name);
|
||||
|
||||
if (!existsSync(instancePath)) {
|
||||
throw new Error(`Instance "${payload.name}" not found`);
|
||||
}
|
||||
|
||||
rmSync(instancePath, { recursive: true, force: true });
|
||||
|
||||
sendResult(id, { deleted: payload.name });
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetInstanceInfo(id: string, payload: GetInstanceInfoRequest) {
|
||||
try {
|
||||
const instancePath = getInstancePath(payload.instancesPath, payload.name);
|
||||
|
||||
if (!existsSync(instancePath)) {
|
||||
throw new Error(`Instance "${payload.name}" not found`);
|
||||
}
|
||||
|
||||
const config = readInstanceConfig(instancePath);
|
||||
if (!config) {
|
||||
throw new Error(`Instance "${payload.name}" has no config`);
|
||||
}
|
||||
|
||||
// Count mods
|
||||
const modsPath = join(instancePath, "mods");
|
||||
let modCount = 0;
|
||||
if (existsSync(modsPath)) {
|
||||
modCount = readdirSync(modsPath).filter((f) => f.endsWith(".jar")).length;
|
||||
}
|
||||
|
||||
sendResult(id, {
|
||||
name: payload.name,
|
||||
path: instancePath,
|
||||
config,
|
||||
modCount,
|
||||
});
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { sendResult, sendError } from "../protocol/transport.js";
|
||||
import type { LaunchRequest, DiagnoseRequest } from "../protocol/types.js";
|
||||
import * as launcher from "../core/launcher.js";
|
||||
|
||||
export async function handleLaunch(id: string, payload: LaunchRequest) {
|
||||
try {
|
||||
const result = await launcher.launchMinecraft(id, payload);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDiagnose(id: string, payload: DiagnoseRequest) {
|
||||
try {
|
||||
const result = await launcher.diagnoseVersion(payload.gamePath, payload.version);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { sendResult, sendError } from "../protocol/transport.js";
|
||||
import type {
|
||||
SearchModsRequest,
|
||||
GetModDetailRequest,
|
||||
GetModVersionsRequest,
|
||||
InstallModRequest,
|
||||
} from "../protocol/types.js";
|
||||
import * as modrinth from "../core/modrinth.js";
|
||||
|
||||
export async function handleSearchMods(id: string, payload: SearchModsRequest) {
|
||||
try {
|
||||
const result = await modrinth.searchMods(
|
||||
payload.query,
|
||||
payload.gameVersion,
|
||||
payload.loader,
|
||||
payload.limit,
|
||||
payload.offset,
|
||||
payload.source
|
||||
);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetModDetail(id: string, payload: GetModDetailRequest) {
|
||||
try {
|
||||
const result = await modrinth.getModDetail(payload.projectId, payload.source);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetModVersions(id: string, payload: GetModVersionsRequest) {
|
||||
try {
|
||||
const result = await modrinth.getModVersions(
|
||||
payload.projectId,
|
||||
payload.gameVersion,
|
||||
payload.loader,
|
||||
payload.source
|
||||
);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleInstallMod(id: string, payload: InstallModRequest) {
|
||||
try {
|
||||
const result = await modrinth.installMod(
|
||||
id,
|
||||
payload.projectId,
|
||||
payload.versionId,
|
||||
payload.gamePath,
|
||||
payload.source
|
||||
);
|
||||
sendResult(id, result);
|
||||
} catch (e: any) {
|
||||
sendError(id, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { startTransport, sendError, sendEvent } from "./protocol/transport.js";
|
||||
import type { Request } from "./protocol/types.js";
|
||||
|
||||
import {
|
||||
handleInstallMinecraft,
|
||||
handleInstallModLoader,
|
||||
handleGetVersionList,
|
||||
handleGetForgeVersionList,
|
||||
handleGetFabricVersionList,
|
||||
} from "./handlers/install.js";
|
||||
|
||||
import {
|
||||
handleLaunch,
|
||||
handleDiagnose,
|
||||
} from "./handlers/launch.js";
|
||||
|
||||
import {
|
||||
handleMicrosoftLoginStart,
|
||||
handleMicrosoftLoginCallback,
|
||||
handleOfflineLogin,
|
||||
handleValidateToken,
|
||||
} from "./handlers/auth.js";
|
||||
|
||||
import {
|
||||
handleSearchMods,
|
||||
handleGetModDetail,
|
||||
handleGetModVersions,
|
||||
handleInstallMod,
|
||||
} from "./handlers/mods.js";
|
||||
|
||||
import {
|
||||
handleCreateInstance,
|
||||
handleListInstances,
|
||||
handleDeleteInstance,
|
||||
handleGetInstanceInfo,
|
||||
} from "./handlers/instance.js";
|
||||
|
||||
import {
|
||||
handleSetBackgroundImage,
|
||||
handleSetBackgroundColor,
|
||||
handleSetBackgroundBlur,
|
||||
handleSetBackgroundOpacity,
|
||||
handleSetBackgroundAnimation,
|
||||
handleGetBackground,
|
||||
handleSetBackgroundTheme,
|
||||
handleResetBackground,
|
||||
} from "./handlers/background.js";
|
||||
|
||||
const handlers: Record<string, (id: string, payload: any) => Promise<void>> = {
|
||||
// Install
|
||||
"install:minecraft": handleInstallMinecraft,
|
||||
"install:mod-loader": handleInstallModLoader,
|
||||
"install:version-list": handleGetVersionList,
|
||||
"install:forge-version-list": handleGetForgeVersionList,
|
||||
"install:fabric-version-list": handleGetFabricVersionList,
|
||||
|
||||
// Launch
|
||||
"launch:launch": handleLaunch,
|
||||
"launch:diagnose": handleDiagnose,
|
||||
|
||||
// Auth
|
||||
"auth:microsoft-login-start": handleMicrosoftLoginStart,
|
||||
"auth:microsoft-login-callback": handleMicrosoftLoginCallback,
|
||||
"auth:offline-login": handleOfflineLogin,
|
||||
"auth:validate-token": handleValidateToken,
|
||||
|
||||
// Mods
|
||||
"mods:search": handleSearchMods,
|
||||
"mods:detail": handleGetModDetail,
|
||||
"mods:versions": handleGetModVersions,
|
||||
"mods:install": handleInstallMod,
|
||||
|
||||
// Instance
|
||||
"instance:create": handleCreateInstance,
|
||||
"instance:list": handleListInstances,
|
||||
"instance:delete": handleDeleteInstance,
|
||||
"instance:info": handleGetInstanceInfo,
|
||||
|
||||
// Background
|
||||
"background:set-image": handleSetBackgroundImage,
|
||||
"background:set-color": handleSetBackgroundColor,
|
||||
"background:set-blur": handleSetBackgroundBlur,
|
||||
"background:set-opacity": handleSetBackgroundOpacity,
|
||||
"background:set-animation": handleSetBackgroundAnimation,
|
||||
"background:get": handleGetBackground,
|
||||
"background:set-theme": handleSetBackgroundTheme,
|
||||
"background:reset": handleResetBackground,
|
||||
};
|
||||
|
||||
async function onMessage(msg: Request) {
|
||||
const handler = handlers[msg.type];
|
||||
if (!handler) {
|
||||
sendError(msg.id, `Unknown handler: ${msg.type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await handler(msg.id, msg.payload);
|
||||
} catch (e: any) {
|
||||
sendError(msg.id, e.message || "Internal error");
|
||||
}
|
||||
}
|
||||
|
||||
process.stderr.write("[koring-sidecar] starting\n");
|
||||
startTransport(onMessage);
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Request, Response, ProgressPayload, ResultPayload } from "./types.js";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
type MessageHandler = (msg: Request) => void;
|
||||
|
||||
let buffer = "";
|
||||
|
||||
function sendResponse<T>(id: string, type: Response["type"], payload: T) {
|
||||
const msg: Response<T> = { id, type, payload };
|
||||
process.stdout.write(JSON.stringify(msg) + "\n");
|
||||
}
|
||||
|
||||
export function sendProgress(id: string, progress: ProgressPayload) {
|
||||
sendResponse(id, "progress", progress);
|
||||
}
|
||||
|
||||
export function sendResult<T>(id: string, data: T) {
|
||||
sendResponse<ResultPayload<T>>(id, "result", { success: true, data });
|
||||
}
|
||||
|
||||
export function sendError(id: string, error: string) {
|
||||
sendResponse<ResultPayload>(id, "result", { success: false, error });
|
||||
}
|
||||
|
||||
export function sendEvent<T>(id: string, event: T) {
|
||||
sendResponse(id, "event", event);
|
||||
}
|
||||
|
||||
export function startTransport(onMessage: MessageHandler) {
|
||||
process.stdin.setEncoding("utf-8");
|
||||
|
||||
process.stdin.on("data", (chunk: string) => {
|
||||
buffer += chunk;
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
try {
|
||||
const msg: Request = JSON.parse(trimmed);
|
||||
onMessage(msg);
|
||||
} catch (e) {
|
||||
process.stderr.write(`[transport] parse error: ${e}\n`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on("end", () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.stderr.write("[transport] ready\n");
|
||||
}
|
||||
|
||||
export function createRequestId(): string {
|
||||
return randomUUID();
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
export interface Request<T = unknown> {
|
||||
id: string;
|
||||
type: string;
|
||||
payload: T;
|
||||
}
|
||||
|
||||
export interface Response<T = unknown> {
|
||||
id: string;
|
||||
type: "result" | "error" | "progress" | "event";
|
||||
payload: T;
|
||||
}
|
||||
|
||||
export interface ProgressPayload {
|
||||
stage: string;
|
||||
current: number;
|
||||
total: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface ResultPayload<T = unknown> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// === Install ===
|
||||
|
||||
export interface InstallMinecraftRequest {
|
||||
version: string;
|
||||
gamePath: string;
|
||||
javaPath?: string;
|
||||
downloadThreads?: number;
|
||||
}
|
||||
|
||||
export interface InstallModLoaderRequest {
|
||||
gamePath: string;
|
||||
javaPath?: string;
|
||||
mcVersion: string;
|
||||
loaderType: "forge" | "fabric" | "quilt" | "neoforge";
|
||||
loaderVersion?: string;
|
||||
}
|
||||
|
||||
export interface GetVersionListRequest {
|
||||
type?: "release" | "snapshot" | "all";
|
||||
}
|
||||
|
||||
export interface GetForgeVersionListRequest {
|
||||
mcVersion?: string;
|
||||
}
|
||||
|
||||
export interface GetFabricVersionListRequest {
|
||||
mcVersion?: string;
|
||||
}
|
||||
|
||||
// === Launch ===
|
||||
|
||||
export interface LaunchRequest {
|
||||
gamePath: string;
|
||||
javaPath: string;
|
||||
version: string;
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
jvmArgs?: string[];
|
||||
gameArgs?: string[];
|
||||
server?: { ip: string; port?: number };
|
||||
detached?: boolean;
|
||||
}
|
||||
|
||||
export interface DiagnoseRequest {
|
||||
gamePath: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
// === Auth ===
|
||||
|
||||
export interface MicrosoftLoginStartRequest {
|
||||
client_id: string;
|
||||
redirect_uri?: string;
|
||||
}
|
||||
|
||||
export interface MicrosoftLoginCallbackRequest {
|
||||
state: string;
|
||||
code: string;
|
||||
client_id: string;
|
||||
}
|
||||
|
||||
export interface OfflineLoginRequest {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface ValidateTokenRequest {
|
||||
accessToken: string;
|
||||
clientToken?: string;
|
||||
}
|
||||
|
||||
// === Mods ===
|
||||
|
||||
export interface SearchModsRequest {
|
||||
query?: string;
|
||||
gameVersion?: string;
|
||||
loader?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
source: "modrinth" | "curseforge";
|
||||
}
|
||||
|
||||
export interface GetModDetailRequest {
|
||||
projectId: string;
|
||||
source: "modrinth" | "curseforge";
|
||||
}
|
||||
|
||||
export interface GetModVersionsRequest {
|
||||
projectId: string;
|
||||
gameVersion?: string;
|
||||
loader?: string;
|
||||
source: "modrinth" | "curseforge";
|
||||
}
|
||||
|
||||
export interface InstallModRequest {
|
||||
projectId: string;
|
||||
versionId?: string;
|
||||
gamePath: string;
|
||||
source: "modrinth" | "curseforge";
|
||||
}
|
||||
|
||||
// === Instance ===
|
||||
|
||||
export interface CreateInstanceRequest {
|
||||
name: string;
|
||||
instancesPath: string;
|
||||
gamePath: string;
|
||||
mcVersion: string;
|
||||
loaderType?: string;
|
||||
loaderVersion?: string;
|
||||
javaPath?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
}
|
||||
|
||||
export interface ListInstancesRequest {
|
||||
instancesPath: string;
|
||||
}
|
||||
|
||||
export interface DeleteInstanceRequest {
|
||||
name: string;
|
||||
instancesPath: string;
|
||||
}
|
||||
|
||||
export interface GetInstanceInfoRequest {
|
||||
name: string;
|
||||
instancesPath: string;
|
||||
}
|
||||
|
||||
// === Background ===
|
||||
|
||||
export type AnimationType = "none" | "gradient" | "particles";
|
||||
export type Theme = "light" | "dark" | "system";
|
||||
|
||||
export interface BackgroundConfig {
|
||||
type: "image" | "color" | "gradient" | "particles";
|
||||
image?: string;
|
||||
color?: string;
|
||||
blur: number;
|
||||
opacity: number;
|
||||
animation: AnimationType;
|
||||
animationSpeed: number;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
export interface SetBackgroundImageRequest {
|
||||
url: string;
|
||||
blur?: number;
|
||||
opacity?: number;
|
||||
}
|
||||
|
||||
export interface SetBackgroundColorRequest {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface SetBackgroundBlurRequest {
|
||||
blur: number;
|
||||
}
|
||||
|
||||
export interface SetBackgroundOpacityRequest {
|
||||
opacity: number;
|
||||
}
|
||||
|
||||
export interface SetBackgroundAnimationRequest {
|
||||
type: AnimationType;
|
||||
speed?: number;
|
||||
}
|
||||
|
||||
export interface SetBackgroundThemeRequest {
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
// === Handler Map ===
|
||||
|
||||
export type HandlerMap = {
|
||||
// Install
|
||||
"install:minecraft": InstallMinecraftRequest;
|
||||
"install:mod-loader": InstallModLoaderRequest;
|
||||
"install:version-list": GetVersionListRequest;
|
||||
"install:forge-version-list": GetForgeVersionListRequest;
|
||||
"install:fabric-version-list": GetFabricVersionListRequest;
|
||||
|
||||
// Launch
|
||||
"launch:launch": LaunchRequest;
|
||||
"launch:diagnose": DiagnoseRequest;
|
||||
|
||||
// Auth
|
||||
"auth:microsoft-login-start": MicrosoftLoginStartRequest;
|
||||
"auth:microsoft-login-callback": MicrosoftLoginCallbackRequest;
|
||||
"auth:offline-login": OfflineLoginRequest;
|
||||
"auth:validate-token": ValidateTokenRequest;
|
||||
|
||||
// Mods
|
||||
"mods:search": SearchModsRequest;
|
||||
"mods:detail": GetModDetailRequest;
|
||||
"mods:versions": GetModVersionsRequest;
|
||||
"mods:install": InstallModRequest;
|
||||
|
||||
// Instance
|
||||
"instance:create": CreateInstanceRequest;
|
||||
"instance:list": ListInstancesRequest;
|
||||
"instance:delete": DeleteInstanceRequest;
|
||||
"instance:info": GetInstanceInfoRequest;
|
||||
|
||||
// Background
|
||||
"background:set-image": SetBackgroundImageRequest;
|
||||
"background:set-color": SetBackgroundColorRequest;
|
||||
"background:set-blur": SetBackgroundBlurRequest;
|
||||
"background:set-opacity": SetBackgroundOpacityRequest;
|
||||
"background:set-animation": SetBackgroundAnimationRequest;
|
||||
"background:get": never;
|
||||
"background:set-theme": SetBackgroundThemeRequest;
|
||||
"background:reset": never;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { join } from "path";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
|
||||
export function ensureDir(dir: string) {
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function getMinecraftPath(gamePath: string) {
|
||||
return gamePath;
|
||||
}
|
||||
|
||||
export function getVersionPath(gamePath: string, versionId: string) {
|
||||
return join(gamePath, "versions", versionId);
|
||||
}
|
||||
|
||||
export function getVersionJsonPath(gamePath: string, versionId: string) {
|
||||
return join(getVersionPath(gamePath, versionId), `${versionId}.json`);
|
||||
}
|
||||
|
||||
export function getLibrariesPath(gamePath: string) {
|
||||
return join(gamePath, "libraries");
|
||||
}
|
||||
|
||||
export function getAssetsPath(gamePath: string) {
|
||||
return join(gamePath, "assets");
|
||||
}
|
||||
|
||||
export function getInstancesPath(instancesPath: string) {
|
||||
return instancesPath;
|
||||
}
|
||||
|
||||
export function getInstancePath(instancesPath: string, name: string) {
|
||||
return join(instancesPath, name);
|
||||
}
|
||||
|
||||
export interface InstanceConfig {
|
||||
name: string;
|
||||
mcVersion: string;
|
||||
loaderType?: string;
|
||||
loaderVersion?: string;
|
||||
javaPath?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export function readInstanceConfig(instancePath: string): InstanceConfig | null {
|
||||
const configPath = join(instancePath, "koring-instance.json");
|
||||
if (!existsSync(configPath)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(configPath, "utf-8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeInstanceConfig(instancePath: string, config: InstanceConfig) {
|
||||
const configPath = join(instancePath, "koring-instance.json");
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "bin"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>koring-launcher</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/splash.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "koring-launcher"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "koring_launcher_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:default",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-is-maximized",
|
||||
"core:window:allow-start-dragging",
|
||||
"opener:default",
|
||||
"shell:allow-execute",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
{
|
||||
"name": "binaries/koring-sidecar",
|
||||
"sidecar": true,
|
||||
"args": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 974 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 903 B |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,321 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::State;
|
||||
use crate::sidecar::{Request, SidecarManager};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CommandResult {
|
||||
pub success: bool,
|
||||
pub data: Option<serde_json::Value>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sidecar_request(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
msg_type: String,
|
||||
payload: serde_json::Value,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type,
|
||||
payload,
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_minecraft(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
version: String,
|
||||
game_path: String,
|
||||
java_path: Option<String>,
|
||||
download_threads: Option<u32>,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "install:minecraft".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"version": version,
|
||||
"gamePath": game_path,
|
||||
"javaPath": java_path,
|
||||
"downloadThreads": download_threads,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_mod_loader(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
mc_version: String,
|
||||
game_path: String,
|
||||
loader_type: String,
|
||||
loader_version: Option<String>,
|
||||
java_path: Option<String>,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "install:mod-loader".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"mcVersion": mc_version,
|
||||
"gamePath": game_path,
|
||||
"loaderType": loader_type,
|
||||
"loaderVersion": loader_version,
|
||||
"javaPath": java_path,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_version_list(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
version_type: Option<String>,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "install:version-list".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"type": version_type,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn launch_game(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
game_path: String,
|
||||
java_path: String,
|
||||
version: String,
|
||||
username: String,
|
||||
uuid: String,
|
||||
access_token: Option<String>,
|
||||
memory: Option<serde_json::Value>,
|
||||
jvm_args: Option<Vec<String>>,
|
||||
game_args: Option<Vec<String>>,
|
||||
server: Option<serde_json::Value>,
|
||||
detached: Option<bool>,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "launch:launch".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"gamePath": game_path,
|
||||
"javaPath": java_path,
|
||||
"version": version,
|
||||
"username": username,
|
||||
"uuid": uuid,
|
||||
"accessToken": access_token,
|
||||
"memory": memory,
|
||||
"jvmArgs": jvm_args,
|
||||
"gameArgs": game_args,
|
||||
"server": server,
|
||||
"detached": detached,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn offline_login(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
username: String,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "auth:offline-login".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"username": username,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn search_mods(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
query: Option<String>,
|
||||
game_version: Option<String>,
|
||||
loader: Option<String>,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
source: String,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "mods:search".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"query": query,
|
||||
"gameVersion": game_version,
|
||||
"loader": loader,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"source": source,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_mod(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
project_id: String,
|
||||
version_id: Option<String>,
|
||||
game_path: String,
|
||||
source: String,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "mods:install".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"projectId": project_id,
|
||||
"versionId": version_id,
|
||||
"gamePath": game_path,
|
||||
"source": source,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_instance(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
name: String,
|
||||
game_path: String,
|
||||
mc_version: String,
|
||||
loader_type: Option<String>,
|
||||
loader_version: Option<String>,
|
||||
java_path: Option<String>,
|
||||
memory: Option<serde_json::Value>,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "instance:create".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"name": name,
|
||||
"gamePath": game_path,
|
||||
"mcVersion": mc_version,
|
||||
"loaderType": loader_type,
|
||||
"loaderVersion": loader_version,
|
||||
"javaPath": java_path,
|
||||
"memory": memory,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_instances(
|
||||
sidecar: State<'_, std::sync::Mutex<SidecarManager>>,
|
||||
instances_path: String,
|
||||
) -> Result<CommandResult, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = Request {
|
||||
id: id.clone(),
|
||||
msg_type: "instance:list".to_string(),
|
||||
payload: serde_json::json!({
|
||||
"instancesPath": instances_path,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut manager = sidecar.lock().map_err(|e| e.to_string())?;
|
||||
manager.send_request(&request)?;
|
||||
|
||||
Ok(CommandResult {
|
||||
success: true,
|
||||
data: Some(serde_json::json!({ "requestId": id })),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
mod sidecar;
|
||||
mod commands;
|
||||
|
||||
use sidecar::SidecarManager;
|
||||
use std::sync::Mutex;
|
||||
use tauri::Manager;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.manage(Mutex::new(SidecarManager::new()))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::sidecar_request,
|
||||
commands::install_minecraft,
|
||||
commands::install_mod_loader,
|
||||
commands::get_version_list,
|
||||
commands::launch_game,
|
||||
commands::offline_login,
|
||||
commands::search_mods,
|
||||
commands::install_mod,
|
||||
commands::create_instance,
|
||||
commands::list_instances,
|
||||
])
|
||||
.setup(|app| {
|
||||
// Hide main window initially
|
||||
if let Some(main) = app.get_webview_window("main") {
|
||||
main.hide().ok();
|
||||
}
|
||||
|
||||
// After splash animation, close splash and show main
|
||||
let handle = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(8)).await;
|
||||
|
||||
// Close splash screen
|
||||
if let Some(splash) = handle.get_webview_window("splashscreen") {
|
||||
splash.close().ok();
|
||||
}
|
||||
|
||||
// Show main window
|
||||
if let Some(main) = handle.get_webview_window("main") {
|
||||
main.show().ok();
|
||||
main.set_focus().ok();
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
eprintln!("[koring] dev mode: sidecar spawn skipped (compile sidecar first)");
|
||||
return Ok(());
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
let handle = app.handle().clone();
|
||||
let state = app.state::<Mutex<SidecarManager>>();
|
||||
let mut sidecar = state.inner().lock().map_err(|e| e.to_string())?;
|
||||
sidecar.spawn(&handle)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
koring_launcher_lib::run()
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String,
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Response {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String,
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
pub struct SidecarManager {
|
||||
child: Option<CommandChild>,
|
||||
}
|
||||
|
||||
impl SidecarManager {
|
||||
pub fn new() -> Self {
|
||||
Self { child: None }
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, app: &AppHandle) -> Result<(), String> {
|
||||
let sidecar_command = app
|
||||
.shell()
|
||||
.sidecar("koring-sidecar")
|
||||
.map_err(|e| format!("Failed to create sidecar command: {}", e))?;
|
||||
|
||||
let (mut rx, child) = sidecar_command
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to spawn sidecar: {}", e))?;
|
||||
|
||||
self.child = Some(child);
|
||||
|
||||
let app_handle = app.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line_bytes) => {
|
||||
let line = String::from_utf8_lossy(&line_bytes);
|
||||
for line in line.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(response) = serde_json::from_str::<Response>(trimmed) {
|
||||
let _ = app_handle.emit("sidecar-response", &response);
|
||||
}
|
||||
}
|
||||
}
|
||||
CommandEvent::Stderr(line_bytes) => {
|
||||
let line = String::from_utf8_lossy(&line_bytes);
|
||||
eprintln!("[sidecar stderr] {}", line);
|
||||
}
|
||||
CommandEvent::Error(err) => {
|
||||
eprintln!("[sidecar error] {}", err);
|
||||
}
|
||||
CommandEvent::Terminated(status) => {
|
||||
eprintln!("[sidecar] terminated with status: {:?}", status);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_request(&mut self, request: &Request) -> Result<(), String> {
|
||||
let child = self.child.as_mut().ok_or("Sidecar not running")?;
|
||||
let json = serde_json::to_string(request).map_err(|e| format!("Serialize error: {}", e))?;
|
||||
child
|
||||
.write((json + "\n").as_bytes())
|
||||
.map_err(|e| format!("Write error: {}", e))
|
||||
}
|
||||
|
||||
pub fn kill(&mut self) -> Result<(), String> {
|
||||
if let Some(child) = self.child.take() {
|
||||
child.kill().map_err(|e| format!("Kill error: {}", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "koring-launcher",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.lingke.koring.launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"label": "splashscreen",
|
||||
"title": "splashscreen",
|
||||
"url": "/splash.html",
|
||||
"width": 480,
|
||||
"height": 320,
|
||||
"decorations": false,
|
||||
"transparent": true,
|
||||
"center": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"label": "main",
|
||||
"title": "koring-launcher",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"decorations": false,
|
||||
"transparent": true,
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": [
|
||||
"binaries/koring-sidecar"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
.logo.vite:hover {
|
||||
filter: drop-shadow(0 0 2em #747bff);
|
||||
}
|
||||
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafb);
|
||||
}
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-top: 10vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: 0.75s;
|
||||
}
|
||||
|
||||
.logo.tauri:hover {
|
||||
filter: drop-shadow(0 0 2em #24c8db);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#greet-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #24c8db;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useState } from "react";
|
||||
import { RootLayout } from "./layouts/RootLayout";
|
||||
import { Home } from "./pages/Home";
|
||||
import { Debug } from "./pages/Debug";
|
||||
|
||||
type Page = "home" | "debug";
|
||||
|
||||
function App() {
|
||||
const [page, setPage] = useState<Page>("home");
|
||||
|
||||
return (
|
||||
<RootLayout>
|
||||
{page === "home" && <Home onNavigate={setPage} />}
|
||||
{page === "debug" && <Debug onNavigate={setPage} />}
|
||||
</RootLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { sidecarRequest } from "./sidecar";
|
||||
|
||||
export interface AuthResult {
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken: string;
|
||||
expiresAt?: number;
|
||||
xboxProfile?: {
|
||||
gamertag: string;
|
||||
gamerscore: string;
|
||||
displayPicRaw: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function offlineLogin(username: string): Promise<AuthResult> {
|
||||
return sidecarRequest<AuthResult>("auth:offline-login", { username });
|
||||
}
|
||||
|
||||
export async function microsoftLoginStart(clientId: string, redirectUri?: string) {
|
||||
return sidecarRequest<{ state: string; authUrl: string }>(
|
||||
"auth:microsoft-login-start",
|
||||
{ client_id: clientId, redirect_uri: redirectUri }
|
||||
);
|
||||
}
|
||||
|
||||
export async function microsoftLoginCallback(
|
||||
code: string,
|
||||
clientId: string,
|
||||
redirectUri?: string
|
||||
): Promise<AuthResult> {
|
||||
return sidecarRequest<AuthResult>("auth:microsoft-login-callback", {
|
||||
code,
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
}
|
||||
|
||||
export async function validateToken(accessToken: string): Promise<boolean> {
|
||||
const result = await sidecarRequest<{ valid: boolean }>("auth:validate-token", {
|
||||
accessToken,
|
||||
});
|
||||
return result.valid;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { sidecarRequest } from "./sidecar";
|
||||
|
||||
export type AnimationType = "none" | "gradient" | "particles";
|
||||
export type Theme = "light" | "dark" | "system";
|
||||
|
||||
export interface BackgroundConfig {
|
||||
type: "image" | "color" | "gradient" | "particles";
|
||||
image?: string;
|
||||
color?: string;
|
||||
blur: number;
|
||||
opacity: number;
|
||||
animation: AnimationType;
|
||||
animationSpeed: number;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
export async function setImageBackground(url: string, blur?: number, opacity?: number): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:set-image", { url, blur, opacity });
|
||||
}
|
||||
|
||||
export async function setColorBackground(color: string): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:set-color", { color });
|
||||
}
|
||||
|
||||
export async function setBackgroundBlur(blur: number): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:set-blur", { blur });
|
||||
}
|
||||
|
||||
export async function setBackgroundOpacity(opacity: number): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:set-opacity", { opacity });
|
||||
}
|
||||
|
||||
export async function setBackgroundAnimation(type: AnimationType, speed?: number): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:set-animation", { type, speed });
|
||||
}
|
||||
|
||||
export async function getBackgroundConfig(): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:get", {});
|
||||
}
|
||||
|
||||
export async function setTheme(theme: Theme): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:set-theme", { theme });
|
||||
}
|
||||
|
||||
export async function resetBackground(): Promise<BackgroundConfig> {
|
||||
return sidecarRequest<BackgroundConfig>("background:reset", {});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from "./sidecar";
|
||||
export * from "./install";
|
||||
export * from "./launch";
|
||||
export * from "./auth";
|
||||
export * from "./mods";
|
||||
export * from "./instance";
|
||||
@@ -0,0 +1,54 @@
|
||||
import { sidecarRequest } from "./sidecar";
|
||||
|
||||
export interface VersionInfo {
|
||||
id: string;
|
||||
type: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface VersionManifest {
|
||||
latest: { release: string; snapshot: string };
|
||||
versions: VersionInfo[];
|
||||
}
|
||||
|
||||
export async function getVersionList(type?: string): Promise<VersionManifest> {
|
||||
return sidecarRequest<VersionManifest>("install:version-list", { type });
|
||||
}
|
||||
|
||||
export async function installMinecraft(
|
||||
version: string,
|
||||
gamePath: string,
|
||||
javaPath?: string,
|
||||
downloadThreads?: number
|
||||
): Promise<{ version: string; gamePath: string }> {
|
||||
return sidecarRequest("install:minecraft", {
|
||||
version,
|
||||
gamePath,
|
||||
javaPath,
|
||||
downloadThreads,
|
||||
});
|
||||
}
|
||||
|
||||
export async function installModLoader(
|
||||
mcVersion: string,
|
||||
gamePath: string,
|
||||
loaderType: "forge" | "fabric" | "quilt" | "neoforge",
|
||||
loaderVersion?: string,
|
||||
javaPath?: string
|
||||
): Promise<{ loaderType: string; mcVersion: string; loaderVersion: string }> {
|
||||
return sidecarRequest("install:mod-loader", {
|
||||
mcVersion,
|
||||
gamePath,
|
||||
loaderType,
|
||||
loaderVersion,
|
||||
javaPath,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getForgeVersions(mcVersion?: string) {
|
||||
return sidecarRequest("install:forge-version-list", { mcVersion });
|
||||
}
|
||||
|
||||
export async function getFabricVersions(mcVersion?: string) {
|
||||
return sidecarRequest("install:fabric-version-list", { mcVersion });
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { sidecarRequest } from "./sidecar";
|
||||
|
||||
export interface InstanceConfig {
|
||||
name: string;
|
||||
mcVersion: string;
|
||||
loaderType?: string;
|
||||
loaderVersion?: string;
|
||||
javaPath?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface InstanceInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
config: InstanceConfig;
|
||||
modCount?: number;
|
||||
}
|
||||
|
||||
export async function createInstance(
|
||||
name: string,
|
||||
gamePath: string,
|
||||
mcVersion: string,
|
||||
loaderType?: string,
|
||||
loaderVersion?: string,
|
||||
javaPath?: string,
|
||||
memory?: { min?: string; max?: string }
|
||||
): Promise<InstanceInfo> {
|
||||
return sidecarRequest<InstanceInfo>("instance:create", {
|
||||
name,
|
||||
gamePath,
|
||||
mcVersion,
|
||||
loaderType,
|
||||
loaderVersion,
|
||||
javaPath,
|
||||
memory,
|
||||
});
|
||||
}
|
||||
|
||||
export async function listInstances(instancesPath: string): Promise<InstanceInfo[]> {
|
||||
return sidecarRequest<InstanceInfo[]>("instance:list", { instancesPath });
|
||||
}
|
||||
|
||||
export async function deleteInstance(
|
||||
name: string,
|
||||
instancesPath: string
|
||||
): Promise<{ deleted: string }> {
|
||||
return sidecarRequest<{ deleted: string }>("instance:delete", {
|
||||
name,
|
||||
instancesPath,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getInstanceInfo(
|
||||
name: string,
|
||||
instancesPath: string
|
||||
): Promise<InstanceInfo> {
|
||||
return sidecarRequest<InstanceInfo>("instance:info", { name, instancesPath });
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { sidecarRequest, onEvent } from "./sidecar";
|
||||
import type { UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
export interface LaunchOptions {
|
||||
gamePath: string;
|
||||
javaPath: string;
|
||||
version: string;
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
jvmArgs?: string[];
|
||||
gameArgs?: string[];
|
||||
server?: { ip: string; port?: number };
|
||||
detached?: boolean;
|
||||
}
|
||||
|
||||
export interface LaunchResult {
|
||||
pid: number;
|
||||
version: string;
|
||||
username: string;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export async function launchGame(options: LaunchOptions): Promise<LaunchResult> {
|
||||
return sidecarRequest<LaunchResult>("launch:launch", options);
|
||||
}
|
||||
|
||||
export async function diagnoseVersion(
|
||||
gamePath: string,
|
||||
version: string
|
||||
) {
|
||||
return sidecarRequest("launch:diagnose", { gamePath, version });
|
||||
}
|
||||
|
||||
export function onGameEvent(
|
||||
requestId: string,
|
||||
callback: (event: { event: string; [key: string]: unknown }) => void
|
||||
): Promise<UnlistenFn> {
|
||||
return onEvent(requestId, callback as (e: Record<string, unknown>) => void);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { sidecarRequest } from "./sidecar";
|
||||
|
||||
export interface ModSearchResult {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
downloads: number;
|
||||
iconUrl?: string;
|
||||
categories?: string[];
|
||||
versions?: string[];
|
||||
loaders?: string[];
|
||||
source: "modrinth" | "curseforge";
|
||||
}
|
||||
|
||||
export interface ModVersionResult {
|
||||
id: string;
|
||||
name: string;
|
||||
versionNumber: string;
|
||||
gameVersions: string[];
|
||||
loaders: string[];
|
||||
files: {
|
||||
filename: string;
|
||||
url: string;
|
||||
size: number;
|
||||
primary: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function searchMods(
|
||||
query?: string,
|
||||
gameVersion?: string,
|
||||
loader?: string,
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
source: "modrinth" | "curseforge" = "modrinth"
|
||||
): Promise<ModSearchResult[]> {
|
||||
return sidecarRequest<ModSearchResult[]>("mods:search", {
|
||||
query,
|
||||
gameVersion,
|
||||
loader,
|
||||
limit,
|
||||
offset,
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getModDetail(
|
||||
projectId: string,
|
||||
source: "modrinth" | "curseforge"
|
||||
): Promise<ModSearchResult> {
|
||||
return sidecarRequest<ModSearchResult>("mods:detail", { projectId, source });
|
||||
}
|
||||
|
||||
export async function getModVersions(
|
||||
projectId: string,
|
||||
gameVersion?: string,
|
||||
loader?: string,
|
||||
source: "modrinth" | "curseforge" = "modrinth"
|
||||
): Promise<ModVersionResult[]> {
|
||||
return sidecarRequest<ModVersionResult[]>("mods:versions", {
|
||||
projectId,
|
||||
gameVersion,
|
||||
loader,
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
export async function installMod(
|
||||
projectId: string,
|
||||
versionId: string | undefined,
|
||||
gamePath: string,
|
||||
source: "modrinth" | "curseforge" = "modrinth"
|
||||
): Promise<{ projectId: string; versionId: string; filename: string; path: string }> {
|
||||
return sidecarRequest("mods:install", {
|
||||
projectId,
|
||||
versionId,
|
||||
gamePath,
|
||||
source,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
export interface SidecarResponse<T = unknown> {
|
||||
id: string;
|
||||
type: "result" | "error" | "progress" | "event";
|
||||
payload: T;
|
||||
}
|
||||
|
||||
export interface CommandResult {
|
||||
success: boolean;
|
||||
data?: { requestId: string };
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ResultPayload<T = unknown> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ProgressPayload {
|
||||
stage: string;
|
||||
current: number;
|
||||
total: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Generic request function
|
||||
export async function sidecarRequest<T = unknown>(
|
||||
type: string,
|
||||
payload: unknown
|
||||
): Promise<T> {
|
||||
const result = await invoke<CommandResult>("sidecar_request", {
|
||||
msgType: type,
|
||||
payload,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || "Request failed");
|
||||
}
|
||||
|
||||
// Wait for the response via event
|
||||
const requestId = result.data?.requestId;
|
||||
if (!requestId) {
|
||||
throw new Error("No request ID returned");
|
||||
}
|
||||
|
||||
return waitForResponse<T>(requestId);
|
||||
}
|
||||
|
||||
// Wait for a specific response by request ID
|
||||
function waitForResponse<T>(requestId: string, timeout = 60000): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
unlisten();
|
||||
reject(new Error("Request timed out"));
|
||||
}, timeout);
|
||||
|
||||
let unlisten: UnlistenFn;
|
||||
|
||||
listen<SidecarResponse>("sidecar-response", (event) => {
|
||||
const response = event.payload;
|
||||
if (response.id !== requestId) return;
|
||||
|
||||
if (response.type === "result") {
|
||||
clearTimeout(timer);
|
||||
unlisten();
|
||||
const payload = response.payload as ResultPayload<T>;
|
||||
if (payload.success) {
|
||||
resolve(payload.data as T);
|
||||
} else {
|
||||
reject(new Error(payload.error || "Request failed"));
|
||||
}
|
||||
} else if (response.type === "error") {
|
||||
clearTimeout(timer);
|
||||
unlisten();
|
||||
reject(new Error(String(response.payload)));
|
||||
}
|
||||
}).then((fn) => {
|
||||
unlisten = fn;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for progress events
|
||||
export function onProgress(
|
||||
requestId: string,
|
||||
callback: (progress: ProgressPayload) => void
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<SidecarResponse>("sidecar-response", (event) => {
|
||||
const response = event.payload;
|
||||
if (response.id === requestId && response.type === "progress") {
|
||||
callback(response.payload as ProgressPayload);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for events (game lifecycle, etc.)
|
||||
export function onEvent(
|
||||
requestId: string,
|
||||
callback: (event: Record<string, unknown>) => void
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<SidecarResponse>("sidecar-response", (event) => {
|
||||
const response = event.payload;
|
||||
if (response.id === requestId && response.type === "event") {
|
||||
callback(response.payload as Record<string, unknown>);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,82 @@
|
||||
import { useEffect } from "react";
|
||||
import { useBackgroundStore } from "@/stores/backgroundStore";
|
||||
|
||||
const DEFAULT_BG = "/background.png";
|
||||
|
||||
export function BackgroundLayer() {
|
||||
const { type, image, color, blur, opacity, animationSpeed, fetchConfig } = useBackgroundStore();
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig();
|
||||
}, [fetchConfig]);
|
||||
|
||||
const getBackgroundStyle = (): React.CSSProperties => {
|
||||
const base: React.CSSProperties = {
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
zIndex: 0,
|
||||
pointerEvents: "none",
|
||||
opacity,
|
||||
};
|
||||
|
||||
if (blur > 0) {
|
||||
base.filter = `blur(${blur}px)`;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "image":
|
||||
return {
|
||||
...base,
|
||||
backgroundImage: `url(${image || DEFAULT_BG})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
};
|
||||
case "color":
|
||||
return {
|
||||
...base,
|
||||
backgroundColor: color || "#1a1a2e",
|
||||
backgroundImage: `url(${DEFAULT_BG})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
};
|
||||
case "gradient":
|
||||
return {
|
||||
...base,
|
||||
background: `linear-gradient(135deg, ${color || "#1a1a2e"}, #16213e, #0f3460)`,
|
||||
animation: `gradient-shift ${10 / animationSpeed}s ease infinite`,
|
||||
};
|
||||
case "particles":
|
||||
return {
|
||||
...base,
|
||||
background: `radial-gradient(circle at 20% 50%, rgba(${color || "26,26,46"}, 0.8) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(22, 33, 62, 0.6) 0%, transparent 40%),
|
||||
radial-gradient(circle at 50% 80%, rgba(15, 52, 96, 0.4) 0%, transparent 60%)`,
|
||||
animation: `particles-float ${20 / animationSpeed}s ease-in-out infinite`,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...base,
|
||||
backgroundImage: `url(${DEFAULT_BG})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@keyframes gradient-shift {
|
||||
0%, 100% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
}
|
||||
@keyframes particles-float {
|
||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||
33% { transform: translateY(-10px) rotate(1deg); }
|
||||
66% { transform: translateY(10px) rotate(-1deg); }
|
||||
}
|
||||
`}</style>
|
||||
<div style={getBackgroundStyle()} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const VERSION = "0.1.0";
|
||||
|
||||
export default function Splash() {
|
||||
const [phase, setPhase] = useState<"enter" | "visible" | "exit">("enter");
|
||||
|
||||
useEffect(() => {
|
||||
const t1 = setTimeout(() => setPhase("visible"), 50);
|
||||
const t2 = setTimeout(() => setPhase("exit"), 7500);
|
||||
return () => { clearTimeout(t1); clearTimeout(t2); };
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="splash-root w-full h-full flex flex-col overflow-hidden">
|
||||
{/* Main content area */}
|
||||
<div
|
||||
className="flex-1 flex items-center pl-12 transition-all duration-700 ease-out"
|
||||
style={{
|
||||
opacity: phase === "enter" ? 0 : phase === "exit" ? 0 : 1,
|
||||
transform: phase === "enter" ? "translateY(10px)" : phase === "exit" ? "translateY(-10px)" : "translateY(0)",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/koring-licon.svg"
|
||||
alt="Koring Launcher"
|
||||
className="splash-logo w-[260px] h-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bottom bar */}
|
||||
<div className="flex items-center justify-between px-6 pb-4 text-xs splash-muted">
|
||||
<span>Provided by Lingke Koring Studio</span>
|
||||
<span>v{VERSION}</span>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.splash-root {
|
||||
background: var(--splash-bg);
|
||||
}
|
||||
.splash-logo {
|
||||
fill: var(--splash-fg);
|
||||
}
|
||||
.splash-muted {
|
||||
color: var(--splash-muted);
|
||||
}
|
||||
|
||||
/* Light mode (default) */
|
||||
:root {
|
||||
--splash-bg: #ffffff;
|
||||
--splash-fg: #1a1a2e;
|
||||
--splash-muted: #888888;
|
||||
}
|
||||
|
||||
/* Dark mode via class */
|
||||
.dark {
|
||||
--splash-bg: #1a1a2e;
|
||||
--splash-fg: #ffffff;
|
||||
--splash-muted: #888888;
|
||||
}
|
||||
|
||||
/* System dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not(.light) {
|
||||
--splash-bg: #1a1a2e;
|
||||
--splash-fg: #ffffff;
|
||||
--splash-muted: #888888;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { TitleBar } from "./TitleBar";
|
||||
|
||||
interface SystemLayerProps {
|
||||
showMinimize?: boolean;
|
||||
showMaximize?: boolean;
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
export function SystemLayer({
|
||||
showMinimize = true,
|
||||
showMaximize = true,
|
||||
showClose = true,
|
||||
}: SystemLayerProps) {
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[100] pointer-events-none"
|
||||
>
|
||||
<TitleBar
|
||||
showMinimize={showMinimize}
|
||||
showMaximize={showMaximize}
|
||||
showClose={showClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { WindowControls } from "./WindowControls";
|
||||
|
||||
interface TitleBarProps {
|
||||
showMinimize?: boolean;
|
||||
showMaximize?: boolean;
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
export function TitleBar({
|
||||
showMinimize = true,
|
||||
showMaximize = true,
|
||||
showClose = true,
|
||||
}: TitleBarProps) {
|
||||
const appWindow = getCurrentWindow();
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.closest("[data-no-drag]")) return;
|
||||
appWindow.startDragging();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="titlebar fixed top-0 left-0 right-0 h-[40px] flex items-center justify-between z-[100] pointer-events-auto"
|
||||
onMouseDown={handleMouseDown}
|
||||
style={{
|
||||
background: "rgba(255, 255, 255, 0.1)",
|
||||
backdropFilter: "blur(12px) saturate(180%)",
|
||||
WebkitBackdropFilter: "blur(12px) saturate(180%)",
|
||||
borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<div className="flex-1" />
|
||||
<WindowControls
|
||||
showMinimize={showMinimize}
|
||||
showMaximize={showMaximize}
|
||||
showClose={showClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
interface WindowControlsProps {
|
||||
showMinimize?: boolean;
|
||||
showMaximize?: boolean;
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
export function WindowControls({
|
||||
showMinimize = true,
|
||||
showMaximize = true,
|
||||
showClose = true,
|
||||
}: WindowControlsProps) {
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
const appWindow = getCurrentWindow();
|
||||
|
||||
useEffect(() => {
|
||||
appWindow.isMaximized().then(setIsMaximized);
|
||||
const unlisten = appWindow.onResized(() => {
|
||||
appWindow.isMaximized().then(setIsMaximized);
|
||||
});
|
||||
return () => { unlisten.then(fn => fn()); };
|
||||
}, [appWindow]);
|
||||
|
||||
const handleMinimize = () => appWindow.minimize();
|
||||
const handleMaximize = () => appWindow.toggleMaximize();
|
||||
const handleClose = () => appWindow.close();
|
||||
|
||||
const btnClass = "flex items-center justify-center w-[25px] h-[25px] rounded transition-colors cursor-default hover:bg-black/10 dark:hover:bg-white/15 text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white";
|
||||
const closeBtnClass = "flex items-center justify-center w-[25px] h-[25px] rounded transition-colors cursor-default hover:bg-red-500 text-black/70 dark:text-white/70 hover:text-white";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 pr-2" data-no-drag>
|
||||
{showMinimize && (
|
||||
<div onClick={handleMinimize} className={btnClass}>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<line x1="2" y1="6" x2="10" y2="6" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{showMaximize && (
|
||||
<div onClick={handleMaximize} className={btnClass}>
|
||||
{isMaximized ? (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<rect x="2.5" y="0.5" width="7" height="7" rx="1" />
|
||||
<rect x="0.5" y="2.5" width="7" height="7" rx="1" fill="var(--background, #fff)" />
|
||||
<rect x="0.5" y="2.5" width="7" height="7" rx="1" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<rect x="1" y="1" width="10" height="10" rx="1" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showClose && (
|
||||
<div onClick={handleClose} className={closeBtnClass}>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<line x1="2" y1="2" x2="10" y2="10" />
|
||||
<line x1="10" y1="2" x2="2" y2="10" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
outline:
|
||||
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||
ghost:
|
||||
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
||||
destructive:
|
||||
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default:
|
||||
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
icon: "size-8",
|
||||
"icon-xs":
|
||||
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
||||
"icon-sm":
|
||||
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
||||
"icon-lg": "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
...props
|
||||
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,139 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@fontsource-variable/geist";
|
||||
@import "@fontsource-variable/inter";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@font-face {
|
||||
font-family: 'Alimama';
|
||||
src: url('/alimama.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-stretch: condensed;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-heading: var(--font-sans);
|
||||
--font-sans: 'Alimama', 'Inter Variable', sans-serif;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-background: var(--background);
|
||||
--radius-sm: calc(var(--radius) * 0.6);
|
||||
--radius-md: calc(var(--radius) * 0.8);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) * 1.4);
|
||||
--radius-2xl: calc(var(--radius) * 1.8);
|
||||
--radius-3xl: calc(var(--radius) * 2.2);
|
||||
--radius-4xl: calc(var(--radius) * 2.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { BackgroundLayer } from "@/components/background/BackgroundLayer";
|
||||
import { SystemLayer } from "@/components/system/SystemLayer";
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: ReactNode;
|
||||
showMinimize?: boolean;
|
||||
showMaximize?: boolean;
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
export function RootLayout({
|
||||
children,
|
||||
showMinimize = true,
|
||||
showMaximize = true,
|
||||
showClose = true,
|
||||
}: RootLayoutProps) {
|
||||
return (
|
||||
<div className="relative w-screen h-screen overflow-hidden">
|
||||
{/* Layer 0: Background */}
|
||||
<BackgroundLayer />
|
||||
|
||||
{/* Layer 1: Content */}
|
||||
<div className="absolute z-[1] left-0 right-0 bottom-0 top-[40px] overflow-auto">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Layer 100: System (titlebar + window controls) */}
|
||||
<SystemLayer
|
||||
showMinimize={showMinimize}
|
||||
showMaximize={showMaximize}
|
||||
showClose={showClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
interface DebugProps {
|
||||
onNavigate: (page: "home") => void;
|
||||
}
|
||||
|
||||
export function Debug({ onNavigate }: DebugProps) {
|
||||
const openSplash = async () => {
|
||||
const existing = await WebviewWindow.getByLabel("splashscreen");
|
||||
if (existing) {
|
||||
existing.show();
|
||||
return;
|
||||
}
|
||||
|
||||
new WebviewWindow("splashscreen", {
|
||||
url: "/splash.html",
|
||||
width: 480,
|
||||
height: 320,
|
||||
decorations: false,
|
||||
transparent: true,
|
||||
center: true,
|
||||
});
|
||||
};
|
||||
|
||||
const closeSplash = async () => {
|
||||
const splash = await WebviewWindow.getByLabel("splashscreen");
|
||||
if (splash) {
|
||||
splash.close();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-2xl font-bold mb-6">Debug</h1>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={openSplash}>Open Splash</Button>
|
||||
<Button variant="destructive" onClick={closeSplash}>Close Splash</Button>
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<Button variant="ghost" onClick={() => onNavigate("home")}>
|
||||
Back to Home
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface HomeProps {
|
||||
onNavigate: (page: "debug") => void;
|
||||
}
|
||||
|
||||
export function Home({ onNavigate }: HomeProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">Koring Launcher</h1>
|
||||
<p className="text-muted-foreground mb-6">Core 调试模式</p>
|
||||
<Button variant="outline" onClick={() => onNavigate("debug")}>
|
||||
Debug
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import Splash from "./components/splash/Splash";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<Splash />
|
||||
</StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,65 @@
|
||||
import { create } from "zustand";
|
||||
import { offlineLogin, microsoftLoginStart, microsoftLoginCallback } from "../api/auth";
|
||||
import type { AuthResult } from "../api/auth";
|
||||
|
||||
interface AuthState {
|
||||
user: AuthResult | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
msAuthUrl: string | null;
|
||||
msAuthState: string | null;
|
||||
|
||||
loginOffline: (username: string) => Promise<void>;
|
||||
startMicrosoftLogin: (clientId: string) => Promise<void>;
|
||||
completeMicrosoftLogin: (code: string, clientId: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
msAuthUrl: null,
|
||||
msAuthState: null,
|
||||
|
||||
loginOffline: async (username: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const user = await offlineLogin(username);
|
||||
set({ user, loading: false });
|
||||
localStorage.setItem("koring-user", JSON.stringify(user));
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
startMicrosoftLogin: async (clientId: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const { state, authUrl } = await microsoftLoginStart(clientId);
|
||||
set({ msAuthUrl: authUrl, msAuthState: state, loading: false });
|
||||
window.open(authUrl, "_blank");
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
completeMicrosoftLogin: async (code: string, clientId: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const user = await microsoftLoginCallback(code, clientId);
|
||||
set({ user, loading: false, msAuthUrl: null, msAuthState: null });
|
||||
localStorage.setItem("koring-user", JSON.stringify(user));
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
set({ user: null, msAuthUrl: null, msAuthState: null });
|
||||
localStorage.removeItem("koring-user");
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
@@ -0,0 +1,133 @@
|
||||
import { create } from "zustand";
|
||||
import {
|
||||
setImageBackground,
|
||||
setColorBackground,
|
||||
setBackgroundBlur,
|
||||
setBackgroundOpacity,
|
||||
setBackgroundAnimation,
|
||||
getBackgroundConfig,
|
||||
setTheme,
|
||||
resetBackground,
|
||||
} from "../api/background";
|
||||
import type { AnimationType, Theme, BackgroundConfig } from "../api/background";
|
||||
|
||||
interface BackgroundState {
|
||||
type: "image" | "color" | "gradient" | "particles";
|
||||
image?: string;
|
||||
color?: string;
|
||||
blur: number;
|
||||
opacity: number;
|
||||
animation: AnimationType;
|
||||
animationSpeed: number;
|
||||
theme: Theme;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
setImage: (url: string, blur?: number, opacity?: number) => Promise<void>;
|
||||
setColor: (color: string) => Promise<void>;
|
||||
setBlur: (blur: number) => Promise<void>;
|
||||
setOpacity: (opacity: number) => Promise<void>;
|
||||
setAnimation: (type: AnimationType, speed?: number) => Promise<void>;
|
||||
setTheme: (theme: Theme) => Promise<void>;
|
||||
fetchConfig: () => Promise<void>;
|
||||
reset: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
const defaultConfig: BackgroundConfig = {
|
||||
type: "color",
|
||||
color: "#1a1a2e",
|
||||
blur: 0,
|
||||
opacity: 1,
|
||||
animation: "none",
|
||||
animationSpeed: 1,
|
||||
theme: "dark",
|
||||
};
|
||||
|
||||
export const useBackgroundStore = create<BackgroundState>((set) => ({
|
||||
...defaultConfig,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
setImage: async (url, blur, opacity) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await setImageBackground(url, blur, opacity);
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setColor: async (color) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await setColorBackground(color);
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setBlur: async (blur) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await setBackgroundBlur(blur);
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setOpacity: async (opacity) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await setBackgroundOpacity(opacity);
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setAnimation: async (type, speed) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await setBackgroundAnimation(type, speed);
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setTheme: async (theme) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await setTheme(theme);
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
fetchConfig: async () => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await getBackgroundConfig();
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
reset: async () => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const config = await resetBackground();
|
||||
set({ ...config, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
@@ -0,0 +1,5 @@
|
||||
export { useInstallStore } from "./installStore";
|
||||
export { useAuthStore } from "./authStore";
|
||||
export { useInstanceStore } from "./instanceStore";
|
||||
export { useLaunchStore } from "./launchStore";
|
||||
export { useModsStore } from "./modsStore";
|
||||
@@ -0,0 +1,69 @@
|
||||
import { create } from "zustand";
|
||||
import { getVersionList, installMinecraft, installModLoader } from "../api/install";
|
||||
import type { VersionManifest } from "../api/install";
|
||||
|
||||
interface ProgressInfo {
|
||||
stage: string;
|
||||
current: number;
|
||||
total: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface InstallState {
|
||||
versions: VersionManifest | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
progress: ProgressInfo | null;
|
||||
installing: boolean;
|
||||
|
||||
fetchVersions: (type?: string) => Promise<void>;
|
||||
install: (version: string, gamePath: string, javaPath?: string) => Promise<void>;
|
||||
installLoader: (
|
||||
mcVersion: string,
|
||||
gamePath: string,
|
||||
loaderType: "forge" | "fabric" | "quilt" | "neoforge",
|
||||
loaderVersion?: string,
|
||||
javaPath?: string
|
||||
) => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useInstallStore = create<InstallState>((set) => ({
|
||||
versions: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
progress: null,
|
||||
installing: false,
|
||||
|
||||
fetchVersions: async (type?: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const versions = await getVersionList(type);
|
||||
set({ versions, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
install: async (version, gamePath, javaPath?) => {
|
||||
set({ installing: true, error: null, progress: null });
|
||||
try {
|
||||
await installMinecraft(version, gamePath, javaPath);
|
||||
set({ installing: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, installing: false });
|
||||
}
|
||||
},
|
||||
|
||||
installLoader: async (mcVersion, gamePath, loaderType, loaderVersion?, javaPath?) => {
|
||||
set({ installing: true, error: null, progress: null });
|
||||
try {
|
||||
await installModLoader(mcVersion, gamePath, loaderType, loaderVersion, javaPath);
|
||||
set({ installing: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, installing: false });
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
@@ -0,0 +1,102 @@
|
||||
import { create } from "zustand";
|
||||
import {
|
||||
createInstance,
|
||||
listInstances,
|
||||
deleteInstance,
|
||||
getInstanceInfo,
|
||||
} from "../api/instance";
|
||||
import type { InstanceInfo } from "../api/instance";
|
||||
|
||||
interface InstanceState {
|
||||
instances: InstanceInfo[];
|
||||
currentInstance: InstanceInfo | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
fetchInstances: (instancesPath: string) => Promise<void>;
|
||||
create: (
|
||||
name: string,
|
||||
gamePath: string,
|
||||
mcVersion: string,
|
||||
loaderType?: string,
|
||||
loaderVersion?: string,
|
||||
javaPath?: string,
|
||||
memory?: { min?: string; max?: string }
|
||||
) => Promise<void>;
|
||||
remove: (name: string, instancesPath: string) => Promise<void>;
|
||||
select: (name: string, instancesPath: string) => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useInstanceStore = create<InstanceState>((set) => ({
|
||||
instances: [],
|
||||
currentInstance: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
fetchInstances: async (instancesPath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const instances = await listInstances(instancesPath);
|
||||
set({ instances, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
create: async (
|
||||
name,
|
||||
gamePath,
|
||||
mcVersion,
|
||||
loaderType?,
|
||||
loaderVersion?,
|
||||
javaPath?,
|
||||
memory?
|
||||
) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const instance = await createInstance(
|
||||
name,
|
||||
gamePath,
|
||||
mcVersion,
|
||||
loaderType,
|
||||
loaderVersion,
|
||||
javaPath,
|
||||
memory
|
||||
);
|
||||
set((state) => ({
|
||||
instances: [...state.instances, instance],
|
||||
loading: false,
|
||||
}));
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
remove: async (name: string, instancesPath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
await deleteInstance(name, instancesPath);
|
||||
set((state) => ({
|
||||
instances: state.instances.filter((i) => i.name !== name),
|
||||
currentInstance:
|
||||
state.currentInstance?.name === name ? null : state.currentInstance,
|
||||
loading: false,
|
||||
}));
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
select: async (name: string, instancesPath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const instance = await getInstanceInfo(name, instancesPath);
|
||||
set({ currentInstance: instance, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
@@ -0,0 +1,65 @@
|
||||
import { create } from "zustand";
|
||||
import { launchGame, onGameEvent, diagnoseVersion } from "../api/launch";
|
||||
import type { LaunchOptions, LaunchResult } from "../api/launch";
|
||||
|
||||
interface GameEvent {
|
||||
event: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface LaunchState {
|
||||
launching: boolean;
|
||||
launched: boolean;
|
||||
gameResult: LaunchResult | null;
|
||||
events: GameEvent[];
|
||||
error: string | null;
|
||||
|
||||
launch: (options: LaunchOptions) => Promise<void>;
|
||||
diagnose: (gamePath: string, version: string) => Promise<void>;
|
||||
reset: () => void;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useLaunchStore = create<LaunchState>((set) => ({
|
||||
launching: false,
|
||||
launched: false,
|
||||
gameResult: null,
|
||||
events: [],
|
||||
error: null,
|
||||
|
||||
launch: async (options: LaunchOptions) => {
|
||||
set({ launching: true, error: null, events: [] });
|
||||
try {
|
||||
const result = await launchGame(options);
|
||||
set({ gameResult: result, launching: false, launched: true });
|
||||
|
||||
// Listen for game events
|
||||
const unlisten = await onGameEvent(result.requestId, (event) => {
|
||||
set((state) => ({
|
||||
events: [...state.events, event],
|
||||
}));
|
||||
|
||||
if (event.event === "exit") {
|
||||
set({ launched: false });
|
||||
unlisten();
|
||||
}
|
||||
});
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, launching: false });
|
||||
}
|
||||
},
|
||||
|
||||
diagnose: async (gamePath: string, version: string) => {
|
||||
try {
|
||||
await diagnoseVersion(gamePath, version);
|
||||
} catch (e: any) {
|
||||
set({ error: e.message });
|
||||
}
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
set({ launching: false, launched: false, gameResult: null, events: [] });
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
@@ -0,0 +1,90 @@
|
||||
import { create } from "zustand";
|
||||
import { searchMods, getModDetail, getModVersions, installMod } from "../api/mods";
|
||||
import type { ModSearchResult, ModVersionResult } from "../api/mods";
|
||||
|
||||
interface ModsState {
|
||||
searchResults: ModSearchResult[];
|
||||
currentMod: ModSearchResult | null;
|
||||
modVersions: ModVersionResult[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
search: (
|
||||
query?: string,
|
||||
gameVersion?: string,
|
||||
loader?: string,
|
||||
source?: "modrinth" | "curseforge"
|
||||
) => Promise<void>;
|
||||
getDetail: (projectId: string, source: "modrinth" | "curseforge") => Promise<void>;
|
||||
getVersions: (
|
||||
projectId: string,
|
||||
gameVersion?: string,
|
||||
loader?: string,
|
||||
source?: "modrinth" | "curseforge"
|
||||
) => Promise<void>;
|
||||
install: (
|
||||
projectId: string,
|
||||
versionId: string | undefined,
|
||||
gamePath: string,
|
||||
source?: "modrinth" | "curseforge"
|
||||
) => Promise<void>;
|
||||
clearError: () => void;
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
export const useModsStore = create<ModsState>((set) => ({
|
||||
searchResults: [],
|
||||
currentMod: null,
|
||||
modVersions: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
search: async (query?, gameVersion?, loader?, source = "modrinth") => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const results = await searchMods(query, gameVersion, loader, 20, 0, source);
|
||||
set({ searchResults: results, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
getDetail: async (projectId, source) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const detail = await getModDetail(projectId, source);
|
||||
set({ currentMod: detail, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
getVersions: async (projectId, gameVersion?, loader?, source = "modrinth") => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const versions = await getModVersions(projectId, gameVersion, loader, source);
|
||||
set({ modVersions: versions, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
install: async (projectId, versionId, gamePath, source = "modrinth") => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
await installMod(projectId, versionId, gamePath, source);
|
||||
set({ loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
|
||||
clear: () =>
|
||||
set({
|
||||
searchResults: [],
|
||||
currentMod: null,
|
||||
modVersions: [],
|
||||
}),
|
||||
}));
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: path.resolve(__dirname, "index.html"),
|
||||
splash: path.resolve(__dirname, "splash.html"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
},
|
||||
}));
|
||||