feat(app): configurable Modrinth endpoints through .env files (#4015)

This commit is contained in:
Alejandro González 2025-07-22 00:55:57 +02:00 committed by GitHub
parent 87de47fe5e
commit d4516d3527
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 95 additions and 49 deletions

View File

@ -75,7 +75,7 @@ jobs:
rename-to: ${{ startsWith(matrix.platform, 'windows') && 'dasel.exe' || 'dasel' }} rename-to: ${{ startsWith(matrix.platform, 'windows') && 'dasel.exe' || 'dasel' }}
chmod: 0755 chmod: 0755
- name: ⚙️ Set application version - name: ⚙️ Set application version and environment
shell: bash shell: bash
run: | run: |
APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')" 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 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' 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 - name: 💨 Setup Turbo cache
uses: rharkor/caching-for-turbo@v1.8 uses: rharkor/caching-for-turbo@v1.8

View File

@ -74,6 +74,10 @@ jobs:
cp .env.local .env cp .env.local .env
sqlx database setup sqlx database setup
- name: ⚙️ Set app environment
working-directory: packages/app-lib
run: cp .env.staging .env
- name: 🔍 Lint and test - name: 🔍 Lint and test
run: pnpm run ci run: pnpm run ci

1
Cargo.lock generated
View File

@ -8930,6 +8930,7 @@ dependencies = [
"data-url", "data-url",
"dirs", "dirs",
"discord-rich-presence", "discord-rich-presence",
"dotenvy",
"dunce", "dunce",
"either", "either",
"encoding_rs", "encoding_rs",

View File

@ -1,2 +1,10 @@
# SQLite database file location MODRINTH_URL=http://localhost:3000/
DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db MODRINTH_API_URL=http://127.0.0.1:8000/v2/
MODRINTH_API_URL_V3=http://127.0.0.1:8000/v3/
MODRINTH_SOCKET_URL=ws://127.0.0.1:8000/
MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/
# SQLite database file used by sqlx for type checking. Uncomment this to a valid path
# in your system and run `cargo sqlx database setup` to generate an empty database that
# can be used for developing the app DB schema
#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db

View File

@ -0,0 +1,10 @@
MODRINTH_URL=https://modrinth.com/
MODRINTH_API_URL=https://api.modrinth.com/v2/
MODRINTH_API_URL_V3=https://api.modrinth.com/v3/
MODRINTH_SOCKET_URL=wss://api.modrinth.com/
MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/
# SQLite database file used by sqlx for type checking. Uncomment this to a valid path
# in your system and run `cargo sqlx database setup` to generate an empty database that
# can be used for developing the app DB schema
#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db

View File

@ -0,0 +1,10 @@
MODRINTH_URL=https://staging.modrinth.com/
MODRINTH_API_URL=https://staging-api.modrinth.com/v2/
MODRINTH_API_URL_V3=https://staging-api.modrinth.com/v3/
MODRINTH_SOCKET_URL=wss://staging-api.modrinth.com/
MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/
# SQLite database file used by sqlx for type checking. Uncomment this to a valid path
# in your system and run `cargo sqlx database setup` to generate an empty database that
# can be used for developing the app DB schema
#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db

View File

@ -82,6 +82,7 @@ ariadne.workspace = true
winreg.workspace = true winreg.workspace = true
[build-dependencies] [build-dependencies]
dotenvy.workspace = true
dunce.workspace = true dunce.workspace = true
[features] [features]

View File

@ -4,12 +4,31 @@ use std::process::{Command, exit};
use std::{env, fs}; use std::{env, fs};
fn main() { fn main() {
println!("cargo::rerun-if-changed=.env");
println!("cargo::rerun-if-changed=java/gradle"); println!("cargo::rerun-if-changed=java/gradle");
println!("cargo::rerun-if-changed=java/src"); println!("cargo::rerun-if-changed=java/src");
println!("cargo::rerun-if-changed=java/build.gradle.kts"); println!("cargo::rerun-if-changed=java/build.gradle.kts");
println!("cargo::rerun-if-changed=java/settings.gradle.kts"); println!("cargo::rerun-if-changed=java/settings.gradle.kts");
println!("cargo::rerun-if-changed=java/gradle.properties"); println!("cargo::rerun-if-changed=java/gradle.properties");
set_env();
build_java_jars();
}
fn set_env() {
for (var_name, var_value) in
dotenvy::dotenv_iter().into_iter().flatten().flatten()
{
if var_name == "DATABASE_URL" {
// The sqlx database URL is a build-time detail that should not be exposed to the crate
continue;
}
println!("cargo::rustc-env={var_name}={var_value}");
}
}
fn build_java_jars() {
let out_dir = let out_dir =
dunce::canonicalize(PathBuf::from(env::var_os("OUT_DIR").unwrap())) dunce::canonicalize(PathBuf::from(env::var_os("OUT_DIR").unwrap()))
.unwrap(); .unwrap();
@ -37,6 +56,7 @@ fn main() {
.current_dir(dunce::canonicalize("java").unwrap()) .current_dir(dunce::canonicalize("java").unwrap())
.status() .status()
.expect("Failed to wait on Gradle build"); .expect("Failed to wait on Gradle build");
if !exit_status.success() { if !exit_status.success() {
println!("cargo::error=Gradle build failed with {exit_status}"); println!("cargo::error=Gradle build failed with {exit_status}");
exit(exit_status.code().unwrap_or(1)); exit(exit_status.code().unwrap_or(1));

View File

@ -1,7 +1,7 @@
use crate::state::ModrinthCredentials; use crate::state::ModrinthCredentials;
#[tracing::instrument] #[tracing::instrument]
pub fn authenticate_begin_flow() -> String { pub fn authenticate_begin_flow() -> &'static str {
crate::state::get_login_url() crate::state::get_login_url()
} }

View File

@ -1,13 +0,0 @@
//! Configuration structs
// pub const MODRINTH_URL: &str = "https://staging.modrinth.com/";
// pub const MODRINTH_API_URL: &str = "https://staging-api.modrinth.com/v2/";
// pub const MODRINTH_API_URL_V3: &str = "https://staging-api.modrinth.com/v3/";
pub const MODRINTH_URL: &str = "https://modrinth.com/";
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
pub const MODRINTH_API_URL_V3: &str = "https://api.modrinth.com/v3/";
pub const MODRINTH_SOCKET_URL: &str = "wss://api.modrinth.com/";
pub const META_URL: &str = "https://launcher-meta.modrinth.com/";

View File

@ -11,7 +11,6 @@ and launching Modrinth mod packs
mod util; mod util;
mod api; mod api;
mod config;
mod error; mod error;
mod event; mod event;
mod launcher; mod launcher;

View File

@ -1,4 +1,3 @@
use crate::config::{META_URL, MODRINTH_API_URL, MODRINTH_API_URL_V3};
use crate::state::ProjectType; use crate::state::ProjectType;
use crate::util::fetch::{FetchSemaphore, fetch_json, sha1_async}; use crate::util::fetch::{FetchSemaphore, fetch_json, sha1_async};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -8,6 +7,7 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::collections::HashMap; use std::collections::HashMap;
use std::env;
use std::fmt::Display; use std::fmt::Display;
use std::hash::Hash; use std::hash::Hash;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -945,7 +945,7 @@ impl CachedEntry {
CacheValueType::Project => { CacheValueType::Project => {
fetch_original_values!( fetch_original_values!(
Project, Project,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"projects", "projects",
CacheValue::Project CacheValue::Project
) )
@ -953,7 +953,7 @@ impl CachedEntry {
CacheValueType::Version => { CacheValueType::Version => {
fetch_original_values!( fetch_original_values!(
Version, Version,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"versions", "versions",
CacheValue::Version CacheValue::Version
) )
@ -961,7 +961,7 @@ impl CachedEntry {
CacheValueType::User => { CacheValueType::User => {
fetch_original_values!( fetch_original_values!(
User, User,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"users", "users",
CacheValue::User CacheValue::User
) )
@ -969,7 +969,7 @@ impl CachedEntry {
CacheValueType::Team => { CacheValueType::Team => {
let mut teams = fetch_many_batched::<Vec<TeamMember>>( let mut teams = fetch_many_batched::<Vec<TeamMember>>(
Method::GET, Method::GET,
MODRINTH_API_URL_V3, env!("MODRINTH_API_URL_V3"),
"teams?ids=", "teams?ids=",
&keys, &keys,
fetch_semaphore, fetch_semaphore,
@ -1008,7 +1008,7 @@ impl CachedEntry {
CacheValueType::Organization => { CacheValueType::Organization => {
let mut orgs = fetch_many_batched::<Organization>( let mut orgs = fetch_many_batched::<Organization>(
Method::GET, Method::GET,
MODRINTH_API_URL_V3, env!("MODRINTH_API_URL_V3"),
"organizations?ids=", "organizations?ids=",
&keys, &keys,
fetch_semaphore, fetch_semaphore,
@ -1063,7 +1063,7 @@ impl CachedEntry {
CacheValueType::File => { CacheValueType::File => {
let mut versions = fetch_json::<HashMap<String, Version>>( let mut versions = fetch_json::<HashMap<String, Version>>(
Method::POST, Method::POST,
&format!("{MODRINTH_API_URL}version_files"), concat!(env!("MODRINTH_API_URL"), "version_files"),
None, None,
Some(serde_json::json!({ Some(serde_json::json!({
"algorithm": "sha1", "algorithm": "sha1",
@ -1119,7 +1119,11 @@ impl CachedEntry {
.map(|x| { .map(|x| {
( (
x.key().to_string(), x.key().to_string(),
format!("{META_URL}{}/v0/manifest.json", x.key()), format!(
"{}{}/v0/manifest.json",
env!("MODRINTH_LAUNCHER_META_URL"),
x.key()
),
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -1154,7 +1158,7 @@ impl CachedEntry {
CacheValueType::MinecraftManifest => { CacheValueType::MinecraftManifest => {
fetch_original_value!( fetch_original_value!(
MinecraftManifest, MinecraftManifest,
META_URL, env!("MODRINTH_LAUNCHER_META_URL"),
format!( format!(
"minecraft/v{}/manifest.json", "minecraft/v{}/manifest.json",
daedalus::minecraft::CURRENT_FORMAT_VERSION daedalus::minecraft::CURRENT_FORMAT_VERSION
@ -1165,7 +1169,7 @@ impl CachedEntry {
CacheValueType::Categories => { CacheValueType::Categories => {
fetch_original_value!( fetch_original_value!(
Categories, Categories,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"tag/category", "tag/category",
CacheValue::Categories CacheValue::Categories
) )
@ -1173,7 +1177,7 @@ impl CachedEntry {
CacheValueType::ReportTypes => { CacheValueType::ReportTypes => {
fetch_original_value!( fetch_original_value!(
ReportTypes, ReportTypes,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"tag/report_type", "tag/report_type",
CacheValue::ReportTypes CacheValue::ReportTypes
) )
@ -1181,7 +1185,7 @@ impl CachedEntry {
CacheValueType::Loaders => { CacheValueType::Loaders => {
fetch_original_value!( fetch_original_value!(
Loaders, Loaders,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"tag/loader", "tag/loader",
CacheValue::Loaders CacheValue::Loaders
) )
@ -1189,7 +1193,7 @@ impl CachedEntry {
CacheValueType::GameVersions => { CacheValueType::GameVersions => {
fetch_original_value!( fetch_original_value!(
GameVersions, GameVersions,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"tag/game_version", "tag/game_version",
CacheValue::GameVersions CacheValue::GameVersions
) )
@ -1197,7 +1201,7 @@ impl CachedEntry {
CacheValueType::DonationPlatforms => { CacheValueType::DonationPlatforms => {
fetch_original_value!( fetch_original_value!(
DonationPlatforms, DonationPlatforms,
MODRINTH_API_URL, env!("MODRINTH_API_URL"),
"tag/donation_platform", "tag/donation_platform",
CacheValue::DonationPlatforms CacheValue::DonationPlatforms
) )
@ -1297,14 +1301,12 @@ impl CachedEntry {
} }
}); });
let version_update_url =
format!("{MODRINTH_API_URL}version_files/update");
let variations = let variations =
futures::future::try_join_all(filtered_keys.iter().map( futures::future::try_join_all(filtered_keys.iter().map(
|((loaders_key, game_version), hashes)| { |((loaders_key, game_version), hashes)| {
fetch_json::<HashMap<String, Version>>( fetch_json::<HashMap<String, Version>>(
Method::POST, Method::POST,
&version_update_url, concat!(env!("MODRINTH_API_URL"), "version_files/update"),
None, None,
Some(serde_json::json!({ Some(serde_json::json!({
"algorithm": "sha1", "algorithm": "sha1",
@ -1368,7 +1370,11 @@ impl CachedEntry {
.map(|x| { .map(|x| {
( (
x.key().to_string(), x.key().to_string(),
format!("{MODRINTH_API_URL}search{}", x.key()), format!(
"{}search{}",
env!("MODRINTH_API_URL"),
x.key()
),
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -1,4 +1,3 @@
use crate::config::{MODRINTH_API_URL_V3, MODRINTH_SOCKET_URL};
use crate::data::ModrinthCredentials; use crate::data::ModrinthCredentials;
use crate::event::FriendPayload; use crate::event::FriendPayload;
use crate::event::emit::emit_friend; use crate::event::emit::emit_friend;
@ -77,7 +76,8 @@ impl FriendsSocket {
if let Some(credentials) = credentials { if let Some(credentials) = credentials {
let mut request = format!( let mut request = format!(
"{MODRINTH_SOCKET_URL}_internal/launcher_socket?code={}", "{}_internal/launcher_socket?code={}",
env!("MODRINTH_SOCKET_URL"),
credentials.session credentials.session
) )
.into_client_request()?; .into_client_request()?;
@ -303,7 +303,7 @@ impl FriendsSocket {
) -> crate::Result<Vec<UserFriend>> { ) -> crate::Result<Vec<UserFriend>> {
fetch_json( fetch_json(
Method::GET, Method::GET,
&format!("{MODRINTH_API_URL_V3}friends"), concat!(env!("MODRINTH_API_URL_V3"), "friends"),
None, None,
None, None,
semaphore, semaphore,
@ -328,7 +328,7 @@ impl FriendsSocket {
) -> crate::Result<()> { ) -> crate::Result<()> {
fetch_advanced( fetch_advanced(
Method::POST, Method::POST,
&format!("{MODRINTH_API_URL_V3}friend/{user_id}"), &format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")),
None, None,
None, None,
None, None,
@ -349,7 +349,7 @@ impl FriendsSocket {
) -> crate::Result<()> { ) -> crate::Result<()> {
fetch_advanced( fetch_advanced(
Method::DELETE, Method::DELETE,
&format!("{MODRINTH_API_URL_V3}friend/{user_id}"), &format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")),
None, None,
None, None,
None, None,

View File

@ -1,4 +1,3 @@
use crate::config::{MODRINTH_API_URL, MODRINTH_URL};
use crate::state::{CacheBehaviour, CachedEntry}; use crate::state::{CacheBehaviour, CachedEntry};
use crate::util::fetch::{FetchSemaphore, fetch_advanced}; use crate::util::fetch::{FetchSemaphore, fetch_advanced};
use chrono::{DateTime, Duration, TimeZone, Utc}; use chrono::{DateTime, Duration, TimeZone, Utc};
@ -31,7 +30,7 @@ impl ModrinthCredentials {
let resp = fetch_advanced( let resp = fetch_advanced(
Method::POST, Method::POST,
&format!("{MODRINTH_API_URL}session/refresh"), concat!(env!("MODRINTH_API_URL"), "session/refresh"),
None, None,
None, None,
Some(("Authorization", &*creds.session)), Some(("Authorization", &*creds.session)),
@ -190,8 +189,8 @@ impl ModrinthCredentials {
} }
} }
pub fn get_login_url() -> String { pub const fn get_login_url() -> &'static str {
format!("{MODRINTH_URL}auth/sign-in?launcher=true") concat!(env!("MODRINTH_URL"), "auth/sign-in?launcher=true")
} }
pub async fn finish_login_flow( pub async fn finish_login_flow(
@ -216,7 +215,7 @@ async fn fetch_info(
) -> crate::Result<crate::state::cache::User> { ) -> crate::Result<crate::state::cache::User> {
let result = fetch_advanced( let result = fetch_advanced(
Method::GET, Method::GET,
&format!("{MODRINTH_API_URL}user"), concat!(env!("MODRINTH_API_URL"), "user"),
None, None,
None, None,
Some(("Authorization", token)), Some(("Authorization", token)),

View File

@ -1,6 +1,5 @@
//! Functions for fetching information from the Internet //! Functions for fetching information from the Internet
use super::io::{self, IOError}; use super::io::{self, IOError};
use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3};
use crate::event::LoadingBarId; use crate::event::LoadingBarId;
use crate::event::emit::emit_loading; use crate::event::emit::emit_loading;
use bytes::Bytes; use bytes::Bytes;
@ -84,8 +83,8 @@ pub async fn fetch_advanced(
.as_ref() .as_ref()
.is_none_or(|x| &*x.0.to_lowercase() != "authorization") .is_none_or(|x| &*x.0.to_lowercase() != "authorization")
&& (url.starts_with("https://cdn.modrinth.com") && (url.starts_with("https://cdn.modrinth.com")
|| url.starts_with(MODRINTH_API_URL) || url.starts_with(env!("MODRINTH_API_URL"))
|| url.starts_with(MODRINTH_API_URL_V3)) || url.starts_with(env!("MODRINTH_API_URL_V3")))
{ {
crate::state::ModrinthCredentials::get_active(exec).await? crate::state::ModrinthCredentials::get_active(exec).await?
} else { } else {