Compare commits

..

1 Commits

Author SHA1 Message Date
blacksmith-sh[bot]
6c4d935d90 Migrate workflows to Blacksmith 2025-07-16 21:13:39 +00:00
33 changed files with 72 additions and 283 deletions

View File

@@ -19,7 +19,7 @@ on:
jobs:
docker:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@@ -8,7 +8,7 @@ on:
jobs:
run-docker:
if: github.repository_owner == 'modrinth'
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository

View File

@@ -8,7 +8,7 @@ on:
jobs:
wait:
if: github.repository_owner == 'modrinth'
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
deployments: write

View File

@@ -17,7 +17,7 @@ on:
jobs:
docker:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@@ -28,13 +28,13 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, windows-latest, ubuntu-22.04]
platform: [macos-latest, windows-latest, blacksmith-4vcpu-ubuntu-2204]
include:
- platform: macos-latest
artifact-target-name: universal-apple-darwin
- platform: windows-latest
artifact-target-name: x86_64-pc-windows-msvc
- platform: ubuntu-22.04
- platform: blacksmith-4vcpu-ubuntu-2204
artifact-target-name: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.platform }}

View File

@@ -15,7 +15,7 @@ on:
jobs:
release:
name: Release Modrinth App
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
LINUX_X64_BUNDLE_ARTIFACT_NAME: App bundle (x86_64-unknown-linux-gnu)

View File

@@ -11,7 +11,7 @@ on:
jobs:
build:
name: Lint and Test
runs-on: ubuntu-22.04
runs-on: blacksmith-4vcpu-ubuntu-2204
env:
# Ensure pnpm output is colored in GitHub Actions logs
@@ -80,4 +80,4 @@ jobs:
- name: 🔍 Verify intl:extract has been run
run: |
pnpm intl:extract
git diff --exit-code --color */*/src/locales/en-US/index.json
git diff --exit-code */*/src/locales/en-US/index.json

View File

@@ -11,7 +11,6 @@
<body>
<div id="app"></div>
<script src="https://tally.so/widgets/embed.js" async></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -71,8 +71,6 @@ import { openUrl } from '@tauri-apps/plugin-opener'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import { get_available_capes, get_available_skins } from './helpers/skins'
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
import { list } from '@/helpers/profile.js'
import { $fetch } from 'ofetch'
const themeStore = useTheming()
@@ -221,8 +219,6 @@ async function setupApp() {
} catch (error) {
console.warn('Failed to generate skin previews in app setup.', error)
}
await processPendingSurveys()
}
const stateFailed = ref(false)
@@ -397,91 +393,6 @@ function handleAuxClick(e) {
e.target.dispatchEvent(event)
}
}
function cleanupOldSurveyDisplayData() {
const threeWeeksAgo = new Date()
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21)
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key.startsWith('survey-') && key.endsWith('-display')) {
const dateValue = new Date(localStorage.getItem(key))
if (dateValue < threeWeeksAgo) {
localStorage.removeItem(key)
}
}
}
}
async function processPendingSurveys() {
function isWithinLastTwoWeeks(date) {
const twoWeeksAgo = new Date()
twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14)
return date >= twoWeeksAgo
}
cleanupOldSurveyDisplayData()
const creds = await getCreds().catch(handleError)
const userId = creds?.user_id
const instances = await list().catch(handleError)
const isActivePlayer =
instances.findIndex(
(instance) =>
isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created),
) >= 0
let surveys = []
try {
surveys = await $fetch('https://api.modrinth.com/v2/surveys')
} catch (e) {
console.error('Error fetching surveys:', e)
}
const surveyToShow = surveys.find(
(survey) =>
localStorage.getItem(`survey-${survey.id}-display`) === null &&
survey.type === 'tally_app' &&
((survey.condition === 'active_player' && isActivePlayer) ||
(survey.assigned_users?.includes(userId) && !survey.dismissed_users?.includes(userId))),
)
if (surveyToShow) {
const formId = surveyToShow.tally_id
const popupOptions = {
layout: 'modal',
width: 700,
autoClose: 2000,
hideTitle: true,
hiddenFields: {
user_id: userId,
},
onOpen: () => console.info('Opened user survey'),
onClose: () => console.info('Closed user survey'),
onSubmit: () => console.info('Active user survey submitted'),
}
try {
if (window.Tally?.openPopup) {
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
localStorage.setItem(`survey-${surveyToShow.id}-display`, new Date())
window.Tally.openPopup(formId, popupOptions)
} else {
console.warn('Tally script not yet loaded')
}
} catch (e) {
console.error('Error opening Tally popup:', e)
}
console.info(`Found user survey to show with tally_id: ${formId}`)
window.Tally.openPopup(formId, popupOptions)
} else {
console.info('No user survey to show')
}
}
</script>
<template>

View File

@@ -67,8 +67,9 @@ export async function determineModelType(texture: string): Promise<'SLIM' | 'CLA
const armWidth = 2
const armHeight = 12
const imageData = context.getImageData(armX, armY, armWidth, armHeight).data
for (let alphaIndex = 3; alphaIndex < imageData.length; alphaIndex += 4) {
if (imageData[alphaIndex] !== 0) {
for (let index = 1; index <= imageData.length; index++) {
//every fourth value in RGBA is the alpha channel
if (index % 4 == 0 && imageData[index - 1] !== 0) {
resolve('CLASSIC')
return
}

View File

@@ -220,7 +220,7 @@ async function refreshSearch() {
}
}
results.value = rawResults.result
currentPage.value = 1
currentPage.value = Math.max(1, Math.min(pageCount.value, currentPage.value))
const persistentParams: LocationQuery = {}
@@ -266,7 +266,6 @@ async function onSearchChangeToTop() {
function clearSearch() {
query.value = ''
currentPage.value = 1
}
watch(

View File

@@ -90,8 +90,8 @@
"font-src": ["https://cdn-raw.modrinth.com/fonts/"],
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:",
"style-src": "'unsafe-inline' 'self'",
"script-src": "https://*.posthog.com https://tally.so/widgets/embed.js 'self'",
"frame-src": "https://www.youtube.com https://www.youtube-nocookie.com https://discord.com https://tally.so/popup/ 'self'",
"script-src": "https://*.posthog.com 'self'",
"frame-src": "https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'",
"media-src": "https://*.githubusercontent.com"
}
}

View File

@@ -45,9 +45,8 @@
<h2
class="relative m-0 max-w-2xl text-base font-normal leading-[155%] text-secondary md:text-[1.2rem]"
>
Modrinth Servers is the easiest way to host your own Minecraft: Java Edition server.
Seamlessly install and play your favorite mods and modpacks, all within the Modrinth
platform.
Modrinth Servers is the easiest way to host your own Minecraft server. Seamlessly install
and play your favorite mods and modpacks, all within the Modrinth platform.
</h2>
<div class="relative flex w-full flex-wrap items-center gap-8 align-middle sm:w-fit">
<div
@@ -460,7 +459,7 @@
</p>
</details>
<details pyro-hash="performance" class="group" :open="$route.hash === '#performance'">
<details pyro-hash="players" class="group" :open="$route.hash === '#players'">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
@@ -481,7 +480,7 @@
</p>
</details>
<details pyro-hash="prices" class="group" :open="$route.hash === '#prices'">
<details pyro-hash="players" class="group" :open="$route.hash === '#prices'">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
@@ -492,24 +491,6 @@
All prices are listed in United States Dollars (USD).
</p>
</details>
<details pyro-hash="versions" class="group" :open="$route.hash === '#versions'">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
What Minecraft versions and loaders can be used?
</summary>
<p class="m-0 ml-6 leading-[160%]">
Modrinth Servers can run any version of Minecraft: Java Edition going all the way
back to version 1.2.5, including snapshot versions.
</p>
<p class="m-0 ml-6 mt-3 leading-[160%]">
We also support a wide range of mod and plugin loaders, including Fabric, Quilt,
Forge, and NeoForge for mods, as well as Paper and Purpur for plugins. Availability
depends on whether the mod or plugin loader supports the selected Minecraft version.
</p>
</details>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled' OR status = 'failed')",
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')",
"describe": {
"columns": [
{
@@ -102,5 +102,5 @@
true
]
},
"hash": "cf020daa52a1316e5f60d197196b880b72c0b2a576e470d9fd7182558103d055"
"hash": "99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2"
}

View File

@@ -43,9 +43,7 @@ pub enum AuthenticationError {
InvalidAuthMethod,
#[error("GitHub Token from incorrect Client ID")]
InvalidClientId,
#[error(
"User email is already registered on Modrinth. Try 'Forgot password' to access your account."
)]
#[error("User email/account is already registered on Modrinth")]
DuplicateUser,
#[error("Invalid state sent, you probably need to get a new websocket")]
SocketError,

View File

@@ -197,7 +197,7 @@ impl DBCharge {
) -> Result<Option<DBCharge>, DatabaseError> {
let user_subscription_id = user_subscription_id.0;
let res = select_charges_with_predicate!(
"WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled' OR status = 'failed')",
"WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')",
user_subscription_id
)
.fetch_optional(exec)

View File

@@ -223,8 +223,8 @@ impl TempUser {
stripe_customer_id: None,
totp_secret: None,
username,
email: self.email.clone(),
email_verified: self.email.is_some(),
email: self.email,
email_verified: true,
avatar_url,
raw_avatar_url,
bio: self.bio,
@@ -1419,15 +1419,15 @@ pub async fn create_account_with_password(
.hash_password(new_account.password.as_bytes(), &salt)?
.to_string();
if !crate::database::models::DBUser::get_by_case_insensitive_email(
if crate::database::models::DBUser::get_by_email(
&new_account.email,
&**pool,
)
.await?
.is_empty()
.is_some()
{
return Err(ApiError::InvalidInput(
"Email is already registered on Modrinth! Try 'Forgot password' to access your account.".to_string(),
"Email is already registered on Modrinth!".to_string(),
));
}
@@ -2220,18 +2220,6 @@ pub async fn set_email(
.await?
.1;
if !crate::database::models::DBUser::get_by_case_insensitive_email(
&email.email,
&**pool,
)
.await?
.is_empty()
{
return Err(ApiError::InvalidInput(
"Email is already registered on Modrinth! Try 'Forgot password' in incognito to access and delete your other account.".to_string(),
));
}
let mut transaction = pool.begin().await?;
sqlx::query!(

View File

@@ -15,7 +15,6 @@
"app:intl:extract": "pnpm run --filter=@modrinth/app-frontend intl:extract",
"blog:fix": "turbo run fix --filter=@modrinth/blog",
"pages:build": "NITRO_PRESET=cloudflare-pages pnpm --filter frontend run build",
"moderation:fix": "turbo run fix --filter=@modrinth/moderation",
"build": "turbo run build --continue",
"lint": "turbo run lint --continue",
"test": "turbo run test --continue",

View File

@@ -284,12 +284,6 @@ async fn import_mmc_unmanaged(
component.version.clone().unwrap_or_default(),
));
}
if component.uid.starts_with("net.neoforged") {
return Some((
PackDependency::NeoForge,
component.version.clone().unwrap_or_default(),
));
}
if component.uid.starts_with("org.quiltmc.quilt-loader") {
return Some((
PackDependency::QuiltLoader,

View File

@@ -0,0 +1 @@
**License id:** %PROJECT_LICENSE_ID% \

View File

@@ -1,2 +1 @@
**License id:** %PROJECT_LICENSE_ID% \
**License Link:** %PROJECT_LICENSE_URL%

View File

@@ -1 +1 @@
> **{PLATFORM}:** {URL}<br />
> **{PLATFORM}:** {URL}

View File

@@ -1,2 +1 @@
<br />
<u>**Donation Links:**</u><br />
<u>**Donation Links:**</u>

View File

@@ -1,5 +1,5 @@
## No English Description
Per section 2.2 of %RULES% a project's [Summary](%PROJECT_SETTINGS_LINK%) and %PROJECT_DESCRIPTION_FLINK% must be in English, unless meant exclusively for non-English use, such as translations.
Per section 2.2 of %RULES% a project's Summary and Description must be in English, unless meant exclusively for non-English use, such as translations.
You may include your non-English Description if you would like but we ask that you also add an English translation of the Description to your project page, if you would like to use an online translator to do this, we recommend [DeepL](https://www.deepl.com/translator).
You may include your non-English Description if you would like but we ask that you also add an English translation of the Description to your Description page, if you would like to use an online translator to do this, we recommend [DeepL](https://www.deepl.com/translator).

View File

@@ -1,6 +0,0 @@
## Identity Verification
**Welcome to Modrinth!** We're happy to see you here, we just want to make sure you're you.
Since this project already exists on the internet we ask that you provide evidence you are the rightful owner of this content.
For instance, by submitting a screenshot accessing the settings of the project's existing pages such as %PLATFORM%.

View File

@@ -1,5 +0,0 @@
## Source Code Requested
To ensure the safety of all Modrinth users, we ask that you provide the source code for this project before resubmission so that it can be reviewed by our Moderation Team.
We also ask that you provide the source for any included binary files, as well as detailed build instructions allowing us to verify that the compiled code you are distributing matches the provided source.
We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub.

View File

@@ -1,4 +0,0 @@
## Source Code Requested
To ensure the safety of all Modrinth users, we ask that you provide the source code for this project and the process you used to obfuscate it before resubmission so that it can be reviewed by our Moderation Team.
We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub.

View File

@@ -20,7 +20,14 @@ const licensesNotRequiringSource: string[] = [
const licenseStage: Stage = {
title: 'Is this license and link valid?',
text: async () => (await import('../messages/checklist-text/licensing.md?raw')).default,
text: async (project) => {
let text = ''
text += (await import('../messages/checklist-text/license/id.md?raw')).default
if (project.license.url)
text += (await import('../messages/checklist-text/license/link.md?raw')).default
return text
},
id: 'license',
icon: BookTextIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
@@ -48,24 +55,33 @@ const licenseStage: Stage = {
},
],
},
// {
// id: 'license_no_source',
// type: 'conditional-button',
// label: 'No Source',
// fallbackWeight: 602,
// suggestedStatus: 'rejected',
// severity: 'medium',
// fallbackMessage: async () => (await import('../messages/license/no_source.md?raw')).default,
// messageVariants: [
// {
// conditions: {
// requiredActions: ['reupload_unclear_fork'],
// },
// weight: 602,
// message: async () => (await import('../messages/license/no_source-fork.md?raw')).default,
// },
// ],
// },
{
id: 'license_no_source',
type: 'conditional-button',
type: 'button',
label: 'No Source',
weight: 602,
suggestedStatus: 'rejected',
severity: 'medium',
shouldShow: (project) => !licensesNotRequiringSource.includes(project.license.id),
messageVariants: [
{
conditions: {
excludedActions: ['license_no_source-fork'],
},
weight: 602,
message: async () => (await import('../messages/license/no_source.md?raw')).default,
},
],
fallbackWeight: 602,
fallbackMessage: async () => '',
message: async () => (await import('../messages/license/no_source.md?raw')).default,
enablesActions: [
{
id: 'license_no_source-fork',

View File

@@ -61,8 +61,7 @@ const links: Stage = {
{
id: 'links_unaccessible_options',
type: 'multi-select-chips',
label: 'Warn of inaccessible link?',
shouldShow: (project) => Boolean(project.source_url || project.discord_url),
label: 'Warn of unaccessible link?',
options: [
{
label: 'Source',

View File

@@ -20,7 +20,6 @@ const reupload: Stage = {
'reupload_unclear_fork',
'reupload_insufficient_fork',
'reupload_request_proof',
'reupload_identity_verification',
],
relevantExtraInput: [
{
@@ -45,12 +44,11 @@ const reupload: Stage = {
suggestedStatus: 'rejected',
severity: 'high',
message: async () => (await import('../messages/reupload/fork.md?raw')).default,
disablesActions: [
'reupload_reupload',
'reupload_insufficient_fork',
'reupload_request_proof',
'reupload_identity_verification',
],
// disablesActions: [
// 'reupload_reupload',
// 'reupload_insufficient_fork',
// 'reupload_request_proof',
// ],
} as ButtonAction,
{
id: 'reupload_insufficient_fork',
@@ -60,12 +58,7 @@ const reupload: Stage = {
suggestedStatus: 'rejected',
severity: 'high',
message: async () => (await import('../messages/reupload/insufficient_fork.md?raw')).default,
disablesActions: [
'reupload_unclear_fork',
'reupload_reupload',
'reupload_request_proof',
'reupload_identity_verification',
],
// disablesActions: ['reupload_unclear_fork', 'reupload_reupload', 'reupload_request_proof'],
} as ButtonAction,
{
id: 'reupload_request_proof',
@@ -76,34 +69,7 @@ const reupload: Stage = {
severity: 'high',
message: async () =>
(await import('../messages/reupload/proof_of_permissions.md?raw')).default,
disablesActions: [
'reupload_reupload',
'reupload_unclear_fork',
'reupload_insufficient_fork',
'reupload_identity_verification',
],
},
{
id: 'reupload_identity_verification',
type: 'button',
label: 'Verify Identity',
weight: 1100,
suggestedStatus: 'rejected',
severity: 'high',
message: async () =>
(await import('../messages/reupload/identity_verification.md?raw')).default,
relevantExtraInput: [
{
label: 'Where else can the project be found?',
variable: 'PLATFORM',
required: true,
},
],
disablesActions: [
'reupload_reupload',
'reupload_insufficient_fork',
'reupload_request_proof',
],
// disablesActions: ['reupload_reupload', 'reupload_unclear_fork', 'reupload_insufficient_fork'],
},
],
}

View File

@@ -1,5 +1,5 @@
import type { Stage } from '../../types/stage'
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
import type { ButtonAction } from '../../types/actions'
import { TriangleAlertIcon } from '@modrinth/assets'
const statusAlerts: Stage = {
@@ -39,41 +39,6 @@ const statusAlerts: Stage = {
message: async () =>
(await import('../messages/status-alerts/account_issues.md?raw')).default,
} as ButtonAction,
{
id: 'status_tec_source_request',
type: 'button',
label: `Request Source`,
suggestedStatus: 'rejected',
severity: 'critical',
disablesActions: ['status_corrections_applied', 'status_private_use'],
shouldShow: (project) =>
project.project_type === 'mod' ||
project.project_type === 'shader' ||
project.project_type.toString() === 'plugin',
weight: -999999,
message: async () => '',
enablesActions: [
{
id: 'status_tec_source_request_options',
type: 'dropdown',
label: 'Why are you requesting source?',
options: [
{
label: 'Obfuscated',
weight: 999999,
message: async () =>
(await import('../messages/status-alerts/tec/source_request-obfs.md?raw')).default,
} as DropdownActionOption,
{
label: 'Binaries',
weight: 999000,
message: async () =>
(await import('../messages/status-alerts/tec/source_request-bins.md?raw')).default,
} as DropdownActionOption,
],
} as DropdownAction,
],
} as ButtonAction,
{
id: 'status_automod_confusion',
type: 'button',

View File

@@ -25,8 +25,6 @@ const versions: Stage = {
label: 'Incorrect Project Type',
suggestedStatus: 'rejected',
severity: 'medium',
weight: -999999,
message: async () => '',
enablesActions: [
{
id: 'versions_incorrect_project_type_options',
@@ -64,8 +62,6 @@ const versions: Stage = {
label: 'Alternate Versions',
suggestedStatus: 'flagged',
severity: 'medium',
weight: -999999,
message: async () => '',
enablesActions: [
{
id: 'versions_incorrect_project_type_options',
@@ -114,7 +110,7 @@ const versions: Stage = {
weight: 1002,
suggestedStatus: 'rejected',
severity: 'high',
shouldShow: (project) => project.project_type === 'modpack',
shouldShow: (project) => project.project_type === `modpack`,
message: async () =>
(await import('../messages/versions/alternate_versions-zip.md?raw')).default,
} as DropdownActionOption,

View File

@@ -10,13 +10,6 @@ export type VersionEntry = {
}
const VERSIONS: VersionEntry[] = [
{
date: `2025-07-19T15:20:00-07:00`,
product: 'web',
body: `### Improvements
- Removed Tumblr icon from footer as we no longer use it.
- Reverted changes to publishing checklist since they need more work.`,
},
{
date: `2025-07-16T12:40:00-07:00`,
product: 'web',