diff --git a/.cargo/config.toml b/.cargo/config.toml index 7115f0015..085f3158f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,5 +2,8 @@ [target.'cfg(windows)'] rustflags = ["-C", "link-args=/STACK:16777220", "--cfg", "tokio_unstable"] +[target.x86_64-pc-windows-msvc] +linker = "rust-lld" + [build] rustflags = ["--cfg", "tokio_unstable"] diff --git a/.github/workflows/daedalus-docker.yml b/.github/workflows/daedalus-docker.yml index 0dda82541..b0f72c964 100644 --- a/.github/workflows/daedalus-docker.yml +++ b/.github/workflows/daedalus-docker.yml @@ -22,23 +22,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 - name: Fetch docker metadata id: docker_meta - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v5 with: images: ghcr.io/modrinth/daedalus - name: Login to GitHub Images - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: file: ./apps/daedalus_client/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=registry,ref=ghcr.io/modrinth/daedalus:main + cache-to: type=inline diff --git a/.github/workflows/labrinth-docker.yml b/.github/workflows/labrinth-docker.yml index 114c8ee48..43577e662 100644 --- a/.github/workflows/labrinth-docker.yml +++ b/.github/workflows/labrinth-docker.yml @@ -20,23 +20,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 - name: Fetch docker metadata id: docker_meta - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v5 with: images: ghcr.io/modrinth/labrinth - name: Login to GitHub Images - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: file: ./apps/labrinth/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=registry,ref=ghcr.io/modrinth/labrinth:main + cache-to: type=inline diff --git a/.github/workflows/theseus-build.yml b/.github/workflows/theseus-build.yml index 76ae5f900..64ae2b334 100644 --- a/.github/workflows/theseus-build.yml +++ b/.github/workflows/theseus-build.yml @@ -75,7 +75,7 @@ jobs: rename-to: ${{ startsWith(matrix.platform, 'windows') && 'dasel.exe' || 'dasel' }} chmod: 0755 - - name: ⚙️ Set application version + - name: ⚙️ Set application version and environment shell: bash run: | APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')" @@ -84,6 +84,8 @@ jobs: dasel put -f packages/app-lib/Cargo.toml -t string -v "${APP_VERSION#v}" 'package.version' dasel put -f apps/app-frontend/package.json -t string -v "${APP_VERSION#v}" 'version' + cp packages/app-lib/.env.prod packages/app-lib/.env + - name: 💨 Setup Turbo cache uses: rharkor/caching-for-turbo@v1.8 diff --git a/.github/workflows/turbo-ci.yml b/.github/workflows/turbo-ci.yml index 82e9f333a..fda114e5e 100644 --- a/.github/workflows/turbo-ci.yml +++ b/.github/workflows/turbo-ci.yml @@ -52,7 +52,7 @@ jobs: # cargo-binstall does not have pre-built binaries for sqlx-cli, so we fall # back to a cached cargo install - name: 🧰 Setup cargo-sqlx - uses: AlexTMjugador/cache-cargo-install-action@feat/features-support + uses: taiki-e/cache-cargo-install-action@v2 with: tool: sqlx-cli locked: false @@ -74,6 +74,10 @@ jobs: cp .env.local .env sqlx database setup + - name: ⚙️ Set app environment + working-directory: packages/app-lib + run: cp .env.staging .env + - name: 🔍 Lint and test run: pnpm run ci diff --git a/Cargo.lock b/Cargo.lock index f6519c3b1..66073b932 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5731,6 +5731,17 @@ dependencies = [ "phf_shared 0.11.3", ] +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_macros 0.12.1", + "phf_shared 0.12.1", + "serde", +] + [[package]] name = "phf_codegen" version = "0.8.0" @@ -5781,6 +5792,16 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b" +dependencies = [ + "fastrand 2.3.0", + "phf_shared 0.12.1", +] + [[package]] name = "phf_macros" version = "0.10.0" @@ -5808,6 +5829,19 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "phf_macros" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713258393a82f091ead52047ca779d37e5766226d009de21696c4e667044368" +dependencies = [ + "phf_generator 0.12.1", + "phf_shared 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -5835,6 +5869,15 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -8930,6 +8973,7 @@ dependencies = [ "data-url", "dirs", "discord-rich-presence", + "dotenvy", "dunce", "either", "encoding_rs", @@ -8945,6 +8989,7 @@ dependencies = [ "notify-debouncer-mini", "p256", "paste", + "phf 0.12.1", "png", "quartz_nbt", "quick-xml 0.37.5", @@ -8984,6 +9029,8 @@ dependencies = [ "dashmap", "either", "enumset", + "hyper 1.6.0", + "hyper-util", "native-dialog", "paste", "serde", diff --git a/Cargo.toml b/Cargo.toml index d95e9b601..b3c0bfa71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ heck = "0.5.0" hex = "0.4.3" hickory-resolver = "0.25.2" hmac = "0.12.1" +hyper = "1.6.0" hyper-rustls = { version = "0.27.7", default-features = false, features = [ "http1", "native-tokio", @@ -98,6 +99,7 @@ notify = { version = "8.0.0", default-features = false } notify-debouncer-mini = { version = "0.6.0", default-features = false } p256 = "0.13.2" paste = "1.0.15" +phf = { version = "0.12.1", features = ["macros"] } png = "0.17.16" prometheus = "0.14.0" quartz_nbt = "0.2.9" diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 1bc25942c..abfabdf52 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -61,9 +61,10 @@ import { renderString } from '@modrinth/utils' import { useFetch } from '@/helpers/fetch.js' import { check } from '@tauri-apps/plugin-updater' import NavButton from '@/components/ui/NavButton.vue' -import { get as getCreds, login, logout } from '@/helpers/mr_auth.js' +import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js' import { get_user } from '@/helpers/cache.js' import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue' +import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue' import PromotionWrapper from '@/components/ui/PromotionWrapper.vue' import { hide_ads_window, init_ads_window } from '@/helpers/ads.js' import FriendsList from '@/components/ui/friends/FriendsList.vue' @@ -263,6 +264,8 @@ const incompatibilityWarningModal = ref() const credentials = ref() +const modrinthLoginFlowWaitModal = ref() + async function fetchCredentials() { const creds = await getCreds().catch(handleError) if (creds && creds.user_id) { @@ -272,8 +275,24 @@ async function fetchCredentials() { } async function signIn() { - await login().catch(handleError) - await fetchCredentials() + modrinthLoginFlowWaitModal.value.show() + + try { + await login() + await fetchCredentials() + } catch (error) { + if ( + typeof error === 'object' && + typeof error['message'] === 'string' && + error.message.includes('Login canceled') + ) { + // Not really an error due to being a result of user interaction, show nothing + } else { + handleError(error) + } + } finally { + modrinthLoginFlowWaitModal.value.hide() + } } async function logOut() { @@ -402,6 +421,9 @@ function handleAuxClick(e) { + + + diff --git a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue index c09255a7c..ee6328ff0 100644 --- a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue +++ b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue @@ -305,12 +305,16 @@ const [ get_game_versions().then(shallowRef).catch(handleError), get_loaders() .then((value) => - value - .filter((item) => item.supported_project_types.includes('modpack')) - .map((item) => item.name.toLowerCase()), + ref( + value + .filter((item) => item.supported_project_types.includes('modpack')) + .map((item) => item.name.toLowerCase()), + ), ) - .then(ref) - .catch(handleError), + .catch((err) => { + handleError(err) + return ref([]) + }), ]) loaders.value.unshift('vanilla') diff --git a/apps/app-frontend/src/components/ui/modal/AuthGrantFlowWaitModal.vue b/apps/app-frontend/src/components/ui/modal/AuthGrantFlowWaitModal.vue new file mode 100644 index 000000000..3f169faff --- /dev/null +++ b/apps/app-frontend/src/components/ui/modal/AuthGrantFlowWaitModal.vue @@ -0,0 +1,42 @@ + + + + + + Sign in + + + + + + + + Please sign in at the browser window that just opened to continue. + + + diff --git a/apps/app-frontend/src/components/ui/world/RecentWorldsList.vue b/apps/app-frontend/src/components/ui/world/RecentWorldsList.vue index a960f805f..5f80a194f 100644 --- a/apps/app-frontend/src/components/ui/world/RecentWorldsList.vue +++ b/apps/app-frontend/src/components/ui/world/RecentWorldsList.vue @@ -1,5 +1,6 @@ diff --git a/apps/app/src/api/oauth_utils/mod.rs b/apps/app/src/api/oauth_utils/mod.rs new file mode 100644 index 000000000..4182cfb6c --- /dev/null +++ b/apps/app/src/api/oauth_utils/mod.rs @@ -0,0 +1,3 @@ +//! Assorted utilities for OAuth 2.0 authorization flows. + +pub mod auth_code_reply; diff --git a/apps/app/src/api/worlds.rs b/apps/app/src/api/worlds.rs index 544ce05d6..103ecdf38 100644 --- a/apps/app/src/api/worlds.rs +++ b/apps/app/src/api/worlds.rs @@ -5,8 +5,8 @@ use tauri::{AppHandle, Manager, Runtime}; use theseus::prelude::ProcessMetadata; use theseus::profile::{QuickPlayType, get_full_path}; use theseus::worlds::{ - DisplayStatus, ServerPackStatus, ServerStatus, World, WorldType, - WorldWithProfile, + DisplayStatus, ProtocolVersion, ServerPackStatus, ServerStatus, World, + WorldType, WorldWithProfile, }; use theseus::{profile, worlds}; @@ -183,14 +183,16 @@ pub async fn remove_server_from_profile( } #[tauri::command] -pub async fn get_profile_protocol_version(path: &str) -> Result> { +pub async fn get_profile_protocol_version( + path: &str, +) -> Result> { Ok(worlds::get_profile_protocol_version(path).await?) } #[tauri::command] pub async fn get_server_status( address: &str, - protocol_version: Option, + protocol_version: Option, ) -> Result { Ok(worlds::get_server_status(address, protocol_version).await?) } diff --git a/apps/app/tauri.conf.json b/apps/app/tauri.conf.json index 724e536d8..8667de5c6 100644 --- a/apps/app/tauri.conf.json +++ b/apps/app/tauri.conf.json @@ -63,6 +63,7 @@ "height": 800, "resizable": true, "title": "Modrinth App", + "label": "main", "width": 1280, "minHeight": 700, "minWidth": 1100, diff --git a/apps/daedalus_client/Dockerfile b/apps/daedalus_client/Dockerfile index 9ea70f9ca..271c829aa 100644 --- a/apps/daedalus_client/Dockerfile +++ b/apps/daedalus_client/Dockerfile @@ -1,9 +1,19 @@ +# syntax=docker/dockerfile:1 + FROM rust:1.88.0 AS build WORKDIR /usr/src/daedalus COPY . . -RUN cargo build --release --package daedalus_client +RUN --mount=type=cache,target=/usr/src/daedalus/target \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry \ + cargo build --release --package daedalus_client +FROM build AS artifacts + +RUN --mount=type=cache,target=/usr/src/daedalus/target \ + mkdir /daedalus \ + && cp /usr/src/daedalus/target/release/daedalus_client /daedalus/daedalus_client FROM debian:bookworm-slim @@ -11,7 +21,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates openssl \ && rm -rf /var/lib/apt/lists/* -COPY --from=build /usr/src/daedalus/target/release/daedalus_client /daedalus/daedalus_client -WORKDIR /daedalus_client +COPY --from=artifacts /daedalus /daedalus -CMD /daedalus/daedalus_client +WORKDIR /daedalus_client +CMD ["/daedalus/daedalus_client"] diff --git a/apps/frontend/src/components/ui/NewsletterButton.vue b/apps/frontend/src/components/ui/NewsletterButton.vue index 61778eaa0..43d59b76a 100644 --- a/apps/frontend/src/components/ui/NewsletterButton.vue +++ b/apps/frontend/src/components/ui/NewsletterButton.vue @@ -1,29 +1,28 @@ - + Subscribe Subscribed! diff --git a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue index 124a49336..cdb97d7c0 100644 --- a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue +++ b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue @@ -8,7 +8,7 @@ Loading data... - All permissions obtained. You may skip this step! + All permissions already obtained. @@ -157,7 +157,7 @@ import type { } from "@modrinth/utils"; import { ButtonStyled } from "@modrinth/ui"; import { ref, computed, watch, onMounted } from "vue"; -import { useLocalStorage } from "@vueuse/core"; +import { useLocalStorage, useSessionStorage } from "@vueuse/core"; const props = defineProps<{ projectId: string; @@ -182,7 +182,26 @@ const persistedModPackData = useLocalStorage( const persistedIndex = useLocalStorage(`modpack-permissions-index-${props.projectId}`, 0); -const modPackData = ref(null); +const modPackData = useSessionStorage( + `modpack-permissions-data-${props.projectId}`, + null, + { + serializer: { + read: (v: any) => (v ? JSON.parse(v) : null), + write: (v: any) => JSON.stringify(v), + }, + }, +); +const permanentNoFiles = useSessionStorage( + `modpack-permissions-permanent-no-${props.projectId}`, + [], + { + serializer: { + read: (v: any) => (v ? JSON.parse(v) : []), + write: (v: any) => JSON.stringify(v), + }, + }, +); const currentIndex = ref(0); const fileApprovalTypes: ModerationModpackPermissionApprovalType[] = [ @@ -251,7 +270,45 @@ async function fetchModPackData(): Promise { const data = (await useBaseFetch(`moderation/project/${props.projectId}`, { internal: true, })) as ModerationModpackResponse; + + const permanentNoItems: ModerationModpackItem[] = Object.entries(data.identified || {}) + .filter(([_, file]) => file.status === "permanent-no") + .map( + ([sha1, file]): ModerationModpackItem => ({ + sha1, + file_name: file.file_name, + type: "identified", + status: file.status, + approved: null, + }), + ) + .sort((a, b) => a.file_name.localeCompare(b.file_name)); + + permanentNoFiles.value = permanentNoItems; + const sortedData: ModerationModpackItem[] = [ + ...Object.entries(data.identified || {}) + .filter( + ([_, file]) => + file.status !== "yes" && + file.status !== "with-attribution-and-source" && + file.status !== "permanent-no", + ) + .map( + ([sha1, file]): ModerationModpackItem => ({ + sha1, + file_name: file.file_name, + type: "identified", + status: file.status, + approved: null, + ...(file.status === "unidentified" && { + proof: "", + url: "", + title: "", + }), + }), + ) + .sort((a, b) => a.file_name.localeCompare(b.file_name)), ...Object.entries(data.unknown_files || {}) .map( ([sha1, fileName]): ModerationUnknownModpackItem => ({ @@ -310,6 +367,7 @@ async function fetchModPackData(): Promise { } catch (error) { console.error("Failed to fetch modpack data:", error); modPackData.value = []; + permanentNoFiles.value = []; persistAll(); } } @@ -321,6 +379,14 @@ function goToPrevious(): void { } } +watch( + modPackData, + (newValue) => { + persistedModPackData.value = newValue; + }, + { deep: true }, +); + function goToNext(): void { if (modPackData.value && currentIndex.value < modPackData.value.length) { currentIndex.value++; @@ -396,6 +462,17 @@ onMounted(() => { } }); +watch( + modPackData, + (newValue) => { + if (newValue && newValue.length === 0) { + emit("complete"); + clearPersistedData(); + } + }, + { immediate: true }, +); + watch( () => props.projectId, () => { @@ -406,6 +483,20 @@ watch( } }, ); + +function getModpackFiles(): { + interactive: ModerationModpackItem[]; + permanentNo: ModerationModpackItem[]; +} { + return { + interactive: modPackData.value || [], + permanentNo: permanentNoFiles.value, + }; +} + +defineExpose({ + getModpackFiles, +});
+ Please sign in at the browser window that just opened to continue. +
All permissions obtained. You may skip this step!
All permissions already obtained.