diff --git a/apps/oneclient/desktop/package.json b/apps/oneclient/desktop/package.json index bb196947..59d4687e 100644 --- a/apps/oneclient/desktop/package.json +++ b/apps/oneclient/desktop/package.json @@ -16,7 +16,8 @@ "build": "tauri build", "dev": "tauri dev", "tauri": "tauri", - "tauri-ext": "pnpm --filter @onelauncher/scripts -- tauri" + "tauri-ext": "pnpm --filter @onelauncher/scripts -- tauri", + "cargo": "cargo" }, "dependencies": { "@oneclient/frontend": "workspace:*" diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts deleted file mode 100644 index ad95f510..00000000 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ /dev/null @@ -1,112 +0,0 @@ -// This file has been generated by Specta. DO NOT EDIT. - -import type { InferCommandOutput } from 'taurpc'; -import { createTauRPCProxy as createProxy } from 'taurpc'; - -type TAURI_CHANNEL = (response: T) => void; - -export type ClusterError = { InvalidVersion: string } | 'MissingJavaVersion' | 'ClusterDownloading' | 'ClusterAlreadyRunning'; - -export type ClusterStage = 'notready' | 'downloading' | 'repairing' | 'ready'; - -export interface CreateCluster { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null } - -export type CryptoError = { InvalidHash: { algorithm: HashAlgorithm; expected: string; actual: string } } | 'InvalidAlgorithm'; - -export type DaoError = 'NotFound' | 'AlreadyExists' | { InvalidValue: [string, string] }; - -export type DirectoryError = 'BaseDir'; - -export type DiscordError = 'MissingClientId' | 'ConnectError'; - -export type GameLoader = 'vanilla' | 'forge' | 'neoforge' | 'quilt' | 'fabric' | 'legacyfabric'; - -export type HashAlgorithm = 'Sha1' | 'Sha256' | 'Md5'; - -/** - * A wrapper around generic and unhelpful [`std::io::Error`] messages. - */ -export type IOError = { InvalidAbsolutePath: string } | { IOErrorWrapper: { path: string } } | 'IOError' | 'DeserializeError' | 'AsyncZipError'; - -export type Icon = string; - -export type IncompatiblePackageType = 'McVersion' | 'Loader'; - -export type IngressError = 'NotFound'; - -export interface IngressPayload { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number } - -export type IngressType = { Download: { file_name: string } } | 'JavaPrepare' | 'JavaCheck' | 'JavaLocate' | 'MinecraftDownload' | { PrepareCluster: { cluster_name: string } }; - -export type JavaError = { ParseVersion: [string] } | 'Execute' | 'MissingJava'; - -export type LauncherError = { type: 'DirError'; data: DirectoryError } | { type: 'IOError'; data: IOError } | { type: 'IngressError'; data: IngressError } | { type: 'JavaError'; data: JavaError } | { type: 'CryptoError'; data: CryptoError } | { type: 'DiscordError'; data: DiscordError } | { type: 'MetadataError'; data: MetadataError } | { type: 'ClusterError'; data: ClusterError } | { type: 'MinecraftAuthError'; data: MinecraftAuthError } | { type: 'ProcessError'; data: ProcessError } | { type: 'PackageError'; data: PackageError } | { type: 'DaoError'; data: DaoError } | { type: 'SerdeError' } | { type: 'AnyhowError' } | { type: 'DbError' } | { type: 'ReqwestError' } | { type: 'InterpulseError' } | { type: 'RegexError' } | { type: 'SemaphoreError' } | { type: 'UrlError' } | { type: 'TauriError' }; - -export type MessageLevel = 'Info' | 'Warn' | 'Error'; - -export interface MessagePayload { level: MessageLevel; message: string } - -export type MetadataError = 'FetchError' | { NotModdedManifest: GameLoader } | { NotVanillaManifest: GameLoader } | 'ParseError' | 'NoMatchingLoader' | 'NoMatchingVersion'; - -/** - * Wrapper around all `Error`s that can occur during the Microsoft authentication process. - */ -export type MinecraftAuthError = 'PublicKeyReading' | 'PKCS8Error' | { SerializeError: { step: MinecraftAuthStep } } | { DeserializeError: { step: MinecraftAuthStep; raw: string } } | { RequestError: { step: MinecraftAuthStep } } | { SigningError: { step: MinecraftAuthStep } } | 'HashError' | 'SessionIdError'; - -/** - * An ordered list of all MSA authentication steps. - */ -export type MinecraftAuthStep = 'DeviceToken' | 'SisuAuthenicate' | 'OAuthToken' | 'RefreshOAuthToken' | 'SisuAuthorize' | 'XstsAuthorize' | 'MinecraftToken' | 'MinecraftEntitlements' | 'MinecraftProfile'; - -/** - * A structure of all needed Minecraft credentials for logging in and account management. - */ -export interface MinecraftCredentials { -/** - * The uuid of the credentials. - */ - id: string; - /** - * The username of the Minecraft account. - */ - username: string; - /** - * The access token as a String. - */ - access_token: string; - /** - * The refresh token as a string for [`MinecraftState#refresh`]. - */ - refresh_token: string; - /** - * The time that the access token expires as a [`DateTime`]. - */ - expires: string; - -} - -export interface Model { id: bigint; folder_name: string; stage: ClusterStage; created_at: string; group_id: bigint | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: bigint | null; icon_url: Icon | null; setting_profile_name: string | null; linked_pack_id: string | null; linked_pack_version: string | null } - -export type PackageError = 'NoPrimaryFile' | 'IsModPack' | { Incompatible: IncompatiblePackageType }; - -export interface Process { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string } - -export type ProcessError = { HookUnsuccessful: number } | 'NoPid'; - -export type ProcessPayload = { type: 'Starting'; command: string } | { type: 'Started'; process: Process } | { type: 'Stopped'; pid: number; exit_code: number } | { type: 'Output'; pid: number; output: string }; - -export interface Resolution { width: number; height: number } - -export interface SettingProfileModel { name: string; java_id: bigint | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null } - -export interface SettingsOsExtra { enable_gamemode: boolean | null } - -const ARGS_MAP = { oneclient: '{"open_dev_tools":[],"return_error":[]}', core: '{"getClusters":[],"createCluster":["options"],"getGlobalProfile":[],"getClusterById":["id"],"openMsaLogin":[],"getUsers":[],"getDefaultUser":["fallback"],"setDefaultUser":["uuid"],"getUser":["uuid"],"getProfileOrDefault":["name"],"removeUser":["uuid"],"removeCluster":["id"],"launchCluster":["id","uuid"]}', events: '{"message":["event"],"process":["event"],"ingress":["event"]}' }; -export interface Router { - events: { ingress: (event: IngressPayload) => Promise; message: (event: MessagePayload) => Promise; process: (event: ProcessPayload) => Promise }; - core: { getClusters: () => Promise>; getClusterById: (id: bigint) => Promise; removeCluster: (id: bigint) => Promise; createCluster: (options: CreateCluster) => Promise; launchCluster: (id: bigint, uuid: string | null) => Promise; getProfileOrDefault: (name: string | null) => Promise; getGlobalProfile: () => Promise; getUsers: () => Promise>; getUser: (uuid: string) => Promise; removeUser: (uuid: string) => Promise; getDefaultUser: (fallback: boolean | null) => Promise; setDefaultUser: (uuid: string | null) => Promise; openMsaLogin: () => Promise }; - oneclient: { return_error: () => Promise; open_dev_tools: () => Promise }; -} - -export type { InferCommandOutput }; -export const createTauRPCProxy = () => createProxy(ARGS_MAP); diff --git a/apps/oneclient/frontend/src/components/MyContext.tsx b/apps/oneclient/frontend/src/components/MyContext.tsx new file mode 100644 index 00000000..b3b4269b --- /dev/null +++ b/apps/oneclient/frontend/src/components/MyContext.tsx @@ -0,0 +1,27 @@ +import type { Context } from 'react'; +import { createContext, useContext } from 'react'; + +const myContext = createContext(null) as Context< + { + text: string; + } | null +>; + +export function MyContextProvider({ children, value }: { + children: React.ReactNode; + value: { + text: string; + } | null; +}) { + // eslint-disable-next-line react/no-context-provider -- ok + return {children}; +} + +export default function useMyContext() { + const context = useContext(myContext); + + if (!context) + throw new Error('useMyContext must be used within a MyContextProvider'); + + return context; +} diff --git a/apps/oneclient/frontend/src/routeTree.gen.ts b/apps/oneclient/frontend/src/routeTree.gen.ts deleted file mode 100644 index 5e682936..00000000 --- a/apps/oneclient/frontend/src/routeTree.gen.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable */ - -// @ts-nocheck - -// noinspection JSUnusedGlobalSymbols - -// This file was automatically generated by TanStack Router. -// You should NOT make any changes in this file as it will be overwritten. -// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. - -// Import Routes - -import { Route as rootRoute } from './routes/__root' -import { Route as IndexImport } from './routes/index' - -// Create/Update Routes - -const IndexRoute = IndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRoute, -} as any) - -// Populate the FileRoutesByPath interface - -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexImport - parentRoute: typeof rootRoute - } - } -} - -// Create and export the route tree - -export interface FileRoutesByFullPath { - '/': typeof IndexRoute -} - -export interface FileRoutesByTo { - '/': typeof IndexRoute -} - -export interface FileRoutesById { - __root__: typeof rootRoute - '/': typeof IndexRoute -} - -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' - fileRoutesByTo: FileRoutesByTo - to: '/' - id: '__root__' | '/' - fileRoutesById: FileRoutesById -} - -export interface RootRouteChildren { - IndexRoute: typeof IndexRoute -} - -const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, -} - -export const routeTree = rootRoute - ._addFileChildren(rootRouteChildren) - ._addFileTypes() - -/* ROUTE_MANIFEST_START -{ - "routes": { - "__root__": { - "filePath": "__root.tsx", - "children": [ - "/" - ] - }, - "/": { - "filePath": "index.tsx" - } - } -} -ROUTE_MANIFEST_END */ diff --git a/apps/oneclient/frontend/src/routes/index.tsx b/apps/oneclient/frontend/src/routes/index.tsx index 7f4a4afa..eb19c339 100644 --- a/apps/oneclient/frontend/src/routes/index.tsx +++ b/apps/oneclient/frontend/src/routes/index.tsx @@ -1,3 +1,4 @@ +import type { LauncherError } from '@/bindings.gen'; import { bindings } from '@/main'; import { Button } from '@onelauncher/common/components'; import { createFileRoute } from '@tanstack/react-router'; @@ -16,15 +17,33 @@ function App() { // mc_loader_version: '11.15.1.2318', // }); - await bindings.core.launchCluster(2 as unknown as bigint, null); + try { + // await bindings.core.launchCluster(9 as unknown as bigint, null); + await bindings.core.removeCluster(9 as unknown as bigint); - const unlisten = await bindings.events.process.on((e) => { - if (e.type === 'Output') { console.log(e.output); } - else if (e.type === 'Stopped') { - console.log(e.exit_code); - unlisten(); - } - }); + const unlisten = await bindings.events.process.on((e) => { + if (e.type === 'Output') { console.log(e.output); } + else if (e.type === 'Stopped') { + console.log(e.exit_code); + unlisten(); + } + }); + } + catch (err: unknown) { + let error = err as LauncherError; + // console.log(error); + // if (error.type === 'DaoError') + // if (error.data.type === 'NotFound') + // console.log(error.data.data); + if (error.type === 'TauriError') + console.error(error.data); + else if (error.type === 'IOError') + console.error(error.data.data); + else if (error.type === 'DaoError') + console.error(error.data); + + console.log(error); + } }; return ( diff --git a/apps/oneclient/frontend/src/routes/test/app.tsx b/apps/oneclient/frontend/src/routes/test/app.tsx new file mode 100644 index 00000000..9b3e6e67 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/test/app.tsx @@ -0,0 +1,21 @@ +import useMyContext from '@/components/MyContext'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/test/app')({ + component: RouteComponent, +}); + +function RouteComponent() { + const myContext = useMyContext(); + + return ( +
+
Hello "/test/app"!
+
+ Value from MyContext: + {' '} + {myContext.text} +
+
+ ); +} diff --git a/apps/oneclient/frontend/src/routes/test/route.tsx b/apps/oneclient/frontend/src/routes/test/route.tsx new file mode 100644 index 00000000..58647854 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/test/route.tsx @@ -0,0 +1,15 @@ +import { MyContextProvider } from '@/components/MyContext'; +import { createFileRoute, Outlet } from '@tanstack/react-router'; + +export const Route = createFileRoute('/test')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( + +
Hello "/test"!
+ +
+ ); +} diff --git a/apps/onelauncher/frontend/src/bindings.gen.ts b/apps/onelauncher/frontend/src/bindings.gen.ts deleted file mode 100644 index c6d70c94..00000000 --- a/apps/onelauncher/frontend/src/bindings.gen.ts +++ /dev/null @@ -1,123 +0,0 @@ -// This file has been generated by Specta. DO NOT EDIT. - -import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurpc' -type TAURI_CHANNEL = (response: T) => void - - -export type ClusterError = { InvalidVersion: string } | "MissingJavaVersion" | "ClusterDownloading" | "ClusterAlreadyRunning" - -export type ClusterStage = "notready" | "downloading" | "repairing" | "ready" - -export type CreateCluster = { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null } - -export type CryptoError = { InvalidHash: { algorithm: HashAlgorithm; expected: string; actual: string } } | "InvalidAlgorithm" - -export type DaoError = "NotFound" | "AlreadyExists" | { InvalidValue: [string, string] } - -export type DirectoryError = "BaseDir" - -export type DiscordError = "MissingClientId" | "ConnectError" - -export type GameLoader = "vanilla" | "forge" | "neoforge" | "quilt" | "fabric" | "legacyfabric" - -export type HashAlgorithm = "Sha1" | "Sha256" | "Md5" - -/** - * A wrapper around generic and unhelpful [`std::io::Error`] messages. - */ -export type IOError = { InvalidAbsolutePath: string } | { IOErrorWrapper: { path: string } } | "IOError" | "DeserializeError" | "AsyncZipError" - -export type Icon = string - -export type IncompatiblePackageType = "McVersion" | "Loader" - -export type IngressError = "NotFound" - -export type IngressPayload = { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number } - -export type IngressType = { Download: { file_name: string } } | "JavaPrepare" | "JavaCheck" | "JavaLocate" | "MinecraftDownload" | { PrepareCluster: { cluster_name: string } } - -export type JavaError = { ParseVersion: [string] } | "Execute" | "MissingJava" - -export type LauncherError = { type: "DirError"; data: DirectoryError } | { type: "IOError"; data: IOError } | { type: "IngressError"; data: IngressError } | { type: "JavaError"; data: JavaError } | { type: "CryptoError"; data: CryptoError } | { type: "DiscordError"; data: DiscordError } | { type: "MetadataError"; data: MetadataError } | { type: "ClusterError"; data: ClusterError } | { type: "MinecraftAuthError"; data: MinecraftAuthError } | { type: "ProcessError"; data: ProcessError } | { type: "PackageError"; data: PackageError } | { type: "DaoError"; data: DaoError } | { type: "SerdeError" } | { type: "AnyhowError" } | { type: "DbError" } | { type: "ReqwestError" } | { type: "InterpulseError" } | { type: "RegexError" } | { type: "SemaphoreError" } | { type: "UrlError" } | { type: "TauriError" } - -export type MessageLevel = "Info" | "Warn" | "Error" - -export type MessagePayload = { level: MessageLevel; message: string } - -export type MetadataError = "FetchError" | { NotModdedManifest: GameLoader } | { NotVanillaManifest: GameLoader } | "ParseError" | "NoMatchingLoader" | "NoMatchingVersion" - -/** - * Wrapper around all `Error`s that can occur during the Microsoft authentication process. - */ -export type MinecraftAuthError = "PublicKeyReading" | "PKCS8Error" | { SerializeError: { step: MinecraftAuthStep } } | { DeserializeError: { step: MinecraftAuthStep; raw: string } } | { RequestError: { step: MinecraftAuthStep } } | { SigningError: { step: MinecraftAuthStep } } | "HashError" | "SessionIdError" - -/** - * An ordered list of all MSA authentication steps. - */ -export type MinecraftAuthStep = "DeviceToken" | "SisuAuthenicate" | "OAuthToken" | "RefreshOAuthToken" | "SisuAuthorize" | "XstsAuthorize" | "MinecraftToken" | "MinecraftEntitlements" | "MinecraftProfile" - -/** - * A structure of all needed Minecraft credentials for logging in and account management. - */ -export type MinecraftCredentials = { -/** - * The uuid of the credentials. - */ -id: string; -/** - * The username of the Minecraft account. - */ -username: string; -/** - * The access token as a String. - */ -access_token: string; -/** - * The refresh token as a string for [`MinecraftState#refresh`]. - */ -refresh_token: string; -/** - * The time that the access token expires as a [`DateTime`]. - */ -expires: string } - -export type Model = { id: bigint; folder_name: string; stage: ClusterStage; created_at: string; group_id: bigint | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: bigint | null; icon_url: Icon | null; setting_profile_name: string | null; linked_pack_id: string | null; linked_pack_version: string | null } - -export type PackageError = "NoPrimaryFile" | "IsModPack" | { Incompatible: IncompatiblePackageType } - -export type Process = { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string } - -export type ProcessError = { HookUnsuccessful: number } | "NoPid" - -export type ProcessPayload = { type: "Starting"; command: string } | { type: "Started"; process: Process } | { type: "Stopped"; pid: number; exit_code: number } | { type: "Output"; pid: number; output: string } - -export type Resolution = { width: number; height: number } - -export type SettingProfileModel = { name: string; java_id: bigint | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null } - -export type SettingsOsExtra = Record - -const ARGS_MAP = { 'events':'{"process":["event"],"message":["event"],"ingress":["event"]}', 'onelauncher':'{"return_error":[],"open_dev_tools":[]}', 'core':'{"getProfileOrDefault":["name"],"setDefaultUser":["uuid"],"launchCluster":["id","uuid"],"getClusterById":["id"],"removeCluster":["id"],"createCluster":["options"],"getUsers":[],"getUser":["uuid"],"getGlobalProfile":[],"getDefaultUser":["fallback"],"getClusters":[],"removeUser":["uuid"],"openMsaLogin":[]}' } -export type Router = { 'onelauncher': { return_error: () => Promise, -open_dev_tools: () => Promise }, -'core': { getClusters: () => Promise, -getClusterById: (id: bigint) => Promise, -removeCluster: (id: bigint) => Promise, -createCluster: (options: CreateCluster) => Promise, -launchCluster: (id: bigint, uuid: string | null) => Promise, -getProfileOrDefault: (name: string | null) => Promise, -getGlobalProfile: () => Promise, -getUsers: () => Promise, -getUser: (uuid: string) => Promise, -removeUser: (uuid: string) => Promise, -getDefaultUser: (fallback: boolean | null) => Promise, -setDefaultUser: (uuid: string | null) => Promise, -openMsaLogin: () => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise } }; - - -export type { InferCommandOutput } -export const createTauRPCProxy = () => createProxy(ARGS_MAP) diff --git a/apps/onelauncher/frontend/src/routeTree.gen.ts b/apps/onelauncher/frontend/src/routeTree.gen.ts deleted file mode 100644 index 1ba59c25..00000000 --- a/apps/onelauncher/frontend/src/routeTree.gen.ts +++ /dev/null @@ -1,669 +0,0 @@ -/* eslint-disable */ - -// @ts-nocheck - -// noinspection JSUnusedGlobalSymbols - -// This file was automatically generated by TanStack Router. -// You should NOT make any changes in this file as it will be overwritten. -// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. - -// Import Routes - -import { Route as rootRoute } from './routes/__root' -import { Route as OnboardingRouteImport } from './routes/onboarding/route' -import { Route as AppRouteImport } from './routes/app/route' -import { Route as IndexImport } from './routes/index' -import { Route as OnboardingIndexImport } from './routes/onboarding/index' -import { Route as AppIndexImport } from './routes/app/index' -import { Route as OnboardingSummaryImport } from './routes/onboarding/summary' -import { Route as OnboardingLoginImport } from './routes/onboarding/login' -import { Route as OnboardingLanguageImport } from './routes/onboarding/language' -import { Route as OnboardingImportImport } from './routes/onboarding/import' -import { Route as OnboardingCompleteImport } from './routes/onboarding/complete' -import { Route as AppSettingsRouteImport } from './routes/app/settings/route' -import { Route as AppClusterRouteImport } from './routes/app/cluster/route' -import { Route as AppSettingsIndexImport } from './routes/app/settings/index' -import { Route as AppClusterIndexImport } from './routes/app/cluster/index' -import { Route as AppBrowserIndexImport } from './routes/app/browser/index' -import { Route as AppSettingsMinecraftImport } from './routes/app/settings/minecraft' -import { Route as AppSettingsAppearanceImport } from './routes/app/settings/appearance' -import { Route as AppSettingsAccountsImport } from './routes/app/settings/accounts' -import { Route as AppClusterWorldsImport } from './routes/app/cluster/worlds' -import { Route as AppClusterSettingsImport } from './routes/app/cluster/settings' -import { Route as AppClusterScreenshotsImport } from './routes/app/cluster/screenshots' - -// Create/Update Routes - -const OnboardingRouteRoute = OnboardingRouteImport.update({ - id: '/onboarding', - path: '/onboarding', - getParentRoute: () => rootRoute, -} as any) - -const AppRouteRoute = AppRouteImport.update({ - id: '/app', - path: '/app', - getParentRoute: () => rootRoute, -} as any) - -const IndexRoute = IndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRoute, -} as any) - -const OnboardingIndexRoute = OnboardingIndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => OnboardingRouteRoute, -} as any) - -const AppIndexRoute = AppIndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => AppRouteRoute, -} as any) - -const OnboardingSummaryRoute = OnboardingSummaryImport.update({ - id: '/summary', - path: '/summary', - getParentRoute: () => OnboardingRouteRoute, -} as any) - -const OnboardingLoginRoute = OnboardingLoginImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => OnboardingRouteRoute, -} as any) - -const OnboardingLanguageRoute = OnboardingLanguageImport.update({ - id: '/language', - path: '/language', - getParentRoute: () => OnboardingRouteRoute, -} as any) - -const OnboardingImportRoute = OnboardingImportImport.update({ - id: '/import', - path: '/import', - getParentRoute: () => OnboardingRouteRoute, -} as any) - -const OnboardingCompleteRoute = OnboardingCompleteImport.update({ - id: '/complete', - path: '/complete', - getParentRoute: () => OnboardingRouteRoute, -} as any) - -const AppSettingsRouteRoute = AppSettingsRouteImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => AppRouteRoute, -} as any) - -const AppClusterRouteRoute = AppClusterRouteImport.update({ - id: '/cluster', - path: '/cluster', - getParentRoute: () => AppRouteRoute, -} as any) - -const AppSettingsIndexRoute = AppSettingsIndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => AppSettingsRouteRoute, -} as any) - -const AppClusterIndexRoute = AppClusterIndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => AppClusterRouteRoute, -} as any) - -const AppBrowserIndexRoute = AppBrowserIndexImport.update({ - id: '/browser/', - path: '/browser/', - getParentRoute: () => AppRouteRoute, -} as any) - -const AppSettingsMinecraftRoute = AppSettingsMinecraftImport.update({ - id: '/minecraft', - path: '/minecraft', - getParentRoute: () => AppSettingsRouteRoute, -} as any) - -const AppSettingsAppearanceRoute = AppSettingsAppearanceImport.update({ - id: '/appearance', - path: '/appearance', - getParentRoute: () => AppSettingsRouteRoute, -} as any) - -const AppSettingsAccountsRoute = AppSettingsAccountsImport.update({ - id: '/accounts', - path: '/accounts', - getParentRoute: () => AppSettingsRouteRoute, -} as any) - -const AppClusterWorldsRoute = AppClusterWorldsImport.update({ - id: '/worlds', - path: '/worlds', - getParentRoute: () => AppClusterRouteRoute, -} as any) - -const AppClusterSettingsRoute = AppClusterSettingsImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => AppClusterRouteRoute, -} as any) - -const AppClusterScreenshotsRoute = AppClusterScreenshotsImport.update({ - id: '/screenshots', - path: '/screenshots', - getParentRoute: () => AppClusterRouteRoute, -} as any) - -// Populate the FileRoutesByPath interface - -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexImport - parentRoute: typeof rootRoute - } - '/app': { - id: '/app' - path: '/app' - fullPath: '/app' - preLoaderRoute: typeof AppRouteImport - parentRoute: typeof rootRoute - } - '/onboarding': { - id: '/onboarding' - path: '/onboarding' - fullPath: '/onboarding' - preLoaderRoute: typeof OnboardingRouteImport - parentRoute: typeof rootRoute - } - '/app/cluster': { - id: '/app/cluster' - path: '/cluster' - fullPath: '/app/cluster' - preLoaderRoute: typeof AppClusterRouteImport - parentRoute: typeof AppRouteImport - } - '/app/settings': { - id: '/app/settings' - path: '/settings' - fullPath: '/app/settings' - preLoaderRoute: typeof AppSettingsRouteImport - parentRoute: typeof AppRouteImport - } - '/onboarding/complete': { - id: '/onboarding/complete' - path: '/complete' - fullPath: '/onboarding/complete' - preLoaderRoute: typeof OnboardingCompleteImport - parentRoute: typeof OnboardingRouteImport - } - '/onboarding/import': { - id: '/onboarding/import' - path: '/import' - fullPath: '/onboarding/import' - preLoaderRoute: typeof OnboardingImportImport - parentRoute: typeof OnboardingRouteImport - } - '/onboarding/language': { - id: '/onboarding/language' - path: '/language' - fullPath: '/onboarding/language' - preLoaderRoute: typeof OnboardingLanguageImport - parentRoute: typeof OnboardingRouteImport - } - '/onboarding/login': { - id: '/onboarding/login' - path: '/login' - fullPath: '/onboarding/login' - preLoaderRoute: typeof OnboardingLoginImport - parentRoute: typeof OnboardingRouteImport - } - '/onboarding/summary': { - id: '/onboarding/summary' - path: '/summary' - fullPath: '/onboarding/summary' - preLoaderRoute: typeof OnboardingSummaryImport - parentRoute: typeof OnboardingRouteImport - } - '/app/': { - id: '/app/' - path: '/' - fullPath: '/app/' - preLoaderRoute: typeof AppIndexImport - parentRoute: typeof AppRouteImport - } - '/onboarding/': { - id: '/onboarding/' - path: '/' - fullPath: '/onboarding/' - preLoaderRoute: typeof OnboardingIndexImport - parentRoute: typeof OnboardingRouteImport - } - '/app/cluster/screenshots': { - id: '/app/cluster/screenshots' - path: '/screenshots' - fullPath: '/app/cluster/screenshots' - preLoaderRoute: typeof AppClusterScreenshotsImport - parentRoute: typeof AppClusterRouteImport - } - '/app/cluster/settings': { - id: '/app/cluster/settings' - path: '/settings' - fullPath: '/app/cluster/settings' - preLoaderRoute: typeof AppClusterSettingsImport - parentRoute: typeof AppClusterRouteImport - } - '/app/cluster/worlds': { - id: '/app/cluster/worlds' - path: '/worlds' - fullPath: '/app/cluster/worlds' - preLoaderRoute: typeof AppClusterWorldsImport - parentRoute: typeof AppClusterRouteImport - } - '/app/settings/accounts': { - id: '/app/settings/accounts' - path: '/accounts' - fullPath: '/app/settings/accounts' - preLoaderRoute: typeof AppSettingsAccountsImport - parentRoute: typeof AppSettingsRouteImport - } - '/app/settings/appearance': { - id: '/app/settings/appearance' - path: '/appearance' - fullPath: '/app/settings/appearance' - preLoaderRoute: typeof AppSettingsAppearanceImport - parentRoute: typeof AppSettingsRouteImport - } - '/app/settings/minecraft': { - id: '/app/settings/minecraft' - path: '/minecraft' - fullPath: '/app/settings/minecraft' - preLoaderRoute: typeof AppSettingsMinecraftImport - parentRoute: typeof AppSettingsRouteImport - } - '/app/browser/': { - id: '/app/browser/' - path: '/browser' - fullPath: '/app/browser' - preLoaderRoute: typeof AppBrowserIndexImport - parentRoute: typeof AppRouteImport - } - '/app/cluster/': { - id: '/app/cluster/' - path: '/' - fullPath: '/app/cluster/' - preLoaderRoute: typeof AppClusterIndexImport - parentRoute: typeof AppClusterRouteImport - } - '/app/settings/': { - id: '/app/settings/' - path: '/' - fullPath: '/app/settings/' - preLoaderRoute: typeof AppSettingsIndexImport - parentRoute: typeof AppSettingsRouteImport - } - } -} - -// Create and export the route tree - -interface AppClusterRouteRouteChildren { - AppClusterScreenshotsRoute: typeof AppClusterScreenshotsRoute - AppClusterSettingsRoute: typeof AppClusterSettingsRoute - AppClusterWorldsRoute: typeof AppClusterWorldsRoute - AppClusterIndexRoute: typeof AppClusterIndexRoute -} - -const AppClusterRouteRouteChildren: AppClusterRouteRouteChildren = { - AppClusterScreenshotsRoute: AppClusterScreenshotsRoute, - AppClusterSettingsRoute: AppClusterSettingsRoute, - AppClusterWorldsRoute: AppClusterWorldsRoute, - AppClusterIndexRoute: AppClusterIndexRoute, -} - -const AppClusterRouteRouteWithChildren = AppClusterRouteRoute._addFileChildren( - AppClusterRouteRouteChildren, -) - -interface AppSettingsRouteRouteChildren { - AppSettingsAccountsRoute: typeof AppSettingsAccountsRoute - AppSettingsAppearanceRoute: typeof AppSettingsAppearanceRoute - AppSettingsMinecraftRoute: typeof AppSettingsMinecraftRoute - AppSettingsIndexRoute: typeof AppSettingsIndexRoute -} - -const AppSettingsRouteRouteChildren: AppSettingsRouteRouteChildren = { - AppSettingsAccountsRoute: AppSettingsAccountsRoute, - AppSettingsAppearanceRoute: AppSettingsAppearanceRoute, - AppSettingsMinecraftRoute: AppSettingsMinecraftRoute, - AppSettingsIndexRoute: AppSettingsIndexRoute, -} - -const AppSettingsRouteRouteWithChildren = - AppSettingsRouteRoute._addFileChildren(AppSettingsRouteRouteChildren) - -interface AppRouteRouteChildren { - AppClusterRouteRoute: typeof AppClusterRouteRouteWithChildren - AppSettingsRouteRoute: typeof AppSettingsRouteRouteWithChildren - AppIndexRoute: typeof AppIndexRoute - AppBrowserIndexRoute: typeof AppBrowserIndexRoute -} - -const AppRouteRouteChildren: AppRouteRouteChildren = { - AppClusterRouteRoute: AppClusterRouteRouteWithChildren, - AppSettingsRouteRoute: AppSettingsRouteRouteWithChildren, - AppIndexRoute: AppIndexRoute, - AppBrowserIndexRoute: AppBrowserIndexRoute, -} - -const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren( - AppRouteRouteChildren, -) - -interface OnboardingRouteRouteChildren { - OnboardingCompleteRoute: typeof OnboardingCompleteRoute - OnboardingImportRoute: typeof OnboardingImportRoute - OnboardingLanguageRoute: typeof OnboardingLanguageRoute - OnboardingLoginRoute: typeof OnboardingLoginRoute - OnboardingSummaryRoute: typeof OnboardingSummaryRoute - OnboardingIndexRoute: typeof OnboardingIndexRoute -} - -const OnboardingRouteRouteChildren: OnboardingRouteRouteChildren = { - OnboardingCompleteRoute: OnboardingCompleteRoute, - OnboardingImportRoute: OnboardingImportRoute, - OnboardingLanguageRoute: OnboardingLanguageRoute, - OnboardingLoginRoute: OnboardingLoginRoute, - OnboardingSummaryRoute: OnboardingSummaryRoute, - OnboardingIndexRoute: OnboardingIndexRoute, -} - -const OnboardingRouteRouteWithChildren = OnboardingRouteRoute._addFileChildren( - OnboardingRouteRouteChildren, -) - -export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/app': typeof AppRouteRouteWithChildren - '/onboarding': typeof OnboardingRouteRouteWithChildren - '/app/cluster': typeof AppClusterRouteRouteWithChildren - '/app/settings': typeof AppSettingsRouteRouteWithChildren - '/onboarding/complete': typeof OnboardingCompleteRoute - '/onboarding/import': typeof OnboardingImportRoute - '/onboarding/language': typeof OnboardingLanguageRoute - '/onboarding/login': typeof OnboardingLoginRoute - '/onboarding/summary': typeof OnboardingSummaryRoute - '/app/': typeof AppIndexRoute - '/onboarding/': typeof OnboardingIndexRoute - '/app/cluster/screenshots': typeof AppClusterScreenshotsRoute - '/app/cluster/settings': typeof AppClusterSettingsRoute - '/app/cluster/worlds': typeof AppClusterWorldsRoute - '/app/settings/accounts': typeof AppSettingsAccountsRoute - '/app/settings/appearance': typeof AppSettingsAppearanceRoute - '/app/settings/minecraft': typeof AppSettingsMinecraftRoute - '/app/browser': typeof AppBrowserIndexRoute - '/app/cluster/': typeof AppClusterIndexRoute - '/app/settings/': typeof AppSettingsIndexRoute -} - -export interface FileRoutesByTo { - '/': typeof IndexRoute - '/onboarding/complete': typeof OnboardingCompleteRoute - '/onboarding/import': typeof OnboardingImportRoute - '/onboarding/language': typeof OnboardingLanguageRoute - '/onboarding/login': typeof OnboardingLoginRoute - '/onboarding/summary': typeof OnboardingSummaryRoute - '/app': typeof AppIndexRoute - '/onboarding': typeof OnboardingIndexRoute - '/app/cluster/screenshots': typeof AppClusterScreenshotsRoute - '/app/cluster/settings': typeof AppClusterSettingsRoute - '/app/cluster/worlds': typeof AppClusterWorldsRoute - '/app/settings/accounts': typeof AppSettingsAccountsRoute - '/app/settings/appearance': typeof AppSettingsAppearanceRoute - '/app/settings/minecraft': typeof AppSettingsMinecraftRoute - '/app/browser': typeof AppBrowserIndexRoute - '/app/cluster': typeof AppClusterIndexRoute - '/app/settings': typeof AppSettingsIndexRoute -} - -export interface FileRoutesById { - __root__: typeof rootRoute - '/': typeof IndexRoute - '/app': typeof AppRouteRouteWithChildren - '/onboarding': typeof OnboardingRouteRouteWithChildren - '/app/cluster': typeof AppClusterRouteRouteWithChildren - '/app/settings': typeof AppSettingsRouteRouteWithChildren - '/onboarding/complete': typeof OnboardingCompleteRoute - '/onboarding/import': typeof OnboardingImportRoute - '/onboarding/language': typeof OnboardingLanguageRoute - '/onboarding/login': typeof OnboardingLoginRoute - '/onboarding/summary': typeof OnboardingSummaryRoute - '/app/': typeof AppIndexRoute - '/onboarding/': typeof OnboardingIndexRoute - '/app/cluster/screenshots': typeof AppClusterScreenshotsRoute - '/app/cluster/settings': typeof AppClusterSettingsRoute - '/app/cluster/worlds': typeof AppClusterWorldsRoute - '/app/settings/accounts': typeof AppSettingsAccountsRoute - '/app/settings/appearance': typeof AppSettingsAppearanceRoute - '/app/settings/minecraft': typeof AppSettingsMinecraftRoute - '/app/browser/': typeof AppBrowserIndexRoute - '/app/cluster/': typeof AppClusterIndexRoute - '/app/settings/': typeof AppSettingsIndexRoute -} - -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/' - | '/app' - | '/onboarding' - | '/app/cluster' - | '/app/settings' - | '/onboarding/complete' - | '/onboarding/import' - | '/onboarding/language' - | '/onboarding/login' - | '/onboarding/summary' - | '/app/' - | '/onboarding/' - | '/app/cluster/screenshots' - | '/app/cluster/settings' - | '/app/cluster/worlds' - | '/app/settings/accounts' - | '/app/settings/appearance' - | '/app/settings/minecraft' - | '/app/browser' - | '/app/cluster/' - | '/app/settings/' - fileRoutesByTo: FileRoutesByTo - to: - | '/' - | '/onboarding/complete' - | '/onboarding/import' - | '/onboarding/language' - | '/onboarding/login' - | '/onboarding/summary' - | '/app' - | '/onboarding' - | '/app/cluster/screenshots' - | '/app/cluster/settings' - | '/app/cluster/worlds' - | '/app/settings/accounts' - | '/app/settings/appearance' - | '/app/settings/minecraft' - | '/app/browser' - | '/app/cluster' - | '/app/settings' - id: - | '__root__' - | '/' - | '/app' - | '/onboarding' - | '/app/cluster' - | '/app/settings' - | '/onboarding/complete' - | '/onboarding/import' - | '/onboarding/language' - | '/onboarding/login' - | '/onboarding/summary' - | '/app/' - | '/onboarding/' - | '/app/cluster/screenshots' - | '/app/cluster/settings' - | '/app/cluster/worlds' - | '/app/settings/accounts' - | '/app/settings/appearance' - | '/app/settings/minecraft' - | '/app/browser/' - | '/app/cluster/' - | '/app/settings/' - fileRoutesById: FileRoutesById -} - -export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - AppRouteRoute: typeof AppRouteRouteWithChildren - OnboardingRouteRoute: typeof OnboardingRouteRouteWithChildren -} - -const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - AppRouteRoute: AppRouteRouteWithChildren, - OnboardingRouteRoute: OnboardingRouteRouteWithChildren, -} - -export const routeTree = rootRoute - ._addFileChildren(rootRouteChildren) - ._addFileTypes() - -/* ROUTE_MANIFEST_START -{ - "routes": { - "__root__": { - "filePath": "__root.tsx", - "children": [ - "/", - "/app", - "/onboarding" - ] - }, - "/": { - "filePath": "index.tsx" - }, - "/app": { - "filePath": "app/route.tsx", - "children": [ - "/app/cluster", - "/app/settings", - "/app/", - "/app/browser/" - ] - }, - "/onboarding": { - "filePath": "onboarding/route.tsx", - "children": [ - "/onboarding/complete", - "/onboarding/import", - "/onboarding/language", - "/onboarding/login", - "/onboarding/summary", - "/onboarding/" - ] - }, - "/app/cluster": { - "filePath": "app/cluster/route.tsx", - "parent": "/app", - "children": [ - "/app/cluster/screenshots", - "/app/cluster/settings", - "/app/cluster/worlds", - "/app/cluster/" - ] - }, - "/app/settings": { - "filePath": "app/settings/route.tsx", - "parent": "/app", - "children": [ - "/app/settings/accounts", - "/app/settings/appearance", - "/app/settings/minecraft", - "/app/settings/" - ] - }, - "/onboarding/complete": { - "filePath": "onboarding/complete.tsx", - "parent": "/onboarding" - }, - "/onboarding/import": { - "filePath": "onboarding/import.tsx", - "parent": "/onboarding" - }, - "/onboarding/language": { - "filePath": "onboarding/language.tsx", - "parent": "/onboarding" - }, - "/onboarding/login": { - "filePath": "onboarding/login.tsx", - "parent": "/onboarding" - }, - "/onboarding/summary": { - "filePath": "onboarding/summary.tsx", - "parent": "/onboarding" - }, - "/app/": { - "filePath": "app/index.tsx", - "parent": "/app" - }, - "/onboarding/": { - "filePath": "onboarding/index.tsx", - "parent": "/onboarding" - }, - "/app/cluster/screenshots": { - "filePath": "app/cluster/screenshots.tsx", - "parent": "/app/cluster" - }, - "/app/cluster/settings": { - "filePath": "app/cluster/settings.tsx", - "parent": "/app/cluster" - }, - "/app/cluster/worlds": { - "filePath": "app/cluster/worlds.tsx", - "parent": "/app/cluster" - }, - "/app/settings/accounts": { - "filePath": "app/settings/accounts.tsx", - "parent": "/app/settings" - }, - "/app/settings/appearance": { - "filePath": "app/settings/appearance.tsx", - "parent": "/app/settings" - }, - "/app/settings/minecraft": { - "filePath": "app/settings/minecraft.tsx", - "parent": "/app/settings" - }, - "/app/browser/": { - "filePath": "app/browser/index.tsx", - "parent": "/app" - }, - "/app/cluster/": { - "filePath": "app/cluster/index.tsx", - "parent": "/app/cluster" - }, - "/app/settings/": { - "filePath": "app/settings/index.tsx", - "parent": "/app/settings" - } - } -} -ROUTE_MANIFEST_END */ diff --git a/packages/core/examples/metadata.rs b/packages/core/examples/metadata.rs index e6bd8f49..47fbd5f7 100644 --- a/packages/core/examples/metadata.rs +++ b/packages/core/examples/metadata.rs @@ -10,11 +10,11 @@ async fn main() -> LauncherResult<()> { println!("Initialized: {}", metadata.initialized()); println!("Minecraft: {:?}", metadata.get_vanilla_or_fetch().await.is_ok()); - println!("Forge: {:?}", metadata.get_modded_or_fetch(GameLoader::Forge).await.is_ok()); + println!("Forge: {:?}", metadata.get_modded_or_fetch(&GameLoader::Forge).await.is_ok()); println!("Initialized: {}", metadata.initialized()); - println!("NeoForge: {:?}", metadata.get_modded_or_fetch(GameLoader::NeoForge).await.is_ok()); - println!("Fabric: {:?}", metadata.get_modded_or_fetch(GameLoader::Fabric).await.is_ok()); - println!("Quilt: {:?}", metadata.get_modded_or_fetch(GameLoader::Quilt).await.is_ok()); + println!("NeoForge: {:?}", metadata.get_modded_or_fetch(&GameLoader::NeoForge).await.is_ok()); + println!("Fabric: {:?}", metadata.get_modded_or_fetch(&GameLoader::Fabric).await.is_ok()); + println!("Quilt: {:?}", metadata.get_modded_or_fetch(&GameLoader::Quilt).await.is_ok()); Ok(()) } \ No newline at end of file diff --git a/packages/core/src/api/cluster/mod.rs b/packages/core/src/api/cluster/mod.rs index fa9704d3..f565e69f 100644 --- a/packages/core/src/api/cluster/mod.rs +++ b/packages/core/src/api/cluster/mod.rs @@ -6,7 +6,6 @@ use onelauncher_entity::{clusters, java_versions}; use onelauncher_entity::icon::Icon; use onelauncher_entity::loader::GameLoader; use sea_orm::ActiveValue::Set; -use serde::Serialize; use tokio::process::Command; use crate::api::game::arguments; @@ -25,8 +24,8 @@ pub use sync::*; use super::ingress::init_ingress; -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum ClusterError { #[error("version '{0}' was not found")] InvalidVersion(String), diff --git a/packages/core/src/api/game/launch.rs b/packages/core/src/api/game/launch.rs index 9771d4e6..007c080b 100644 --- a/packages/core/src/api/game/launch.rs +++ b/packages/core/src/api/game/launch.rs @@ -119,7 +119,7 @@ pub async fn launch_minecraft( arguments::java_arguments( args.get(&interpulse::api::minecraft::ArgumentType::Jvm) .map(Vec::as_slice), - &dirs.natives_dir().join(&version_name), + &dirs.natives_dir().join(&version.id), &dirs.libraries_dir(), &arguments::classpaths( &dirs.libraries_dir(), diff --git a/packages/core/src/api/game/metadata.rs b/packages/core/src/api/game/metadata.rs index 23e38502..bf3792d8 100644 --- a/packages/core/src/api/game/metadata.rs +++ b/packages/core/src/api/game/metadata.rs @@ -386,7 +386,7 @@ pub async fn download_libraries( Ok(failed) } -// MARK: Loader Version +// MARK: Get Loader /// Gets the loader version for a given Minecraft version and loader /// If `loader_version` is `None`, it will return the latest stable version else /// it will return the specified version if found @@ -402,7 +402,7 @@ pub async fn get_loader_version( let state = State::get().await?; let metadata = state.metadata.read().await; - let manifest = metadata.get_modded(loader)?; + let manifest = metadata.get_modded(&loader)?; let Some(loaders) = manifest.game_versions.iter().find(|it| { it.id @@ -422,3 +422,23 @@ pub async fn get_loader_version( Ok(Some(loader_version)) } + +// MARK: Get Versions +/// Gets the list of Minecraft versions from the metadata store +#[tracing::instrument] +pub async fn get_game_versions() -> LauncherResult> { + let state = State::get().await?; + let metadata = state.metadata.read().await; + + let manifest = metadata.get_vanilla()?; + Ok(manifest.versions.clone()) +} + +/// Gets the list of modded loaders and their versions from the metadata store +#[tracing::instrument] +pub async fn get_loaders_for_version(mc_version: &str) -> LauncherResult> { + let state = State::get().await?; + let mut metadata = state.metadata.write().await; + + Ok(metadata.get_loaders_for_version(mc_version).await?) +} diff --git a/packages/core/src/api/java/mod.rs b/packages/core/src/api/java/mod.rs index c251797c..1d284158 100644 --- a/packages/core/src/api/java/mod.rs +++ b/packages/core/src/api/java/mod.rs @@ -12,13 +12,13 @@ use crate::utils::io; pub mod dao; -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum JavaError { #[error("failed to parse version '{0}'")] - ParseVersion(String, #[source] #[serde(skip)] std::num::ParseIntError), + ParseVersion(String, #[source] #[skip] std::num::ParseIntError), #[error("failed to execute java command")] - Execute(#[from] #[serde(skip)] std::io::Error), + Execute(#[from] #[skip] std::io::Error), #[error("no java installations found")] MissingJava, } diff --git a/packages/core/src/api/packages/mod.rs b/packages/core/src/api/packages/mod.rs index 00c6e5e1..b8c384c0 100644 --- a/packages/core/src/api/packages/mod.rs +++ b/packages/core/src/api/packages/mod.rs @@ -3,7 +3,6 @@ use onelauncher_entity::icon::Icon; use onelauncher_entity::package::PackageType; use onelauncher_entity::{clusters, packages}; use reqwest::Method; -use serde::Serialize; use crate::error::{LauncherError, LauncherResult}; use crate::store::Dirs; @@ -19,8 +18,8 @@ pub mod provider; #[cfg(test)] mod tests; -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum PackageError { #[error("no file marked for download")] NoPrimaryFile, @@ -30,8 +29,8 @@ pub enum PackageError { Incompatible(#[from] IncompatiblePackageType), } -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum IncompatiblePackageType { #[error("package is not compatible with the current version")] McVersion, diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 5d211245..92e5a9da 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -32,6 +32,13 @@ pub trait TauriLauncherApi { #[taurpc(alias = "getGlobalProfile")] async fn get_global_profile() -> SettingsProfile; + // Game Metadata + #[taurpc(alias = "getGameVersions")] + async fn get_game_versions() -> LauncherResult>; + + #[taurpc(alias = "getLoadersForVersion")] + async fn get_loaders_for_version(mc_version: String) -> LauncherResult>; + // Users #[taurpc(alias = "getUsers")] @@ -69,6 +76,8 @@ pub struct TauriLauncherApiImpl; #[taurpc::resolvers] impl TauriLauncherApi for TauriLauncherApiImpl { + + // Clusters async fn get_clusters(self) -> LauncherResult> { api::cluster::dao::get_all_clusters().await } @@ -84,13 +93,6 @@ impl TauriLauncherApi for TauriLauncherApiImpl { async fn create_cluster(self, options: CreateCluster) -> LauncherResult { let cluster = api::cluster::create_cluster(&options.name, &options.mc_version, options.mc_loader, options.mc_loader_version.as_deref(), options.icon).await?; - // if api::setting_profiles::dao::get_profile_by_name(&options.name).await?.is_none() { - // api::setting_profiles::create_profile(&options.name, async |mut profile| { - // profile.mem_max = sea_orm::ActiveValue::Set(Some(2048)); - // Ok(profile) - // }).await?; - // } - Ok(cluster) } @@ -113,14 +115,28 @@ impl TauriLauncherApi for TauriLauncherApiImpl { Ok(()) } - async fn get_profile_or_default(self, name: Option) -> LauncherResult { - api::setting_profiles::dao::get_profile_or_default(name.as_ref()).await - } + // Setting Profiles async fn get_global_profile(self) -> SettingsProfile { api::setting_profiles::get_global_profile().await } + async fn get_profile_or_default(self, name: Option) -> LauncherResult { + api::setting_profiles::dao::get_profile_or_default(name.as_ref()).await + } + + + // Game Metadata + async fn get_loaders_for_version(self, mc_version: String) -> LauncherResult> { + api::game::metadata::get_loaders_for_version(&mc_version).await + } + + async fn get_game_versions(self) -> LauncherResult> { + api::game::metadata::get_game_versions().await + } + + + // Users async fn get_users(self) -> LauncherResult> { api::credentials::get_users().await } diff --git a/packages/core/src/error.rs b/packages/core/src/error.rs index d24049f4..a2760b19 100644 --- a/packages/core/src/error.rs +++ b/packages/core/src/error.rs @@ -1,8 +1,5 @@ -use serde::Serialize; - -#[onelauncher_macro::specta] -#[derive(thiserror::Error, Debug, Serialize)] -#[serde(tag = "type", content = "data")] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum LauncherError { #[error(transparent)] DirError(#[from] crate::store::DirectoryError), @@ -30,41 +27,31 @@ pub enum LauncherError { DaoError(#[from] DaoError), #[error("json error: {0}")] - SerdeError(#[from] #[serde(skip)] serde_json::Error), + SerdeError(#[from] #[skip] serde_json::Error), #[error(transparent)] - AnyhowError(#[from] #[serde(skip)] anyhow::Error), + AnyhowError(#[from] #[skip] anyhow::Error), #[error("database error: {0}")] - DbError(#[from] #[serde(skip)] sea_orm::DbErr), + DbError(#[from] #[skip] sea_orm::DbErr), #[error("http error: {0}")] - ReqwestError(#[from] #[serde(skip)] reqwest::Error), + ReqwestError(#[from] #[skip] reqwest::Error), #[error("meta error: {0}")] - InterpulseError(#[from] #[serde(skip)] interpulse::Error), + InterpulseError(#[from] #[skip] interpulse::Error), #[error(transparent)] - RegexError(#[from] #[serde(skip)] regex::Error), + RegexError(#[from] #[skip] regex::Error), #[error("couldn't acquire semaphore: {0}")] - SemaphoreError(#[from] #[serde(skip)] tokio::sync::AcquireError), + SemaphoreError(#[from] #[skip] tokio::sync::AcquireError), #[error(transparent)] - UrlError(#[from] #[serde(skip)] url::ParseError), + UrlError(#[from] #[skip] url::ParseError), #[cfg(feature = "tauri")] #[error(transparent)] - TauriError(#[from] #[serde(skip)] tauri::Error), + TauriError(#[from] #[skip] tauri::Error), } -// impl serde::Serialize for LauncherError { -// fn serialize(&self, serializer: S) -> Result -// where -// S: serde::Serializer, -// { -// // Serialize the error as a string -// serializer.serialize_str(&self.to_string()) -// } -// } - pub type LauncherResult = Result; -#[onelauncher_macro::specta] -#[derive(thiserror::Error, Debug, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum DaoError { #[error("entity was not found")] NotFound, diff --git a/packages/core/src/store/credentials.rs b/packages/core/src/store/credentials.rs index c32d7b73..822c7349 100644 --- a/packages/core/src/store/credentials.rs +++ b/packages/core/src/store/credentials.rs @@ -1010,19 +1010,23 @@ pub enum MinecraftAuthStep { } /// Wrapper around all `Error`s that can occur during the Microsoft authentication process. -#[onelauncher_macro::specta] -#[derive(thiserror::Error, Debug, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum MinecraftAuthError { #[error("failed to read public key during key generation")] PublicKeyReading, #[error("failed to serialize private key using PKCS8: {0}")] - PKCS8Error(#[from] #[serde(skip)] p256::pkcs8::Error), + PKCS8Error( + #[from] + #[skip] + p256::pkcs8::Error + ), #[error("failed to serialize JSON during MSA step {step:?}: {source}")] SerializeError { step: MinecraftAuthStep, #[source] - #[serde(skip)] + #[skip] source: serde_json::Error, }, #[error( @@ -1033,24 +1037,24 @@ pub enum MinecraftAuthError { raw: String, #[source] - #[serde(skip)] + #[skip] source: serde_json::Error, - #[serde(skip)] + #[skip] status_code: reqwest::StatusCode, }, #[error("failed to request using HTTP during MSA step {step:?}: {source}")] RequestError { step: MinecraftAuthStep, #[source] - #[serde(skip)] + #[skip] source: reqwest::Error, }, #[error("failed to create signed buffer during MSA step {step:?}: {source}")] SigningError { step: MinecraftAuthStep, #[source] - #[serde(skip)] + #[skip] source: std::io::Error, }, #[error("failed to read user hash")] diff --git a/packages/core/src/store/dirs.rs b/packages/core/src/store/dirs.rs index 7300149f..009705cc 100644 --- a/packages/core/src/store/dirs.rs +++ b/packages/core/src/store/dirs.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use onelauncher_entity::package::{PackageType, Provider}; -use serde::Serialize; use tokio::sync::OnceCell; use crate::{utils, LauncherResult}; @@ -16,8 +15,8 @@ pub struct Dirs { base_dir: PathBuf, } -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum DirectoryError { #[error("Failed to get the base directory for the launcher.")] BaseDir, diff --git a/packages/core/src/store/discord.rs b/packages/core/src/store/discord.rs index 4c3134ce..d8e58b15 100644 --- a/packages/core/src/store/discord.rs +++ b/packages/core/src/store/discord.rs @@ -1,7 +1,6 @@ use std::sync::{atomic::AtomicBool, Arc}; use discord_rich_presence::{activity::{Activity, Assets, Button, Timestamps}, DiscordIpc, DiscordIpcClient}; -use serde::Serialize; use tokio::sync::RwLock; use crate::error::LauncherResult; @@ -107,8 +106,8 @@ impl DiscordRPC { } } -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum DiscordError { #[error("Discord client ID is missing")] MissingClientId, diff --git a/packages/core/src/store/ingress.rs b/packages/core/src/store/ingress.rs index 77562965..10184951 100644 --- a/packages/core/src/store/ingress.rs +++ b/packages/core/src/store/ingress.rs @@ -15,8 +15,8 @@ pub struct IngressProcessor { ingress_feeds: RwLock>, } -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum IngressError { #[error("ingress not found")] NotFound, diff --git a/packages/core/src/store/metadata.rs b/packages/core/src/store/metadata.rs index 228539d9..e05b6e92 100644 --- a/packages/core/src/store/metadata.rs +++ b/packages/core/src/store/metadata.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::str::FromStr; use interpulse::api::minecraft::VersionManifest as VanillaManifest; @@ -8,7 +9,7 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use crate::constants; -use crate::error::LauncherResult; +use crate::error::{LauncherError, LauncherResult}; use crate::utils::http::fetch_json; use crate::utils::io; @@ -19,6 +20,7 @@ use super::Dirs; pub struct Metadata { initialized: bool, inner: MetadataInner, + version_loader_cache: HashMap> } #[derive(Debug, Default, Serialize, Deserialize)] @@ -31,8 +33,8 @@ struct MetadataInner { // legacyfabric: Option, } -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum MetadataError { #[error("failed to fetch metadata")] FetchError, @@ -41,7 +43,11 @@ pub enum MetadataError { #[error("loader {0} does not use a vanilla manifest")] NotVanillaManifest(GameLoader), #[error("failed to parse metadata: {0}")] - ParseError(#[from] #[serde(skip)] serde_json::Error), + ParseError( + #[from] + #[skip] + serde_json::Error + ), #[error("no matching loader found")] NoMatchingLoader, #[error("no matching version found")] @@ -70,9 +76,9 @@ impl Metadata { } #[tracing::instrument(skip(self))] - pub async fn get_modded_or_fetch(&mut self, loader: GameLoader) -> LauncherResult<&ModdedManifest> { + pub async fn get_modded_or_fetch(&mut self, loader: &GameLoader) -> LauncherResult<&ModdedManifest> { if !loader.is_modded() { - return Err(MetadataError::NotModdedManifest(loader).into()); + return Err(MetadataError::NotModdedManifest(*loader).into()); } if !self.initialized() { @@ -86,9 +92,9 @@ impl Metadata { self.inner.minecraft.as_ref().ok_or_else(|| MetadataError::FetchError.into()) } - pub fn get_modded(&self, loader: GameLoader) -> LauncherResult<&ModdedManifest> { + pub fn get_modded(&self, loader: &GameLoader) -> LauncherResult<&ModdedManifest> { if !loader.is_modded() { - return Err(MetadataError::NotModdedManifest(loader).into()); + return Err(MetadataError::NotModdedManifest(*loader).into()); } match loader { @@ -215,17 +221,48 @@ impl Metadata { (quilt, true) }; } + + /// Returns a list of loaders that are compatible with the given Minecraft version. + /// + /// WARNING: Can be heavy in certain cases! + pub async fn get_loaders_for_version(&mut self, mc_version: &str) -> LauncherResult> { + if !self.initialized() { + self.initialize().await?; + } + + if let Some(hit) = self.version_loader_cache.get(mc_version) { + return Ok(hit.clone()); + } + + let mut loaders = Vec::::new(); + for loader in GameLoader::modded_loaders() { + let manifest = match self.get_modded(loader) { + Ok(manifest) => manifest, + Err(LauncherError::MetadataError(MetadataError::NotModdedManifest(_))) => continue, + Err(e) => return Err(e), + }; + + let found = manifest.game_versions.iter().find(|it| it.id == mc_version); + if found.is_some() { + loaders.push(*loader); + } + } + + self.version_loader_cache.insert(mc_version.to_owned(), loaders.clone()); + + Ok(loaders) + } } -pub async fn fetch_vanilla_manifest(loader: GameLoader) -> LauncherResult { +async fn fetch_vanilla_manifest(loader: GameLoader) -> LauncherResult { fetch_manifest(loader).await } -pub async fn fetch_modded_manifest(loader: GameLoader) -> LauncherResult { +async fn fetch_modded_manifest(loader: GameLoader) -> LauncherResult { fetch_manifest(loader).await } -pub async fn fetch_manifest(loader: GameLoader) -> LauncherResult { +async fn fetch_manifest(loader: GameLoader) -> LauncherResult { fetch_json::( Method::GET, format!( diff --git a/packages/core/src/store/processes.rs b/packages/core/src/store/processes.rs index c111771f..a3790d2c 100644 --- a/packages/core/src/store/processes.rs +++ b/packages/core/src/store/processes.rs @@ -24,8 +24,8 @@ use crate::utils::io::IOError; use super::State; use super::credentials::MinecraftCredentials; -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum ProcessError { #[error("launch hook exited with non-zero code {0}")] HookUnsuccessful(i32), diff --git a/packages/core/src/utils/crypto.rs b/packages/core/src/utils/crypto.rs index 1daa5134..5687c906 100644 --- a/packages/core/src/utils/crypto.rs +++ b/packages/core/src/utils/crypto.rs @@ -8,8 +8,8 @@ use tokio::io::AsyncReadExt; use super::io::IOError; -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum CryptoError { #[error("expected {algorithm} hash {expected}, but got {actual}")] InvalidHash { diff --git a/packages/core/src/utils/io.rs b/packages/core/src/utils/io.rs index cb376e2d..34702c61 100644 --- a/packages/core/src/utils/io.rs +++ b/packages/core/src/utils/io.rs @@ -13,8 +13,8 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio_util::compat::TokioAsyncWriteCompatExt; /// A wrapper around generic and unhelpful [`std::io::Error`] messages. -#[onelauncher_macro::specta] -#[derive(Debug, thiserror::Error, Serialize)] +#[onelauncher_macro::error] +#[derive(Debug, thiserror::Error)] pub enum IOError { #[error("invalid absolute path '{0}'")] InvalidAbsolutePath(PathBuf), @@ -22,16 +22,16 @@ pub enum IOError { #[error("error acessing path: {source}, path: {path}")] IOErrorWrapper { #[source] - #[serde(skip)] + #[skip] source: std::io::Error, path: String, }, #[error(transparent)] - IOError(#[from] #[serde(skip)] std::io::Error), + IOError(#[from] #[skip] std::io::Error), #[error("json deserialization error: {0}")] - DeserializeError(#[from] #[serde(skip)] serde_json::Error), + DeserializeError(#[from] #[skip] serde_json::Error), #[error(transparent)] - AsyncZipError(#[from] #[serde(skip)] async_zip::error::ZipError), + AsyncZipError(#[from] #[skip] async_zip::error::ZipError), } impl> From<(P, std::io::Error)> for IOError { diff --git a/packages/entity/src/domain/loader.rs b/packages/entity/src/domain/loader.rs index 14dc3322..fa4dca20 100644 --- a/packages/entity/src/domain/loader.rs +++ b/packages/entity/src/domain/loader.rs @@ -55,6 +55,16 @@ impl GameLoader { } } + pub const fn modded_loaders() -> &'static [Self] { + &[ + Self::Forge, + Self::NeoForge, + Self::Quilt, + Self::Fabric, + Self::LegacyFabric, + ] + } + } impl TryFrom for GameLoader { diff --git a/packages/macro/src/error.rs b/packages/macro/src/error.rs new file mode 100644 index 00000000..0d98cbd3 --- /dev/null +++ b/packages/macro/src/error.rs @@ -0,0 +1,179 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, Data, DeriveInput, Fields}; + +pub fn error_attr(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + + let Data::Enum(data_enum) = &input.data else { + return syn::Error::new_spanned(&input.ident, "Only enums are supported") + .to_compile_error() + .into(); + }; + + let ident = &input.ident; + let mut serialize_arms = Vec::new(); + + for variant in &data_enum.variants { + let v_ident = &variant.ident; + match &variant.fields { + Fields::Unit => { + serialize_arms.push(quote! { + Self::#v_ident => { + let mut s = serializer.serialize_struct(stringify!(#ident), 2)?; + s.serialize_field("type", stringify!(#v_ident))?; + s.serialize_field("data", &self.to_string())?; + s.end() + } + }); + } + Fields::Unnamed(fields_unnamed) => { + if fields_unnamed.unnamed.len() == 1 { + let field = &fields_unnamed.unnamed[0]; + let mut has_from = false; + let mut has_skip = false; + + for attr in &field.attrs { + if attr.path().is_ident("from") { + has_from = true; + } else if attr.path().is_ident("skip") { + has_skip = true; + } + } + + if has_from && !has_skip { + serialize_arms.push(quote! { + Self::#v_ident(inner) => { + let mut s = serializer.serialize_struct(stringify!(#ident), 2)?; + s.serialize_field("type", stringify!(#v_ident))?; + s.serialize_field("data", inner)?; + s.end() + } + }); + } else { + serialize_arms.push(quote! { + Self::#v_ident(_) => { + let mut s = serializer.serialize_struct(stringify!(#ident), 2)?; + s.serialize_field("type", stringify!(#v_ident))?; + s.serialize_field("data", &self.to_string())?; + s.end() + } + }); + } + } else { + serialize_arms.push(quote! { + Self::#v_ident(..) => { + let mut s = serializer.serialize_struct(stringify!(#ident), 2)?; + s.serialize_field("type", stringify!(#v_ident))?; + s.serialize_field("data", &self.to_string())?; + s.end() + } + }); + } + } + Fields::Named(_) => { + serialize_arms.push(quote! { + Self::#v_ident { .. } => { + let mut s = serializer.serialize_struct(stringify!(#ident), 2)?; + s.serialize_field("type", stringify!(#v_ident))?; + s.serialize_field("data", &self.to_string())?; + s.end() + } + }); + } + } + } + + let serialize_impl = quote! { + impl serde::Serialize for #ident { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use serde::ser::SerializeStruct; + match self { + #(#serialize_arms),* + } + } + } + }; + + let output = quote! { + #[cfg_attr(feature = "specta", derive(onelauncher_macro::SerializedError))] + #input + + #serialize_impl + }; + + output.into() +} + +pub fn error_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let enum_name = &input.ident; + let specta_name = format_ident!("{}SpectaType", enum_name); + let mut variants = vec![]; + + let Data::Enum(data_enum) = &input.data else { + return syn::Error::new_spanned(enum_name, "Only enums are supported") + .to_compile_error() + .into(); + }; + + for variant in &data_enum.variants { + let name = &variant.ident; + let ty = match &variant.fields { + Fields::Unit => quote! { String }, + + Fields::Unnamed(fields) => { + // Handle newtype-style or tuple-like enums + if fields.unnamed.len() == 1 { + let field = &fields.unnamed[0]; + let field_ty = &field.ty; + + let has_from = field + .attrs + .iter() + .any(|a| a.path().is_ident("from")); + + let has_skip = field + .attrs + .iter() + .any(|a| a.path().is_ident("skip")); + + if has_from && !has_skip { + quote! { + #field_ty + } + } else { + quote! { String } + } + } else { + quote! { String } + } + } + + Fields::Named(_) => { + quote! { String } // Simplification: treat all struct-like variants as String + } + }; + + variants.push(quote! { + #name(#ty) + }); + } + + let enum_name_str = enum_name.to_string(); + let output = quote! { + #[derive(Debug, specta::Type, serde::Serialize)] + #[specta(remote = #enum_name)] + #[specta(tag = "type", content = "data")] + #[specta(rename = #enum_name_str)] + pub enum #specta_name { + #(#variants,)* + } + }; + + output.into() +} \ No newline at end of file diff --git a/packages/macro/src/lib.rs b/packages/macro/src/lib.rs index 584d54b0..89cd0997 100644 --- a/packages/macro/src/lib.rs +++ b/packages/macro/src/lib.rs @@ -2,7 +2,7 @@ mod specta; #[cfg(feature = "tauri")] -mod tauri; +mod error; mod pin; @@ -14,13 +14,13 @@ mod pin; /// #[cfg_attr(feature = "specta", derive(specta::Type))] /// ``` /// -/// ## `with_event` argument -/// Add `#[specta(with_event)]` to add the `tauri_specta::Event` derive. +/// ## `rename` attribute +/// Add `rename = ''` attribute to rename the type. /// /// #### Expands to: /// ``` /// #[cfg_attr(feature = "specta", derive(specta::Type))] -/// #[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] +/// #[cfg_attr(feature = "specta", specta(rename = #rename))] /// ``` #[proc_macro_attribute] #[cfg(feature = "specta")] @@ -28,16 +28,16 @@ pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> p specta::specta(attr, item) } -/// This macro is used to derive the `tauri::command` as well as the `specta::specta` attribute for the annotated item. -/// #### Expands to: -/// ``` -/// #[specta::specta] -/// #[tauri::command] -/// ``` +/// This macro is used to implement the `serde::Serialize` trait for the annotated item. +/// It only works for enums and tries to serialize them so that taurpc can use them properly. +#[proc_macro_derive(SerializedError, attributes(from, skip))] +pub fn serialized_error_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + error::error_derive(item) +} + #[proc_macro_attribute] -#[cfg(feature = "tauri")] -pub fn command(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { - tauri::tauri_command(attr, item) +pub fn error(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + error::error_attr(attr, item) } /// This macro Box::Pin a function diff --git a/packages/macro/src/tauri.rs b/packages/macro/src/tauri.rs deleted file mode 100644 index 3600a859..00000000 --- a/packages/macro/src/tauri.rs +++ /dev/null @@ -1,14 +0,0 @@ -use quote::quote; -use syn::{parse_macro_input, Item}; - -pub fn tauri_command(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(item as Item); - - let expanded = quote! { - #[specta::specta] - #[tauri::command] - #input - }; - - expanded.into() -} \ No newline at end of file diff --git a/packages/utils/src/io.rs b/packages/utils/src/io.rs index 68a2b610..401cfcf0 100644 --- a/packages/utils/src/io.rs +++ b/packages/utils/src/io.rs @@ -10,6 +10,7 @@ use tokio::task::spawn_blocking; /// A wrapper around generic and unhelpful [`std::io::Error`] messages. #[derive(Debug, thiserror::Error)] +#[serde(tag = "type", content = "data")] pub enum IOError { /// A wrapped [`std::io::Error`] along with the path involved in the error. #[error("error acessing path: {source}, path: {path}")] @@ -36,6 +37,7 @@ impl> From<(P, std::io::Error)> for IOError { } #[derive(Debug, thiserror::Error)] +#[serde(tag = "type", content = "data")] #[error("received a non UTF-8 path: ", .0.to_string_lossy())] pub struct NonUtf8PathError(pub Box); diff --git a/test.rs b/test.rs new file mode 100644 index 00000000..e69de29b