Merge commit 'a8caa1afc3115cc79da25d8129e749932c7dc2a5' into feature-elyby-account

This commit is contained in:
2025-07-20 02:08:02 +03:00
127 changed files with 3205 additions and 994 deletions

View File

@@ -284,6 +284,12 @@ 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

@@ -1,16 +1,16 @@
import { promises as fs } from 'fs'
import * as path from 'path'
import fastGlob from 'fast-glob'
import { repoPath, toVarName } from './utils'
import { glob } from 'glob'
import { PUBLIC_SRC, PUBLIC_LOCATIONS, ARTICLES_GLOB, COMPILED_DIR } from './blog.config'
async function checkPublicAssets() {
const srcFiles = await fastGlob(['**/*'], { cwd: PUBLIC_SRC, dot: true })
const srcFiles = await glob('**/*', { cwd: PUBLIC_SRC, dot: true })
let allOk = true
for (const target of PUBLIC_LOCATIONS) {
for (const relativeFile of srcFiles) {
const shouldExist = path.join(target, relativeFile)
const shouldExist = path.posix.join(target, relativeFile)
try {
await fs.access(shouldExist)
} catch {
@@ -26,15 +26,15 @@ async function checkPublicAssets() {
}
async function checkCompiledArticles() {
const mdFiles = await fastGlob([ARTICLES_GLOB])
const compiledFiles = await fastGlob([`${COMPILED_DIR}/*.ts`])
const mdFiles = await glob(ARTICLES_GLOB)
const compiledFiles = await glob(`${COMPILED_DIR}/*.ts`)
const compiledVarNames = compiledFiles.map((f) => path.basename(f, '.ts'))
// Check all .md have compiled .ts and .content.ts and the proper public thumbnail
for (const file of mdFiles) {
const varName = toVarName(path.basename(file, '.md'))
const compiledPath = path.join(COMPILED_DIR, varName + '.ts')
const contentPath = path.join(COMPILED_DIR, varName + '.content.ts')
const compiledPath = path.posix.join(COMPILED_DIR, varName + '.ts')
const contentPath = path.posix.join(COMPILED_DIR, varName + '.content.ts')
if (!compiledVarNames.includes(varName)) {
console.error(`⚠️ Missing compiled article for: ${file} (should be: ${compiledPath})`)
process.exit(1)
@@ -59,7 +59,7 @@ async function checkCompiledArticles() {
if (varName === 'index' || varName.endsWith('.content')) continue
const mdPathGlob = repoPath(`packages/blog/articles/**/${varName.replace(/_/g, '*')}.md`)
const found = await fastGlob([mdPathGlob])
const found = await glob(mdPathGlob)
if (!found.length) {
console.error(`❌ Compiled article ${compiled} has no matching markdown source!`)
process.exit(1)

View File

@@ -1,12 +1,12 @@
import { promises as fs } from 'fs'
import * as path from 'path'
import fg from 'fast-glob'
import matter from 'gray-matter'
import { md } from '@modrinth/utils'
import { minify } from 'html-minifier-terser'
import { copyDir, toVarName } from './utils'
import RSS from 'rss'
import { parseStringPromise } from 'xml2js'
import { glob } from 'glob'
import {
ARTICLES_GLOB,
@@ -24,7 +24,7 @@ async function ensureCompiledDir() {
}
async function hasThumbnail(slug: string): Promise<boolean> {
const thumbnailPath = path.join(PUBLIC_SRC, slug, 'thumbnail.webp')
const thumbnailPath = path.posix.join(PUBLIC_SRC, slug, 'thumbnail.webp')
try {
await fs.access(thumbnailPath)
return true
@@ -48,7 +48,7 @@ function getThumbnailUrl(slug: string, hasThumb: boolean): string {
async function compileArticles() {
await ensureCompiledDir()
const files = await fg([ARTICLES_GLOB])
const files = await glob(ARTICLES_GLOB)
console.log(`🔎 Found ${files.length} markdown articles!`)
const articleExports: string[] = []
const articlesArray: string[] = []
@@ -75,8 +75,8 @@ async function compileArticles() {
const slug = frontSlug || path.basename(file, '.md')
const varName = toVarName(slug)
const exportFile = path.join(COMPILED_DIR, `${varName}.ts`)
const contentFile = path.join(COMPILED_DIR, `${varName}.content.ts`)
const exportFile = path.posix.join(COMPILED_DIR, `${varName}.ts`)
const contentFile = path.posix.join(COMPILED_DIR, `${varName}.content.ts`)
const thumbnailPresent = await hasThumbnail(slug)
const contentTs = `
@@ -221,7 +221,7 @@ async function deleteDirContents(dir: string) {
const entries = await fs.readdir(dir, { withFileTypes: true })
await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(dir, entry.name)
const fullPath = path.posix.join(dir, entry.name)
if (entry.isDirectory()) {
await fs.rm(fullPath, { recursive: true, force: true })
} else {

View File

@@ -1,56 +1,56 @@
// AUTO-GENERATED FILE - DO NOT EDIT
import { article as a_new_chapter_for_modrinth_servers } from './a_new_chapter_for_modrinth_servers'
import { article as accelerating_development } from './accelerating_development'
import { article as becoming_sustainable } from './becoming_sustainable'
import { article as capital_return } from './capital_return'
import { article as carbon_ads } from './carbon_ads'
import { article as creator_monetization } from './creator_monetization'
import { article as creator_update } from './creator_update'
import { article as creator_updates_july_2025 } from './creator_updates_july_2025'
import { article as design_refresh } from './design_refresh'
import { article as download_adjustment } from './download_adjustment'
import { article as knossos_v2_1_0 } from './knossos_v2_1_0'
import { article as licensing_guide } from './licensing_guide'
import { article as modpack_changes } from './modpack_changes'
import { article as modpacks_alpha } from './modpacks_alpha'
import { article as modrinth_app_beta } from './modrinth_app_beta'
import { article as modrinth_beta } from './modrinth_beta'
import { article as modrinth_servers_beta } from './modrinth_servers_beta'
import { article as new_site_beta } from './new_site_beta'
import { article as plugins_resource_packs } from './plugins_resource_packs'
import { article as pride_campaign_2025 } from './pride_campaign_2025'
import { article as redesign } from './redesign'
import { article as skins_now_in_modrinth_app } from './skins_now_in_modrinth_app'
import { article as two_years_of_modrinth_history } from './two_years_of_modrinth_history'
import { article as two_years_of_modrinth } from './two_years_of_modrinth'
import { article as whats_modrinth } from './whats_modrinth'
import { article as windows_borderless_malware_disclosure } from './windows_borderless_malware_disclosure'
import { article as whats_modrinth } from './whats_modrinth'
import { article as two_years_of_modrinth } from './two_years_of_modrinth'
import { article as two_years_of_modrinth_history } from './two_years_of_modrinth_history'
import { article as skins_now_in_modrinth_app } from './skins_now_in_modrinth_app'
import { article as redesign } from './redesign'
import { article as pride_campaign_2025 } from './pride_campaign_2025'
import { article as plugins_resource_packs } from './plugins_resource_packs'
import { article as new_site_beta } from './new_site_beta'
import { article as modrinth_servers_beta } from './modrinth_servers_beta'
import { article as modrinth_beta } from './modrinth_beta'
import { article as modrinth_app_beta } from './modrinth_app_beta'
import { article as modpacks_alpha } from './modpacks_alpha'
import { article as modpack_changes } from './modpack_changes'
import { article as licensing_guide } from './licensing_guide'
import { article as knossos_v2_1_0 } from './knossos_v2_1_0'
import { article as download_adjustment } from './download_adjustment'
import { article as design_refresh } from './design_refresh'
import { article as creator_updates_july_2025 } from './creator_updates_july_2025'
import { article as creator_update } from './creator_update'
import { article as creator_monetization } from './creator_monetization'
import { article as carbon_ads } from './carbon_ads'
import { article as capital_return } from './capital_return'
import { article as becoming_sustainable } from './becoming_sustainable'
import { article as accelerating_development } from './accelerating_development'
import { article as a_new_chapter_for_modrinth_servers } from './a_new_chapter_for_modrinth_servers'
export const articles = [
a_new_chapter_for_modrinth_servers,
accelerating_development,
becoming_sustainable,
capital_return,
carbon_ads,
creator_monetization,
creator_update,
creator_updates_july_2025,
design_refresh,
download_adjustment,
knossos_v2_1_0,
licensing_guide,
modpack_changes,
modpacks_alpha,
modrinth_app_beta,
modrinth_beta,
modrinth_servers_beta,
new_site_beta,
plugins_resource_packs,
pride_campaign_2025,
redesign,
skins_now_in_modrinth_app,
two_years_of_modrinth_history,
two_years_of_modrinth,
whats_modrinth,
windows_borderless_malware_disclosure,
whats_modrinth,
two_years_of_modrinth,
two_years_of_modrinth_history,
skins_now_in_modrinth_app,
redesign,
pride_campaign_2025,
plugins_resource_packs,
new_site_beta,
modrinth_servers_beta,
modrinth_beta,
modrinth_app_beta,
modpacks_alpha,
modpack_changes,
licensing_guide,
knossos_v2_1_0,
download_adjustment,
design_refresh,
creator_updates_july_2025,
creator_update,
creator_monetization,
carbon_ads,
capital_return,
becoming_sustainable,
accelerating_development,
a_new_chapter_for_modrinth_servers,
]

View File

@@ -9,6 +9,7 @@
"fix": "jiti ./compile.ts && eslint . --fix && prettier --write ."
},
"devDependencies": {
"@types/glob": "^9.0.0",
"@types/html-minifier-terser": "^7.0.2",
"@types/rss": "^0.0.32",
"@types/xml2js": "^0.4.14",
@@ -19,7 +20,7 @@
},
"dependencies": {
"@modrinth/utils": "workspace:*",
"fast-glob": "^3.3.3",
"glob": "^10.2.7",
"gray-matter": "^4.0.3",
"html-minifier-terser": "^7.2.0",
"rss": "^1.2.2",

View File

@@ -8,7 +8,7 @@ export function getRepoRoot(): string {
}
export function repoPath(...segments: string[]): string {
return path.join(getRepoRoot(), ...segments)
return path.posix.join(getRepoRoot(), ...segments)
}
export async function copyDir(
@@ -20,8 +20,8 @@ export async function copyDir(
await fs.mkdir(dest, { recursive: true })
const entries = await fs.readdir(src, { withFileTypes: true })
for (const entry of entries) {
const srcPath = path.join(src, entry.name)
const destPath = path.join(dest, entry.name)
const srcPath = path.posix.join(src, entry.name)
const destPath = path.posix.join(dest, entry.name)
if (entry.isDirectory()) {
await copyDir(srcPath, destPath, logFn)
} else if (entry.isFile()) {

View File

@@ -1,6 +1,8 @@
<!-- TODO: After checklist v1.5, move everything into src directory. -->
# @modrinth/moderation
This package contains the moderation checklist system used for reviewing projects on Modrinth. It provides a structured and transparent way to define moderation stages, actions, and messages that are displayed to moderators during the review process.
This package contains both the moderation checklist system used by moderators for reviewing projects on Modrinth, and the publishing checklist (nag system) that provides automated feedback to project authors during the submission process.
## Structure
@@ -9,22 +11,31 @@ The package is organized as follows:
```
/packages/moderation/
├── data/
│ ├── checklist.ts # Main checklist definition - imports and exports all stages
│ ├── messages/ # Markdown files containing message templates
│ ├── checklist.ts # Main moderation checklist definition - imports and exports all stages
│ ├── messages/ # Markdown files containing message templates for moderation
│ │ ├── title/ # Messages for the title stage
│ │ ├── description/ # Messages for the description stage
│ │ └── ... # One directory per stage
── stages/ # Stage definition files
├── title.ts # Title stage definition
├── description.ts # Description stage definition
└── ... # One file per stage
── stages/ # Moderation stage definition files
├── title.ts # Title stage definition
├── description.ts # Description stage definition
└── ... # One file per stage
│ └── nags/ # Publishing checklist (nag system) files
│ ├── core.ts # Core nags (required fields, basic validation)
│ ├── core.i18n.ts # Internationalization messages for core nags
│ └── ...
└── types/ # Type definitions
├── actions.ts # Action-related types
├── messages.ts # Message-related types
── stage.ts # Stage-related types
├── actions.ts # Action-related types (moderation)
├── messages.ts # Message-related types (moderation)
── stage.ts # Stage-related types (moderation)
└── nags.ts # Nag-related types (publishing checklist)
```
## Stages
## Moderation Checklist System
The moderation checklist provides a structured and transparent way to define moderation stages, actions, and messages that are displayed to moderators during the review process.
### Stages
A stage represents a discrete step in the moderation process, like checking a project's title, description, or links. Each stage has:
@@ -35,7 +46,7 @@ A stage represents a discrete step in the moderation process, like checking a pr
Stages are defined in individual files in the `data/stages` directory and are assembled into the complete checklist in `data/checklist.ts`.
## Actions
### Actions
Actions represent decisions moderators can make for each stage. They can be buttons, dropdowns, toggles, etc. Actions can have:
@@ -47,11 +58,11 @@ Actions represent decisions moderators can make for each stage. They can be butt
Each action requires a unique `id` field that is used for conditional logic and action relationships. The `suggestedStatus` and `severity` fields help determine the overall moderation outcome.
## Messages
### Messages
Messages are the actual text that will be included in communications to project authors. To promote maintainability and reuse, messages are stored as Markdown files in the `data/messages` directory, organized by stage.
### Variable replacement
#### Variable replacement
You can use variables in your messages that will be replaced with user input:
@@ -81,11 +92,11 @@ More text after the variable.
The `%MESSAGE%` placeholder will be replaced with the text entered by the moderator.
## Conditional logic
### Conditional logic
The moderation system supports conditional behavior that changes based on the selection of other actions.
### Conditional messages
#### Conditional messages
You can define different messages for an action based on other selected actions:
@@ -108,7 +119,7 @@ You can define different messages for an action based on other selected actions:
}
```
### Enabling and disabling actions
#### Enabling and disabling actions
Actions can enable or disable other actions when selected:
@@ -131,7 +142,7 @@ Actions can enable or disable other actions when selected:
}
```
### Conditional text inputs
#### Conditional text inputs
Text inputs can be conditionally shown based on selected actions:
@@ -147,3 +158,101 @@ relevantExtraInput: [
},
]
```
## Publishing Checklist (Nag System)
The nag system provides automated feedback to project authors during the submission process, helping them improve their projects before they reach moderation. It analyzes project data and provides suggestions, warnings, and requirements.
### Nags
A nag represents a specific issue or suggestion for improvement. Each nag has:
- A unique `id` for identification
- A `title` and `description` displayed to the user
- A `status` indicating severity: `'required'`, `'warning'`, or `'suggestion'`
- A `shouldShow` function that determines when the nag should be displayed
- An optional `link` to help users address the issue
### Internationalization
Each nag category has a corresponding `.i18n.ts` file containing message definitions:
```typescript
// Example from core.i18n.ts
export default defineMessages({
addDescriptionTitle: {
id: 'nags.add-description.title',
defaultMessage: 'Add a description',
},
addDescriptionDescription: {
id: 'nags.add-description.description',
defaultMessage:
"A description that clearly describes the project's purpose and function is required.",
},
})
```
If you want to use context in the messages, you can do so like this:
```typescript
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(messages.descriptionTooShortDescription, {
length: context.project.body?.length || 0,
minChars: MIN_DESCRIPTION_CHARS,
})
}
```
### Nag Context
The `NagContext` type provides access to:
- `project`: Current project data
- `versions`: Project versions
- `tags`: Frontend "tags" (generated state)
- `currentRoute`: Current page route
- and other data...
### Adding New Nags
To add a new nag:
1. Add the nag definition to the appropriate category file (or make a new category file and add it to `data/nags.ts`)
2. Add corresponding i18n messages to the `.i18n.ts` file
3. Implement the `shouldShow` logic based on project state
4. Add appropriate links to help users resolve the issue
5. Run `pnpm run fix` to fix lint issues & generate the root locale index.json file.
Example:
```typescript
// In description.ts
{
id: 'new-nag',
title: messages.newNagTitle,
description: messages.newNagDescription,
status: 'warning',
shouldShow: (context: NagContext) => {
// Your validation logic here
return someCondition
},
link: {
path: 'settings/description',
title: messages.editDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
},
}
```
```typescript
// In description.i18n.ts
newNagTitle: {
id: 'nags.new-nag.title',
defaultMessage: 'New Nag Title',
},
newNagDescription: {
id: 'nags.new-nag.description',
defaultMessage: 'Description of the new nag issue.',
```

View File

@@ -1,28 +1,32 @@
import type { Stage } from '../types/stage'
import modpackPermissionsStage from './modpack-permissions-stage'
import categories from './stages/categories'
import copyright from './stages/copyright'
import reupload from './stages/reupload'
import description from './stages/description'
import gallery from './stages/gallery'
import links from './stages/links'
import ruleFollowing from './stages/rule-following'
import sideTypes from './stages/side-types'
import slug from './stages/slug'
import summary from './stages/summary'
import title from './stages/title'
import titleSlug from './stages/title-slug'
import versions from './stages/versions'
import license from './stages/license'
import undefinedProject from './stages/undefined-project'
import statusAlerts from './stages/status-alerts'
export default [
title,
slug,
titleSlug,
summary,
description,
links,
license,
categories,
sideTypes,
gallery,
versions,
copyright,
reupload,
ruleFollowing,
modpackPermissionsStage,
statusAlerts,
undefinedProject,
] as ReadonlyArray<Stage>

View File

@@ -1,3 +1,3 @@
## Misuse of Tags
Per section 5.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous), it is important that the metadata of your projects is accurate. Including that selected tags honestly represent your project.
Per section 5.1 of %RULES%, it is important that the metadata of your projects is accurate. Including that selected tags honestly represent your project.

View File

@@ -0,0 +1 @@
It looks like the Optimization tag is not accurate for this project.

View File

@@ -0,0 +1,5 @@
Currently, your pack has multiple Resolutions selected, in most cases, this will misrepresent your project.
For a brief rundown of how this works:
Vanilla Minecraft textures like blocks and items have a width and height of 16 pixels.
This means that packs with textures the same size as the default are considered 16x.
Some packs make the resolution of textures smaller than the default, such as 8x, and some bigger, such as 32x.

View File

@@ -0,0 +1,2 @@
**Featured Tags:** %PROJECT_CATEGORIES% \
**Additional Tags:** %PROJECT_ADDITIONAL_CATEGORIES%

View File

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

View File

@@ -0,0 +1,4 @@
**Discord:** %PROJECT_DISCORD_URL% \
**Issues:** %PROJECT_ISSUES_URL% \
**Source:** %PROJECT_SOURCE_URL% \
**Wiki:** %PROJECT_WIKI_URL%

View File

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

View File

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

View File

@@ -0,0 +1 @@
**Applying for:** `%PROJECT_REQUESTED_STATUS%`

View File

@@ -0,0 +1,2 @@
**Summary:**
`%PROJECT_SUMMARY%`

View File

@@ -0,0 +1,4 @@
**Title:** %PROJECT_TITLE% \
**Slug:** `%PROJECT_SLUG%`
**Title issues?**

View File

@@ -1,7 +1,6 @@
## Insufficient Description
Per section 2.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#general-expectations) your project's Description should clearly inform the reader of the content, purpose, and appeal of your project.
Per section 2.1 of %RULES% your project's Description should clearly inform the reader of the content, purpose, and appeal of your project.
Currently, it looks like there are some missing details.
> %EXPLAINER%
%EXPLAINER%

View File

@@ -1,5 +1,5 @@
## No English Description
Per section 2.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#accessibility) a project's Summary and Description must be in English, unless meant exclusively for non-English use, such as translations.
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.
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).
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).

View File

@@ -1,6 +1,6 @@
## Description Accessibility
Per section 2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#clear-and-honest-function) your description must be plainly readable and accessible.
Per section 2 of %RULES% your description must be plainly readable and accessible.
Using non-standard text characters like Zalgo or "fancy text" in place of text anywhere in your project, including the Description, Summary, or Title can make your project pages inaccessible.

View File

@@ -1,7 +1,4 @@
## Unfinished Description
It looks like your project Description is still a WIP (Work In Progress).
> %REASON%
Please remember to submit only when ready, as it is important your project meets the requirements of Section 2.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#general-expectations), if you have any questions on this feel free to reach out!
It looks like your project Description is still a Work In Progress.
Please remember to submit only when ready, as it is important your project meets the requirements of Section 2.1 of %RULES%.

View File

@@ -1,6 +1,6 @@
## Insufficient Gallery Images
We ask that projects like yours show off their content using images in the Gallery, or optionally in the Description, in order to effectively and clearly inform users of its content per section 2.1 of [Modrinth's content rules](https://modrinth.com/legal/rules#general-expectations).
We ask that projects like yours show off their content using images in the Gallery, or optionally in the Description, in order to effectively and clearly inform users of its content per section 2.1 of %RULES%.
Keep in mind that you should:
- Set a featured image that best represents your project.

View File

@@ -1,3 +1,3 @@
## Unrelated Gallery Images
Per section 5.5 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) any images in your project's Gallery must be relevant to the project and also include a Title.
Per section 5.5 of %RULES% any images in your project's Gallery must be relevant to the project and also include a Title.

View File

@@ -0,0 +1 @@
If this is a project-specific License you've written yourself, you must host the full License in a way that makes it publicly available, for instance, in a public source repository on a platform like GitHub.

View File

@@ -0,0 +1,4 @@
## Invalid License Link
It's important that your project's License link is accurate and leads directly to a valid license for this content.
Your current link: `%PROJECT_LICENSE_URL%` does not appear to lead to a valid license for this project, or it is not publicly accessable.

View File

@@ -0,0 +1,5 @@
## No Source Code Provided
Your project's license of `%PROJECT_LICENSE_NAME%`, requires source disclosure.
Consider adding a Source link to your project's repository, or including a Sources file for each version as an Additional File.
Keep in mind this may be a requirement of the source work's licensing, which must be abided per section 4 of %RULES%.

View File

@@ -0,0 +1,4 @@
## No Source Code Provided
Your project's license of `%PROJECT_LICENSE_NAME%`, requires source disclosure.
Consider adding a Source link to your project's repository, or including a Sources file for each version as an Additional File. You may also want to refer to %LICENSING_GUIDE% if you wish to select a different License, remember to make sure your selected License is consistent with the license in your project's files as well.

View File

@@ -1,3 +1,4 @@
## Misuse of External Resources
## Misuse of Links
Per section 5.4 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) all links must lead to correctly labeled publicly available resources that are directly related to your project.
Per section 5.4 of %RULES% all %PROJECT_LINKS_FLINK% must lead to correctly labeled publicly available resources that are directly related to your project.
Currently it looks like your %MISUSED_LINKS% link(s) are misused or incorrectly labeled.

View File

@@ -1,5 +0,0 @@
## Unreachable Links
Per section 5.4 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) all links must lead to correctly labeled publicly available resources that are directly related to your project.
Currently, your %LINK% link is inaccessible!

View File

@@ -1,5 +0,0 @@
## Unreachable Links
Per section 5.4 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) all links must lead to correctly labeled publicly available resources that are directly related to your project.
Currently, your Source link directs to a Page Not Found error, likely because your repository is private, make sure to make your repository public before resubmitting your project!

View File

@@ -0,0 +1 @@
Currently, your Discord link directs to an invalid invite, likely because your invite has expired. Make sure to set your invite link to permanent with unlimited uses before resubmitting your project.

View File

@@ -0,0 +1 @@
Currently, your Source link directs to a Page Not Found error, likely because your repository is private. Make sure to set your repository to public before resubmitting your project.

View File

@@ -0,0 +1,3 @@
## Unreachable Links
Per section 5.4 of %RULES% all %PROJECT_LINKS_FLINK% must lead to correctly labeled publicly available resources that are directly related to your project.

View File

@@ -0,0 +1,4 @@
## Forks and Reuploads
Per section 4 of %RULES%, please provide proof that this project is both license-abiding and significantly divergent from the source work.
Alternatively, please provide proof of your explicit permission from the author of the source work to distribute this content on Modrinth.

View File

@@ -0,0 +1,6 @@
## 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

@@ -0,0 +1,4 @@
## Insufficient Fork
This project does not appear to significantly diverge from the source work, or does not abide by the license of the source work as required by section 4 of %RULES%.
Please provide proof of your explicit permission to distribute this project from the creator(s) of the source work.

View File

@@ -0,0 +1,5 @@
## Proof of permissions
This project appears to contain content from other creators.
Per section 4 of %RULES%, we ask that you provide proof of your permission to distribute this content, or derivatives of this content in your project on Modrinth.
Either implicit permission abiding by the terms of the content's license(s) or explicit permission from the original creator of the content.

View File

@@ -1,7 +1,5 @@
## Reuploads are forbidden
This project appears to contain content from %ORIGINAL_PROJECT% by %ORIGINAL_AUTHOR%.
Per section 4 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) this is strictly forbidden.
If you believe this is an error, or you can verify you are the creator and rightful owner of this content please let us know. Otherwise, we ask that you **do not resubmit this project**.`,
This project appears to contain content from %ORIGINAL_PROJECT% by %ORIGINAL_AUTHOR%.
Per section 4 of %RULES% this is strictly forbidden.
If you believe this is an error, or you can verify you are the creator and rightful owner of this content please let us know. Otherwise, we ask that you **do not resubmit this project**.

View File

@@ -1,5 +1 @@
# Does not follow content rules
Our moderators have determined that your project does not follow Modrinth's Content Rules, and has been rejected.
%MESSAGE%

View File

@@ -1,6 +1,6 @@
## Environment Information
Per section 5.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous), it is important that the metadata of your projects is accurate, including whether the project runs on the client or server side.
Per section 5.1 of %RULES%, it is important that the metadata of your projects is accurate, including whether the project runs on the client or server side.
For a brief rundown of how this works:

View File

@@ -1,6 +1,6 @@
## Incorrect Environment Information
Per section 5.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous), it is important that the metadata of your projects is accurate, including whether the project runs on the client or server side.
Per section 5.1 of %RULES%, it is important that the metadata of your projects is accurate, including whether the project runs on the client or server side.
For a brief rundown of how this works:

View File

@@ -1,3 +1,3 @@
## Misuse of Slug
Per section 5.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) your project slug (URL) must accurately represent your project.
Per section 5.2 of %RULES% must accurately represent your project.

View File

@@ -0,0 +1,6 @@
---
## Account Issues Indicated
We're sorry to hear you're having trouble accessing your accounts, unfortunately, our moderation team is unable to assist with account-related issues.
Before resubmitting your project, %SUPPORT%.

View File

@@ -0,0 +1,5 @@
---
Unfortunately, our AutoMod cannot read your project's Description or your messages to moderation.
AutoMod will warn both you and our Moderation Staff about potential issues, but if you've already followed the necessary steps these warnings can safely be ignored.
Note that if your project is being rejected by AutoMod this means your project has content that can not be included in your modpack and must be removed before resubmission, including deleting versions of your modpack that include the content.

View File

@@ -0,0 +1,5 @@
---
## Corrections Applied
I've gone ahead and corrected the issues listed above so your project can be Approved.

View File

@@ -0,0 +1,8 @@
---
## Private Use
Under normal circumstances, your project would be rejected due to the issues listed above.
However, since your project is not intended for for public use, these requirements will be waived and your project will be unlisted. This means it will remain accessible through a direct link without appearing in public search results, allowing you to share it privately.
If you're okay with this, or submitted your project to be unlisted already, than no further action is necessary.
If you would like to publish your project publicly, please address all moderation concerns before resubmitting this project.

View File

@@ -0,0 +1,5 @@
## 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

@@ -0,0 +1,4 @@
## 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

@@ -1,6 +1,6 @@
## Insufficient Summary
Per section 5.3 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) your Summary can not include any extra formatting such as lists, or links.
Per section 5.3 of %RULES% your Summary can not include any extra formatting such as lists, or links.
Your project summary should provide a brief overview of your project that informs and entices users.

View File

@@ -1,5 +1,5 @@
## Insufficient Summary
Per section 5.3 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) your project summary should provide a brief overview of your project that informs and entices users.
Per section 5.3 of %RULES% your project summary should provide a brief overview of your project that informs and entices users.
This is the first thing most people will see about your mod other than the Logo, so it's important it be accurate, reasonably detailed, and exciting.

View File

@@ -1,5 +1,5 @@
## No English Summary
Per section 2.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#accessibility) a project's Summary and Description 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 Summary but we ask that you also add an English translation.

View File

@@ -1,6 +1,6 @@
## Insufficient Summary
Per section 5.3 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) your Summary can not be the same as your project's Title.
Per section 5.3 of %RULES% your Summary can not be the same as your project's Title.
Your project summary should provide a brief overview of your project that informs and entices users.

View File

@@ -1,7 +1,6 @@
## Project Title
## Minecraft Project Names
Projects must not use Minecraft's branding or include "Minecraft" as a significant part of the title.
The title of your project may be confusingly similar to the game, and we encourage you to change your title to avoid a potential violation of Minecraft's Usage Guidelines.
Abbreviations like "MC" or elaborate titles that do not make the name Minecraft a significant portion of the name are okay.
Projects must not use Minecraft's branding or include "Minecraft" as a significant part of the title.
Your project's current Name of `%PROJECT_TITLE%` may be confusingly similar to, or imply association with, the game Minecraft. We encourage you to change your project's [Name](%PROJECT_SETTINGS_LINK%) to avoid a potential violation of Minecraft's Usage Guidelines.
Abbreviations like "MC" or elaborate titles that do not make the name Minecraft a significant portion of the name are okay.
When editing your project's Name, remember to update its [URL](%PROJECT_SETTINGS_LINK%) to match.

View File

@@ -0,0 +1,2 @@
You may reference the source work in the Description of your fork, however, you must do so in a way that is unlikely to cause confusion or imply association with or endorsement from the author of the source work.
Additionally, per section 4 of %RULES%, we ask that you ensure your project's Name and Branding abide by the license of the source work.

View File

@@ -0,0 +1 @@
You may reference the projects that make up the focus point of your modpack, however, you must do so in a way that is unlikely to cause confusion or imply association with or endorsement from the author of that content.

View File

@@ -1,3 +1,5 @@
## Project Branding
Per section 1.8 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) we ask that you change your project title and other relevant branding to avoid causing confusion or implying association with existing projects.
Per section 1.8 of %RULES%, your project or its branding must not imply association or be easily confused with any other person or organization.
We ask that you change your project's [Name](%PROJECT_SETTINGS_LINK%) and other relevant branding to avoid causing confusion or implying association with existing projects or individuals.
When editing your project's Name, remember to update its [URL](%PROJECT_SETTINGS_LINK%) to match.

View File

@@ -1,7 +1,6 @@
## Misuse of Title
## Misuse of Project Name
Per section 5.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) we ask that you limit the title to just the name of your project.
Additional information, such as themes, tags, supported versions or loaders, etc. should be saved for the Summary or Description.
When changing your project title, remember to also ensure that your project slug (URL) matches and accurately represents your project.
Per section 5.2 of %RULES%, your project's [Name](%PROJECT_SETTINGS_LINK%) should not include unnecessary information such as loaders, themes, tags, or versions.
Your project's current Name of `%PROJECT_TITLE%` appears to contain extra information.
We ask that you remove all additional information from the [Name](%PROJECT_SETTINGS_LINK%). Instead, consider including this in your project's Summary or Description, or as a part of its relevant metadata.
When editing your project's Name, remember to update its [URL](%PROJECT_SETTINGS_LINK%) to match.

View File

@@ -0,0 +1,4 @@
## No Versions
It looks like all versions of your project have been deleted, meaning that our Moderation Team cannot finish reviewing your project.
Please resubmit your project once you've uploaded a new version.

View File

@@ -0,0 +1,5 @@
## Unsupported Project
Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules), Modrinth does not support uploading multiple variations of your project as Additional files.
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want.
We ask that you upload each alternate version of your project as a new project, ensuring that all users will be able to access and easily find your content.

View File

@@ -0,0 +1,5 @@
## Unsupported Project
Modrinth does not support uploading projects that have unnecessary or extraneous installation steps.
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want.
We ask that you upload each alternate version of your project as a new project, ensuring that all users will be able to access and easily find your content.

View File

@@ -0,0 +1,6 @@
## Unsupported Project
Modrinth does not support uploading multiple variations of your project as separate versions.
New Versions of your project should strictly be a linear upgrade of the same content.
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want.
We ask that you upload each alternate version of your project as a new project, ensuring that all users will be able to access and easily find your content.

View File

@@ -0,0 +1,4 @@
## Incorrect Additional Files
Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) the additional files section should only be used for specific designated purposes such as a `Sources.jar`.
To ensure a smooth experience for you and your users, please upload each alternate version of your modpack as its own Modpack project, thank you.

View File

@@ -0,0 +1,6 @@
## Unsupported Project
Modrinth does not support uploading multiple variations of your project as separate versions.
New Versions of your project should strictly be a linear upgrade of the same content.
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want.
To ensure a smooth experience for you and your users, please upload each alternate version of your modpack as its own Modpack project, thank you.

View File

@@ -0,0 +1,5 @@
## Incorrect Additional Files
Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) the additional files section should only be used for specific designated purposes such as a `Sources.jar`.
Modrinth does not support the upload of modpacks in the `.zip` format, as this may cause issues for Modrinth users or distribute copyrighted content without the proper permissions.
If you would like to upload a server-specific version of your modpack, consider creating a separate Modpack project.

View File

@@ -0,0 +1,5 @@
## Broken Version
It looks like you've uploaded multiple files with the same name to the same version. This triggers a bug that produces duplicate primary file entries.
This may cause issues for users and creators as it would prevent Modpacks that include this content from functioning in the Modrinth App and other launchers.
We ask that you delete and reupload all applicable versions. Be sure to only upload one file to each version before resubmission.

View File

@@ -1,7 +0,0 @@
## Incorrect Use of Additional Files
It looks like you've uploaded multiple `mod.jar` files to one Version as Additional Files. Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous) each Version of your project must include only one `mod.jar` that corresponds to its respective Minecraft and loader versions.
This allows users to easily find and download the file they need for the version they're on with ease. The Additional Files feature can be used for things like a `Sources.jar`.
Please upload each version of your mod separately, thank you.

View File

@@ -0,0 +1,5 @@
## Incorrect Use of Additional Files
It looks like you've uploaded multiple primary files to one Version as Additional Files. Per section 5.7 of %RULES% each Version of your project must include only one primary file that corresponds to its respective Minecraft and loader versions.
This allows users to easily find and download the content they need for their game profile with ease. The Additional Files feature can be used for things like a `Sources.jar`.
Please upload each version of your project separately, thank you.

View File

@@ -0,0 +1,3 @@
## Data Packs on Modrinth
It looks like you've selected loaders for your Data Pack that are causing it to be marked as a different project type. Data Packs must only be uploaded with the "Data Pack" loader selected. Please re-upload all versions of your data pack and make sure to only select "Data Pack" as the loader.

View File

@@ -1,3 +1,5 @@
## Modpacks on Modrinth
It looks like you've uploaded your Modpack as a `.zip`, unfortunately, this is invalid and is why your project type is "Mod". I recommend taking a look at our support page about [Modrinth Modpacks](https://support.modrinth.com/en/articles/8802250-modpacks-on-modrinth), and once you're ready feel free to resubmit your project as a `.mrpack`. Don't forget to delete the old files from your Versions!
It looks like you've uploaded your Modpack as a `.zip`, unfortunately, this is invalid and is why your project type is "Mod". Please refer to our support article to learn more about %MODPACKS_ON_MODRINTH%.
Once you're ready feel free to resubmit your project as a `.mrpack`.
Don't forget to delete the old files from your %PROJECT_VERSIONS_FLINK%!

View File

@@ -0,0 +1,5 @@
## Excessive File Size
This project appears to include libs or dependencies, unnecessarily redistributing their entire contents.
This is often due to an error in project structure or compilation, and in some cases, may violate the copyrights or licensing agreements of these libraries.
We ask that you remove all unnecessary files, assets, and code from your project before resubmission.

View File

@@ -0,0 +1,4 @@
## Vanilla Assets
Your resource pack currently includes an excessive amount of unmodified assets from vanilla Minecraft.
Please remove these from your pack and ensure your project only contains original assets created by you, thank you.

View File

@@ -1,15 +1,22 @@
import type { ModerationModpackPermissionApprovalType, Project } from '@modrinth/utils'
import type { Stage } from '../types/stage'
import { BoxIcon } from '@modrinth/assets'
import { PackageOpenIcon } from '@modrinth/assets'
export default {
id: 'modpack-permissions',
title: 'Modpack Permissions',
icon: BoxIcon,
icon: PackageOpenIcon,
// Replace me please.
guidance_url: 'https://docs.modrinth.com/moderation/modpack-permissions',
guidance_url:
'https://www.notion.so/Content-Moderation-Cheat-Sheets-22d5ee711bf081a4920ef08879fe6bf5?source=copy_link#22d5ee711bf08116bd8bc1186f357062',
shouldShow: (project: Project) => project.project_type === 'modpack',
actions: [],
actions: [
{
id: 'button',
type: 'button',
label: 'This dummy button must be present or the stage will not appear.',
},
],
} as Stage
export const finalPermissionMessages: Record<
@@ -18,7 +25,7 @@ export const finalPermissionMessages: Record<
> = {
yes: undefined,
'with-attribution-and-source': undefined,
'with-attribution': `The following content has attribution requirements, meaning that you must link back to the page where you originally found this content in your modpack description or version changelog (e.g. linking a mod's CurseForge page if you got it from CurseForge):`,
'with-attribution': `The following content has attribution requirements, meaning that you must link back to the page where you originally found this content in your Modpack's description or version changelog (e.g. linking a mod's CurseForge page if you got it from CurseForge):`,
no: 'The following content is not allowed in Modrinth modpacks due to licensing restrictions. Please contact the author(s) directly for permission or remove the content from your modpack:',
'permanent-no': `The following content is not allowed in Modrinth modpacks, regardless of permission obtained. This may be because it breaks Modrinth's content rules or because the authors, upon being contacted for permission, have declined. Please remove the content from your modpack:`,
unidentified: `The following content could not be identified. Please provide proof of its origin along with proof that you have permission to include it:`,

View File

@@ -0,0 +1,7 @@
import type { Nag } from '../types/nags'
import { coreNags } from './nags/core'
import { descriptionNags } from './nags/description'
import { linksNags } from './nags/links'
import { tagsNags } from './nags/tags'
export default [...coreNags, ...linksNags, ...descriptionNags, ...tagsNags] as Nag[]

View File

@@ -0,0 +1,116 @@
import { defineMessages } from '@vintl/vintl'
export default defineMessages({
moderatorFeedbackTitle: {
id: 'nags.moderator-feedback.title',
defaultMessage: 'Review moderator feedback',
},
moderatorFeedbackDescription: {
id: 'nags.moderator-feedback.description',
defaultMessage:
'Review any feedback from moderators regarding your project before resubmitting.',
},
moderationTitle: {
id: 'nags.moderation.title',
defaultMessage: 'Visit moderation thread',
},
uploadVersionTitle: {
id: 'nags.upload-version.title',
defaultMessage: 'Upload a version',
},
uploadVersionDescription: {
id: 'nags.upload-version.description',
defaultMessage: 'At least one version is required for a project to be submitted for review.',
},
versionsTitle: {
id: 'nags.versions.title',
defaultMessage: 'Visit versions page',
},
addDescriptionTitle: {
id: 'nags.add-description.title',
defaultMessage: 'Add a description',
},
addDescriptionDescription: {
id: 'nags.add-description.description',
defaultMessage:
"A description that clearly describes the project's purpose and function is required.",
},
settingsDescriptionTitle: {
id: 'nags.settings.description.title',
defaultMessage: 'Visit description settings',
},
addIconTitle: {
id: 'nags.add-icon.title',
defaultMessage: 'Add an icon',
},
addIconDescription: {
id: 'nags.add-icon.description',
defaultMessage:
'Your project should have a nice-looking icon to uniquely identify your project at a glance.',
},
settingsTitle: {
id: 'nags.settings.title',
defaultMessage: 'Visit general settings',
},
featureGalleryImageTitle: {
id: 'nags.feature-gallery-image.title',
defaultMessage: 'Feature a gallery image',
},
featureGalleryImageDescription: {
id: 'nags.feature-gallery-image.description',
defaultMessage: 'Featured gallery images may be the first impression of many users.',
},
galleryTitle: {
id: 'nags.gallery.title',
defaultMessage: 'Visit gallery page',
},
selectTagsTitle: {
id: 'nags.select-tags.title',
defaultMessage: 'Select tags',
},
selectTagsDescription: {
id: 'nags.select-tags.description',
defaultMessage: 'Select all tags that apply to your project.',
},
settingsTagsTitle: {
id: 'nags.settings.tags.title',
defaultMessage: 'Visit tag settings',
},
addLinksTitle: {
id: 'nags.add-links.title',
defaultMessage: 'Add external links',
},
addLinksDescription: {
id: 'nags.add-links.description',
defaultMessage:
'Add any relevant links targeted outside of Modrinth, such as sources, issues, or a Discord invite.',
},
settingsLinksTitle: {
id: 'nags.settings.links.title',
defaultMessage: 'Visit links settings',
},
selectEnvironmentsTitle: {
id: 'nags.select-environments.title',
defaultMessage: 'Select supported environments',
},
selectEnvironmentsDescription: {
id: 'nags.select-environments.description',
defaultMessage: `Select if the {projectType} functions on the client-side and/or server-side.`,
},
settingsEnvironmentsTitle: {
id: 'nags.settings.environments.title',
defaultMessage: 'Visit general settings',
},
selectLicenseTitle: {
id: 'nags.select-license.title',
defaultMessage: 'Select license',
},
selectLicenseDescription: {
id: 'nags.select-license.description',
defaultMessage: 'Select the license your {projectType} is distributed under.',
},
settingsLicenseTitle: {
id: 'nags.settings.license.title',
defaultMessage: 'Visit license settings',
},
})

View File

@@ -0,0 +1,151 @@
import type { Nag, NagContext } from '../../types/nags'
import { formatProjectType } from '@modrinth/utils'
import { useVIntl } from '@vintl/vintl'
import messages from './core.i18n'
export const coreNags: Nag[] = [
{
id: 'moderator-feedback',
title: messages.moderatorFeedbackTitle,
description: messages.moderatorFeedbackDescription,
status: 'suggestion',
shouldShow: (context: NagContext) =>
context.tags.rejectedStatuses.includes(context.project.status),
link: {
path: 'moderation',
title: messages.moderationTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-moderation',
},
},
{
id: 'upload-version',
title: messages.uploadVersionTitle,
description: messages.uploadVersionDescription,
status: 'required',
shouldShow: (context: NagContext) => context.versions.length < 1,
link: {
path: 'versions',
title: messages.versionsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-versions',
},
},
{
id: 'add-description',
title: messages.addDescriptionTitle,
description: messages.addDescriptionDescription,
status: 'required',
shouldShow: (context: NagContext) =>
context.project.body === '' || context.project.body.startsWith('# Placeholder description'),
link: {
path: 'settings/description',
title: messages.settingsDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
},
},
{
id: 'add-icon',
title: messages.addIconTitle,
description: messages.addIconDescription,
status: 'suggestion',
shouldShow: (context: NagContext) => !context.project.icon_url,
link: {
path: 'settings',
title: messages.settingsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'feature-gallery-image',
title: messages.featureGalleryImageTitle,
description: messages.featureGalleryImageDescription,
status: 'suggestion',
shouldShow: (context: NagContext) => {
const featuredGalleryImage = context.project.gallery?.find((img) => img.featured)
return context.project?.gallery?.length === 0 || !featuredGalleryImage
},
link: {
path: 'gallery',
title: messages.galleryTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-gallery',
},
},
{
id: 'select-tags',
title: messages.selectTagsTitle,
description: messages.selectTagsDescription,
status: 'suggestion',
shouldShow: (context: NagContext) =>
context.project.versions.length > 0 && context.project.categories.length < 1,
link: {
path: 'settings/tags',
title: messages.settingsTagsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
},
},
{
id: 'add-links',
title: messages.addLinksTitle,
description: messages.addLinksDescription,
status: 'suggestion',
shouldShow: (context: NagContext) =>
!(
context.project.issues_url ||
context.project.source_url ||
context.project.wiki_url ||
context.project.discord_url ||
context.project.donation_urls.length > 0
),
link: {
path: 'settings/links',
title: messages.settingsLinksTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
},
},
{
id: 'select-environments',
title: messages.selectEnvironmentsTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(messages.selectEnvironmentsDescription, {
projectType: formatProjectType(context.project.project_type).toLowerCase(),
})
},
status: 'required',
shouldShow: (context: NagContext) => {
const excludedTypes = ['resourcepack', 'plugin', 'shader', 'datapack']
return (
context.project.versions.length > 0 &&
!excludedTypes.includes(context.project.project_type) &&
(context.project.client_side === 'unknown' ||
context.project.server_side === 'unknown' ||
(context.project.client_side === 'unsupported' &&
context.project.server_side === 'unsupported'))
)
},
link: {
path: 'settings',
title: messages.settingsEnvironmentsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'select-license',
title: messages.selectLicenseTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(messages.selectLicenseDescription, {
projectType: formatProjectType(context.project.project_type).toLowerCase(),
})
},
status: 'required',
shouldShow: (context: NagContext) => context.project.license.id === 'LicenseRef-Unknown',
link: {
path: 'settings/license',
title: messages.settingsLicenseTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-license',
},
},
]

View File

@@ -0,0 +1,88 @@
import { defineMessages } from '@vintl/vintl'
export default defineMessages({
descriptionTooShortTitle: {
id: 'nags.description-too-short.title',
defaultMessage: 'Description may be insufficient',
},
descriptionTooShortDescription: {
id: 'nags.description-too-short.description',
defaultMessage:
"Your description is {length} characters. It's recommended to have at least {minChars} characters to provide users with enough information about your project.",
},
longHeadersTitle: {
id: 'nags.long-headers.title',
defaultMessage: 'Headers are too long',
},
longHeadersDescription: {
id: 'nags.long-headers.description',
defaultMessage:
'{count, plural, one {# header} other {# headers}} in your description {count, plural, one {is} other {are}} too long. Headers should be concise and act as section titles, not full sentences.',
},
summaryTooShortTitle: {
id: 'nags.summary-too-short.title',
defaultMessage: 'Summary may be insufficient',
},
summaryTooShortDescription: {
id: 'nags.summary-too-short.description',
defaultMessage:
"Your summary is {length} characters. It's recommended to have at least {minChars} characters to provide users with enough information about your project.",
},
minecraftTitleClauseTitle: {
id: 'nags.minecraft-title-clause.title',
defaultMessage: 'Title contains "Minecraft"',
},
minecraftTitleClauseDescription: {
id: 'nags.minecraft-title-clause.description',
defaultMessage:
'Please remove "Minecraft" from your title. You cannot use "Minecraft" in your title for legal reasons.',
},
titleContainsTechnicalInfoTitle: {
id: 'nags.title-contains-technical-info.title',
defaultMessage: 'Title contains loader or version info',
},
titleContainsTechnicalInfoDescription: {
id: 'nags.title-contains-technical-info.description',
defaultMessage:
'Removing these helps keep titles clean and makes your project easier to find. Version and loader information is automatically displayed alongside your project.',
},
summarySameAsTitleTitle: {
id: 'nags.summary-same-as-title.title',
defaultMessage: 'Summary is project name',
},
summarySameAsTitleDescription: {
id: 'nags.summary-same-as-title.description',
defaultMessage:
"Your summary is the same as your project name. Please change it. It's recommended to have a unique summary to provide more context about your project.",
},
imageHeavyDescriptionTitle: {
id: 'nags.image-heavy-description.title',
defaultMessage: 'Description is mostly images',
},
imageHeavyDescriptionDescription: {
id: 'nags.image-heavy-description.description',
defaultMessage:
'Please add more descriptive text to help users understand your project, especially those using screen readers or with slow internet connections.',
},
missingAltTextTitle: {
id: 'nags.missing-alt-text.title',
defaultMessage: 'Images missing alt text',
},
missingAltTextDescription: {
id: 'nags.missing-alt-text.description',
defaultMessage:
'Some of your images are missing alt text, which is important for accessibility, especially for visually impaired users.',
},
editDescriptionTitle: {
id: 'nags.edit-description.title',
defaultMessage: 'Edit description',
},
editSummaryTitle: {
id: 'nags.edit-summary.title',
defaultMessage: 'Edit summary',
},
editTitleTitle: {
id: 'nags.edit-title.title',
defaultMessage: 'Edit title',
},
})

View File

@@ -0,0 +1,226 @@
import type { Nag, NagContext } from '../../types/nags'
import { useVIntl } from '@vintl/vintl'
import messages from './description.i18n'
export const MIN_DESCRIPTION_CHARS = 500
export const MAX_HEADER_LENGTH = 100
export const MIN_SUMMARY_CHARS = 125
function analyzeHeaderLength(markdown: string): { hasLongHeaders: boolean; longHeaders: string[] } {
if (!markdown) return { hasLongHeaders: false, longHeaders: [] }
const withoutCodeBlocks = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
const headerRegex = /^(#{1,3})\s+(.+)$/gm
const headers = [...withoutCodeBlocks.matchAll(headerRegex)]
const longHeaders: string[] = []
headers.forEach((match) => {
const headerText = match[2].trim()
const sentenceEnders = /[.!?]+/g
const sentences = headerText.split(sentenceEnders).filter((s) => s.trim().length > 0)
const hasSentenceEnders = sentenceEnders.test(headerText)
const isVeryLong = headerText.length > MAX_HEADER_LENGTH
const hasMultipleSentences = sentences.length > 1
if (hasSentenceEnders || isVeryLong || hasMultipleSentences) {
longHeaders.push(headerText)
}
})
return {
hasLongHeaders: longHeaders.length > 0,
longHeaders,
}
}
function analyzeImageContent(markdown: string): { imageHeavy: boolean; hasEmptyAltText: boolean } {
if (!markdown) return { imageHeavy: false, hasEmptyAltText: false }
const withoutCodeBlocks = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
const imageRegex = /!\[([^\]]*)\]\([^)]+\)/g
const images = [...withoutCodeBlocks.matchAll(imageRegex)]
const htmlImageRegex = /<img[^>]*>/gi
const htmlImages = [...withoutCodeBlocks.matchAll(htmlImageRegex)]
const totalImages = images.length + htmlImages.length
if (totalImages === 0) return { imageHeavy: false, hasEmptyAltText: false }
const textWithoutImages = withoutCodeBlocks
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '')
.replace(/<img[^>]*>/gi, '')
.replace(/\s+/g, ' ')
.trim()
const textLength = textWithoutImages.length
const imageHeavy = textLength < 100 || (totalImages >= 3 && textLength < 200)
const hasEmptyAltText =
images.some((match) => !match[1]?.trim()) ||
htmlImages.some((match) => {
const altMatch = match[0].match(/alt\s*=\s*["']([^"']*)["']/i)
return !altMatch || !altMatch[1]?.trim()
})
return { imageHeavy, hasEmptyAltText }
}
export const descriptionNags: Nag[] = [
{
id: 'description-too-short',
title: messages.descriptionTooShortTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(messages.descriptionTooShortDescription, {
length: context.project.body?.length || 0,
minChars: MIN_DESCRIPTION_CHARS,
})
},
status: 'warning',
shouldShow: (context: NagContext) => {
const bodyLength = context.project.body?.trim()?.length || 0
return bodyLength < MIN_DESCRIPTION_CHARS && bodyLength !== 0
},
link: {
path: 'settings/description',
title: messages.editDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
},
},
{
id: 'long-headers',
title: messages.longHeadersTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
const { longHeaders } = analyzeHeaderLength(context.project.body || '')
const count = longHeaders.length
return formatMessage(messages.longHeadersDescription, {
count,
})
},
status: 'warning',
shouldShow: (context: NagContext) => {
const { hasLongHeaders } = analyzeHeaderLength(context.project.body || '')
return hasLongHeaders
},
link: {
path: 'settings/description',
title: messages.editDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
},
},
{
id: 'summary-too-short',
title: messages.summaryTooShortTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(messages.summaryTooShortDescription, {
length: context.project.description?.length || 0,
minChars: MIN_SUMMARY_CHARS,
})
},
status: 'warning',
shouldShow: (context: NagContext) => {
const summaryLength = context.project.description?.trim()?.length || 0
return summaryLength < MIN_SUMMARY_CHARS && summaryLength !== 0
},
link: {
path: 'settings',
title: messages.editSummaryTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'minecraft-title-clause',
title: messages.minecraftTitleClauseTitle,
description: messages.minecraftTitleClauseDescription,
status: 'required',
shouldShow: (context: NagContext) => {
const title = context.project.title?.toLowerCase() || ''
const wordsInTitle = title.split(' ').filter((word) => word.length > 0)
return title.includes('minecraft') && title.length > 0 && wordsInTitle.length <= 3
},
link: {
path: 'settings',
title: messages.editTitleTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'title-contains-technical-info',
title: messages.titleContainsTechnicalInfoTitle,
description: messages.titleContainsTechnicalInfoDescription,
status: 'warning',
shouldShow: (context: NagContext) => {
const title = context.project.title?.toLowerCase() || ''
if (!title) return false
const loaderNames =
context.tags.loaders?.map((loader: { name: string }) => loader.name?.toLowerCase()) || []
const hasLoader = loaderNames.some((loader) => loader && title.includes(loader.toLowerCase()))
const versionPatterns = [/\b1\.\d+(\.\d+)?\b/]
const hasVersionPattern = versionPatterns.some((pattern) => pattern.test(title))
return hasLoader || hasVersionPattern
},
link: {
path: 'settings',
title: messages.editTitleTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'summary-same-as-title',
title: messages.summarySameAsTitleTitle,
description: messages.summarySameAsTitleDescription,
status: 'required',
shouldShow: (context: NagContext) => {
const title = context.project.title?.trim() || ''
const summary = context.project.description?.trim() || ''
return title === summary && title.length > 0 && summary.length > 0
},
link: {
path: 'settings',
title: messages.editSummaryTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'image-heavy-description',
title: messages.imageHeavyDescriptionTitle,
description: messages.imageHeavyDescriptionDescription,
status: 'warning',
shouldShow: (context: NagContext) => {
const { imageHeavy } = analyzeImageContent(context.project.body || '')
return imageHeavy
},
link: {
path: 'settings/description',
title: messages.editDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
},
},
{
id: 'missing-alt-text',
title: messages.missingAltTextTitle,
description: messages.missingAltTextDescription,
status: 'warning',
shouldShow: (context: NagContext) => {
const { hasEmptyAltText } = analyzeImageContent(context.project.body || '')
return hasEmptyAltText
},
link: {
path: 'settings/description',
title: messages.editDescriptionTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
},
},
]

View File

@@ -0,0 +1,4 @@
export * from './core'
export * from './links'
export * from './description'
export * from './tags'

View File

@@ -0,0 +1,48 @@
import { defineMessages } from '@vintl/vintl'
export default defineMessages({
verifyExternalLinksTitle: {
id: 'nags.verify-external-links.title',
defaultMessage: 'Verify external links',
},
verifyExternalLinksDescription: {
id: 'nags.verify-external-links.description',
defaultMessage:
"Some of your external links may be using domains that aren't recognized as common for their link type.",
},
invalidLicenseUrlTitle: {
id: 'nags.invalid-license-url.title',
defaultMessage: 'Invalid license URL',
},
invalidLicenseUrlDescriptionDefault: {
id: 'nags.invalid-license-url.description.default',
defaultMessage: 'License URL is invalid.',
},
invalidLicenseUrlDescriptionDomain: {
id: 'nags.invalid-license-url.description.domain',
defaultMessage:
'Your license URL points to {domain}, which is not appropriate for license information. License URLs should link to the actual license text or legal documentation, not social media, gaming platforms etc.',
},
invalidLicenseUrlDescriptionMalformed: {
id: 'nags.invalid-license-url.description.malformed',
defaultMessage:
'Your license URL appears to be malformed. Please provide a valid URL to your license text.',
},
gplLicenseSourceRequiredTitle: {
id: 'nags.gpl-license-source-required.title',
defaultMessage: 'GPL license requires source',
},
gplLicenseSourceRequiredDescription: {
id: 'nags.gpl-license-source-required.description',
defaultMessage:
'Your {projectType} uses a GPL license which requires source code to be available. Please provide a source code link or consider using a different license.',
},
visitLinksSettingsTitle: {
id: 'nags.visit-links-settings.title',
defaultMessage: 'Visit links settings',
},
editLicenseTitle: {
id: 'nags.edit-license.title',
defaultMessage: 'Edit license',
},
})

View File

@@ -0,0 +1,155 @@
import type { Nag, NagContext } from '../../types/nags'
import { formatProjectType } from '@modrinth/utils'
import { useVIntl } from '@vintl/vintl'
import messages from './links.i18n'
export const commonLinkDomains = {
source: ['github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org', 'git.sr.ht'],
issues: ['github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org'],
discord: ['discord.gg', 'discord.com'],
licenseBlocklist: [
'youtube.com',
'youtu.be',
'modrinth.com',
'curseforge.com',
'twitter.com',
'x.com',
'discord.gg',
'discord.com',
'instagram.com',
'facebook.com',
'tiktok.com',
'reddit.com',
'twitch.tv',
'patreon.com',
'ko-fi.com',
'paypal.com',
'buymeacoffee.com',
],
}
export function isCommonUrl(url: string | undefined, commonDomains: string[]): boolean {
if (!url) return false
try {
const domain = new URL(url).hostname.toLowerCase()
return commonDomains.some((allowed) => domain.includes(allowed))
} catch {
return false
}
}
export function isUncommonLicenseUrl(url: string | undefined, domains: string[]): boolean {
if (!url) return false
try {
const domain = new URL(url).hostname.toLowerCase()
return domains.some((uncommonDomain) => domain.includes(uncommonDomain))
} catch {
return false
}
}
export const linksNags: Nag[] = [
{
id: 'verify-external-links',
title: messages.verifyExternalLinksTitle,
description: messages.verifyExternalLinksDescription,
status: 'warning',
shouldShow: (context: NagContext) => {
return (
!isCommonUrl(context.project.source_url, commonLinkDomains.source) ||
!isCommonUrl(context.project.issues_url, commonLinkDomains.issues) ||
!isCommonUrl(context.project.discord_url, commonLinkDomains.discord)
)
},
link: {
path: 'settings/links',
title: messages.visitLinksSettingsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
},
},
{
id: 'invalid-license-url',
title: messages.invalidLicenseUrlTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
const licenseUrl = context.project.license.url
if (!licenseUrl) {
return formatMessage(messages.invalidLicenseUrlDescriptionDefault)
}
try {
const domain = new URL(licenseUrl).hostname.toLowerCase()
return formatMessage(messages.invalidLicenseUrlDescriptionDomain, { domain })
} catch {
return formatMessage(messages.invalidLicenseUrlDescriptionMalformed)
}
},
status: 'required',
shouldShow: (context: NagContext) => {
const licenseUrl = context.project.license.url
if (!licenseUrl) return false
const isBlocklisted = isUncommonLicenseUrl(licenseUrl, commonLinkDomains.licenseBlocklist)
try {
new URL(licenseUrl)
return isBlocklisted
} catch {
return true
}
},
link: {
path: 'settings',
title: messages.editLicenseTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
},
},
{
id: 'gpl-license-source-required',
title: messages.gplLicenseSourceRequiredTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(messages.gplLicenseSourceRequiredDescription, {
projectType: formatProjectType(context.project.project_type).toLowerCase(),
})
},
status: 'required',
shouldShow: (context: NagContext) => {
const gplLicenses = [
'GPL-2.0',
'GPL-2.0+',
'GPL-2.0-only',
'GPL-2.0-or-later',
'GPL-3.0',
'GPL-3.0+',
'GPL-3.0-only',
'GPL-3.0-or-later',
'LGPL-2.1',
'LGPL-2.1+',
'LGPL-2.1-only',
'LGPL-2.1-or-later',
'LGPL-3.0',
'LGPL-3.0+',
'LGPL-3.0-only',
'LGPL-3.0-or-later',
'AGPL-3.0',
'AGPL-3.0+',
'AGPL-3.0-only',
'AGPL-3.0-or-later',
]
const isGplLicense = gplLicenses.includes(context.project.license.id)
const hasSourceUrl = !!context.project.source_url
return isGplLicense && !hasSourceUrl
},
link: {
path: 'settings/links',
title: messages.visitLinksSettingsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
},
},
]

View File

@@ -0,0 +1,35 @@
import { defineMessages } from '@vintl/vintl'
export default defineMessages({
tooManyTagsTitle: {
id: 'nags.too-many-tags.title',
defaultMessage: 'Too many tags selected',
},
tooManyTagsDescription: {
id: 'nags.too-many-tags.description',
defaultMessage:
"You've selected {tagCount} tags. Consider reducing to 5 or fewer to keep your project focused and easier to discover.",
},
multipleResolutionTagsTitle: {
id: 'nags.multiple-resolution-tags.title',
defaultMessage: 'Multiple resolution tags selected',
},
multipleResolutionTagsDescription: {
id: 'nags.multiple-resolution-tags.description',
defaultMessage:
"You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag that matches their primary resolution.",
},
allTagsSelectedTitle: {
id: 'nags.all-tags-selected.title',
defaultMessage: 'All tags selected',
},
allTagsSelectedDescription: {
id: 'nags.all-tags-selected.description',
defaultMessage:
"You've selected all {totalAvailableTags} available tags. This defeats the purpose of tags, which are meant to help users find relevant projects. Please select only the tags that truly apply to your project.",
},
editTagsTitle: {
id: 'nags.edit-tags.title',
defaultMessage: 'Edit tags',
},
})

View File

@@ -0,0 +1,107 @@
import type { Project } from '@modrinth/utils'
import type { Nag, NagContext } from '../../types/nags'
import { useVIntl } from '@vintl/vintl'
import messages from './tags.i18n'
function getCategories(
project: Project & { actualProjectType: string },
tags: {
categories?: {
project_type: string
}[]
},
) {
return (
tags.categories?.filter(
(category: { project_type: string }) => category.project_type === project.actualProjectType,
) ?? []
)
}
export const tagsNags: Nag[] = [
{
id: 'too-many-tags',
title: messages.tooManyTagsTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
const tagCount =
context.project.categories.length + (context.project.additional_categories?.length || 0)
return formatMessage(messages.tooManyTagsDescription, {
tagCount,
})
},
status: 'warning',
shouldShow: (context: NagContext) => {
const tagCount =
context.project.categories.length + (context.project.additional_categories?.length || 0)
return tagCount > 5
},
link: {
path: 'settings/tags',
title: messages.editTagsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
},
},
{
id: 'multiple-resolution-tags',
title: messages.multipleResolutionTagsTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
const resolutionTags = context.project.categories.filter((tag: string) =>
['16x', '32x', '48x', '64x', '128x', '256x', '512x', '1024x'].includes(tag),
)
return formatMessage(messages.multipleResolutionTagsDescription, {
count: resolutionTags.length,
tags: resolutionTags.join(', '),
})
},
status: 'warning',
shouldShow: (context: NagContext) => {
if (context.project.project_type !== 'resourcepack') return false
const resolutionTags = context.project.categories.filter((tag: string) =>
['16x', '32x', '48x', '64x', '128x', '256x', '512x', '1024x'].includes(tag),
)
return resolutionTags.length > 1
},
link: {
path: 'settings/tags',
title: messages.editTagsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
},
},
{
id: 'all-tags-selected',
title: messages.allTagsSelectedTitle,
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
const categoriesForProjectType = getCategories(
context.project as Project & { actualProjectType: string },
context.tags,
)
const totalAvailableTags = categoriesForProjectType.length
return formatMessage(messages.allTagsSelectedDescription, {
totalAvailableTags,
})
},
status: 'required',
shouldShow: (context: NagContext) => {
const categoriesForProjectType = getCategories(
context.project as Project & { actualProjectType: string },
context.tags,
)
const totalSelectedTags =
context.project.categories.length + (context.project.additional_categories?.length || 0)
return totalSelectedTags === categoriesForProjectType.length
},
link: {
path: 'settings/tags',
title: messages.editTagsTitle,
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
},
},
]

View File

@@ -3,21 +3,53 @@ import type { ButtonAction } from '../../types/actions'
import { TagsIcon } from '@modrinth/assets'
const categories: Stage = {
title: "Are the project's tags/categories accurate?",
title: "Are the project's tags accurate?",
id: 'tags',
icon: TagsIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
navigate: '/settings/tags',
shouldShow: (project) =>
project.categories.length > 0 || project.additional_categories.length > 0,
text: async () => {
return (await import('../messages/checklist-text/categories.md?raw')).default
},
actions: [
{
id: 'categories_inaccurate',
type: 'button',
label: 'Inaccurate',
weight: 10,
weight: 700,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/categories/inaccurate.md?raw')).default,
disablesActions: ['categories_optimization_misused', 'categories_resolutions_misused'],
} as ButtonAction,
{
id: 'categories_optimization_misused',
type: 'button',
label: 'Optimization',
weight: 701,
suggestedStatus: 'flagged',
severity: 'low',
shouldShow: (project) => project.categories.includes('optimization'),
message: async () =>
(await import('../messages/categories/inaccurate.md?raw')).default +
(await import('../messages/categories/optimization_misused.md?raw')).default,
disablesActions: ['categories_inaccurate', 'categories_resolutions_misused'],
} as ButtonAction,
{
id: 'categories_resolutions_misused',
type: 'button',
label: 'Resolutions',
weight: 702,
suggestedStatus: 'flagged',
severity: 'low',
shouldShow: (project) => project.project_type === 'resourcepack',
message: async () =>
(await import('../messages/categories/inaccurate.md?raw')).default +
(await import('../messages/categories/resolutions_misused.md?raw')).default,
disablesActions: ['categories_inaccurate', 'categories_optimization_misused'],
},
],
}

View File

@@ -1,37 +0,0 @@
import type { Stage } from '../../types/stage'
import type { ButtonAction } from '../../types/actions'
import { CopyrightIcon } from '@modrinth/assets'
const copyright: Stage = {
title: 'Does the author have proper permissions to post this project?',
id: 'copyright',
icon: CopyrightIcon,
guidance_url: 'https://modrinth.com/legal/rules',
actions: [
{
id: 'copyright_reupload',
type: 'button',
label: 'Re-upload',
weight: 10,
suggestedStatus: 'rejected',
severity: 'high',
message: async () => (await import('../messages/copyright/reupload.md?raw')).default,
relevantExtraInput: [
{
label: 'What is the title of the original project?',
variable: 'ORIGINAL_PROJECT',
required: true,
suggestions: ['Vanilla Tweaks'],
},
{
label: 'What is the author of the original project?',
variable: 'ORIGINAL_AUTHOR',
required: true,
suggestions: ['Vanilla Tweaks Team'],
},
],
} as ButtonAction,
],
}
export default copyright

View File

@@ -3,7 +3,7 @@ import type { ButtonAction } from '../../types/actions'
import { LibraryIcon } from '@modrinth/assets'
const description: Stage = {
title: "Is the project's description sufficient?",
title: 'Is the description sufficient, accurate, and accessible?',
id: 'description',
icon: LibraryIcon,
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
@@ -13,7 +13,7 @@ const description: Stage = {
id: 'description_insufficient',
type: 'button',
label: 'Insufficient (custom)',
weight: 10,
weight: 400,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/description/insufficient.md?raw')).default,
@@ -30,7 +30,7 @@ const description: Stage = {
id: 'description_insufficient_packs',
type: 'button',
label: 'Insufficient',
weight: 10,
weight: 401,
suggestedStatus: 'flagged',
severity: 'medium',
shouldShow: (project) => project.project_type === 'modpack',
@@ -41,7 +41,7 @@ const description: Stage = {
id: 'description_insufficient_projects',
type: 'button',
label: 'Insufficient',
weight: 10,
weight: 401,
suggestedStatus: 'flagged',
severity: 'medium',
shouldShow: (project) => project.project_type !== 'modpack',
@@ -52,7 +52,7 @@ const description: Stage = {
id: 'description_non_english',
type: 'button',
label: 'Non-english',
weight: 10,
weight: 402,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/description/non-english.md?raw')).default,
@@ -61,23 +61,16 @@ const description: Stage = {
id: 'description_unfinished',
type: 'button',
label: 'Unfinished',
weight: 10,
weight: 403,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/description/unfinished.md?raw')).default,
relevantExtraInput: [
{
label: 'Please specify the reason the description appears unfinished.',
variable: 'REASON',
required: true,
},
],
} as ButtonAction,
{
id: 'description_headers_as_body',
type: 'button',
label: 'Headers as body text',
weight: 10,
weight: 404,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/description/headers-as-body.md?raw')).default,
@@ -86,7 +79,7 @@ const description: Stage = {
id: 'description_image_only',
type: 'button',
label: 'Image-only',
weight: 10,
weight: 405,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/description/image-only.md?raw')).default,
@@ -95,7 +88,7 @@ const description: Stage = {
id: 'description_non_standard_text',
type: 'button',
label: 'Non-standard text',
weight: 10,
weight: 406,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () =>

View File

@@ -13,7 +13,7 @@ const gallery: Stage = {
id: 'gallery_insufficient',
type: 'button',
label: 'Insufficient',
weight: 10,
weight: 900,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/gallery/insufficient.md?raw')).default,
@@ -22,7 +22,7 @@ const gallery: Stage = {
id: 'gallery_not_relevant',
type: 'button',
label: 'Not relevant',
weight: 10,
weight: 901,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/gallery/not-relevant.md?raw')).default,

View File

@@ -0,0 +1,84 @@
import { BookTextIcon } from '@modrinth/assets'
import type { Stage } from '../../types/stage'
const licensesNotRequiringSource: string[] = [
'LicenseRef-All-Rights-Reserved',
'Apache-2.0',
'BSD-2-Clause',
'BSD-3-Clause',
'CC0-1.0',
'CC-BY-4.0',
'CC-BY-SA-4.0',
'CC-BY-NC-4.0',
'CC-BY-NC-SA-4.0',
'CC-BY-ND-4.0',
'CC-BY-NC-ND-4.0',
'ISC',
'MIT',
'Zlib',
]
const licenseStage: Stage = {
title: 'Is this license and link valid?',
text: async () => (await import('../messages/checklist-text/licensing.md?raw')).default,
id: 'license',
icon: BookTextIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
navigate: '/settings/license',
actions: [
{
id: 'license_invalid_link',
type: 'button',
label: 'Invalid Link',
weight: 600,
suggestedStatus: 'flagged',
severity: 'medium',
shouldShow: (project) => Boolean(project.license.url),
message: async () => (await import('../messages/license/invalid_link.md?raw')).default,
enablesActions: [
{
id: 'license_invalid_link-custom_license',
type: 'toggle',
label: 'Invalid Link: Custom License',
weight: 601,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () =>
(await import('../messages/license/invalid_link-custom_license.md?raw')).default,
},
],
},
{
id: 'license_no_source',
type: 'conditional-button',
label: 'No Source',
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 () => '',
enablesActions: [
{
id: 'license_no_source-fork',
type: 'toggle',
label: 'No Source: Fork',
weight: 602,
suggestedStatus: 'rejected',
severity: 'high',
message: async () => (await import('../messages/license/no_source-fork.md?raw')).default,
},
],
},
],
}
export default licenseStage

View File

@@ -3,43 +3,82 @@ import type { ButtonAction } from '../../types/actions'
import { LinkIcon } from '@modrinth/assets'
const links: Stage = {
title: "Are the project's links accessible and not misleading?",
title: "Are the project's links accurate and accessible?",
id: 'links',
icon: LinkIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
guidance_url: 'https://modrinth.com/legal/rules',
navigate: '/settings/links',
shouldShow: (project) =>
Boolean(
project.issues_url ||
project.source_url ||
project.wiki_url ||
project.discord_url ||
project.donation_urls.length > 0,
),
text: async (project) => {
let text = (await import('../messages/checklist-text/links/base.md?raw')).default
if (project.donation_urls.length > 0) {
text += (await import('../messages/checklist-text/links/donation/donations.md?raw')).default
for (const donation of project.donation_urls) {
text += (await import(`../messages/checklist-text/links/donation/donation.md?raw`)).default
.replace('{URL}', donation.url)
.replace('{PLATFORM}', donation.platform)
}
}
return text
},
actions: [
{
id: 'links_misused',
type: 'button',
label: 'Links are misused',
weight: 10,
weight: 500,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/links/misused.md?raw')).default,
} as ButtonAction,
{
id: 'links_not_accessible_source',
type: 'button',
label: 'Not accessible (source)',
weight: 10,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/links/not-accessible-source.md?raw')).default,
} as ButtonAction,
{
id: 'links_not_accessible_other',
type: 'button',
label: 'Not accessible (other)',
weight: 10,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/links/not-accessible-other.md?raw')).default,
relevantExtraInput: [
{
label: 'Please specify the link type that is inaccessible.',
variable: 'LINK',
required: true,
label: 'What links are misused?',
variable: 'MISUSED_LINKS',
required: false,
},
],
} as ButtonAction,
{
id: 'links_unaccessible',
type: 'button',
label: 'Links are inaccessible',
weight: 510,
suggestedStatus: 'flagged',
// Theoretically a conditional could go here to prevent overlap of misuse and unaccessible messages repeating while still allowing for a multi-select in each.
// if links_misused was selected, send nothing.
message: async () => (await import('../messages/links/not_accessible.md?raw')).default,
enablesActions: [
{
id: 'links_unaccessible_options',
type: 'multi-select-chips',
label: 'Warn of inaccessible link?',
shouldShow: (project) => Boolean(project.source_url || project.discord_url),
options: [
{
label: 'Source',
weight: 511,
shouldShow: (project) => Boolean(project.source_url),
message: async () =>
(await import('../messages/links/not_accessible-source.md?raw')).default,
},
{
label: 'Discord',
weight: 512,
shouldShow: (project) => Boolean(project.discord_url),
message: async () =>
(await import('../messages/links/not_accessible-discord.md?raw')).default,
},
],
},
],
} as ButtonAction,

View File

@@ -0,0 +1,111 @@
import type { Stage } from '../../types/stage'
import type { ButtonAction } from '../../types/actions'
import { CopyrightIcon } from '@modrinth/assets'
const reupload: Stage = {
title: 'Does the author have proper permissions to post this project?',
id: 'reupload',
icon: CopyrightIcon,
guidance_url: 'https://modrinth.com/legal/rules',
actions: [
{
id: 'reupload_reupload',
type: 'button',
label: 'Re-upload',
weight: 1100,
suggestedStatus: 'rejected',
severity: 'high',
message: async () => (await import('../messages/reupload/reupload.md?raw')).default,
disablesActions: [
'reupload_unclear_fork',
'reupload_insufficient_fork',
'reupload_request_proof',
'reupload_identity_verification',
],
relevantExtraInput: [
{
label: 'What is the title of the original project?',
variable: 'ORIGINAL_PROJECT',
required: true,
suggestions: ['Vanilla Tweaks'],
},
{
label: 'What is the author of the original project?',
variable: 'ORIGINAL_AUTHOR',
required: true,
suggestions: ['Vanilla Tweaks Team'],
},
],
} as ButtonAction,
{
id: 'reupload_unclear_fork',
type: 'button',
label: 'Unclear Fork',
weight: 1100,
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',
],
} as ButtonAction,
{
id: 'reupload_insufficient_fork',
type: 'button',
label: 'Insufficient Fork',
weight: 1100,
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',
],
} as ButtonAction,
{
id: 'reupload_request_proof',
type: 'button',
label: 'Proof of permissions',
weight: 1100,
suggestedStatus: 'rejected',
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',
],
},
],
}
export default reupload

View File

@@ -3,17 +3,18 @@ import type { ButtonAction } from '../../types/actions'
import { ListBulletedIcon } from '@modrinth/assets'
const ruleFollowing: Stage = {
title: 'Does this project break our content rules?',
title: 'Does this project violate the rules?',
id: 'rule-following',
icon: ListBulletedIcon,
guidance_url: 'https://modrinth.com/legal/rules',
navigate: '/',
guidance_url:
'https://www.notion.so/Creator-Communication-Guide-1b65ee711bf080ec9337e3ccdded146c',
navigate: '/moderation',
actions: [
{
id: 'rule_breaking_yes',
type: 'button',
label: 'Yes',
weight: 10,
weight: 0,
suggestedStatus: 'rejected',
severity: 'critical',
message: async () => (await import('../messages/rule-breaking.md?raw')).default,

View File

@@ -12,8 +12,8 @@ const sideTypes: Stage = {
{
id: 'side_types_inaccurate_modpack',
type: 'button',
label: 'Inaccurate (modpack)',
weight: 10,
label: 'Inaccurate',
weight: 800,
suggestedStatus: 'flagged',
severity: 'low',
shouldShow: (project) => project.project_type === 'modpack',
@@ -23,8 +23,8 @@ const sideTypes: Stage = {
{
id: 'side_types_inaccurate_mod',
type: 'button',
label: 'Inaccurate (mod)',
weight: 10,
label: 'Inaccurate',
weight: 800,
suggestedStatus: 'flagged',
severity: 'low',
shouldShow: (project) => project.project_type === 'mod',

View File

@@ -1,23 +0,0 @@
import { HashIcon } from '@modrinth/assets'
import type { Stage } from '../../types/stage'
const slugStage: Stage = {
title: 'Is the slug accurate and appropriate?',
id: 'slug',
icon: HashIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
navigate: '/settings',
actions: [
{
id: 'slug_misused',
type: 'button',
label: 'Misused',
weight: 100,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/slug/misused.md?raw')).default,
},
],
}
export default slugStage

View File

@@ -0,0 +1,88 @@
import type { Stage } from '../../types/stage'
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
import { TriangleAlertIcon } from '@modrinth/assets'
const statusAlerts: Stage = {
title: `Is anything else affecting this project's status?`,
id: 'status-alerts',
icon: TriangleAlertIcon,
text: async () => (await import('../messages/checklist-text/status-alerts/text.md?raw')).default,
guidance_url:
'https://www.notion.so/Project-Modification-Guidelines-22e5ee711bf080628416f0471ba6af02',
navigate: '/moderation',
actions: [
{
id: 'status_corrections_applied',
type: 'button',
label: 'Corrections applied',
weight: 999999,
suggestedStatus: 'approved',
disablesActions: ['status_private_use', 'status_account_issues'],
message: async () => (await import('../messages/status-alerts/fixed.md?raw')).default,
} as ButtonAction,
{
id: 'status_private_use',
type: 'button',
label: 'Private use',
weight: 999999,
suggestedStatus: 'flagged',
disablesActions: ['status_corrections_applied', 'status_account_issues'],
message: async () => (await import('../messages/status-alerts/private.md?raw')).default,
} as ButtonAction,
{
id: 'status_account_issues',
type: 'button',
label: 'Account issues',
weight: 999999,
suggestedStatus: 'rejected',
disablesActions: ['status_corrections_applied', 'status_private_use'],
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',
label: `Automod confusion`,
weight: 999999,
message: async () =>
(await import('../messages/status-alerts/automod_confusion.md?raw')).default,
} as ButtonAction,
],
}
export default statusAlerts

View File

@@ -4,6 +4,7 @@ import { AlignLeftIcon } from '@modrinth/assets'
const summary: Stage = {
title: "Is the project's summary sufficient?",
text: async () => (await import('../messages/checklist-text/summary/summary.md?raw')).default,
id: 'summary',
icon: AlignLeftIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
@@ -12,25 +13,27 @@ const summary: Stage = {
id: 'summary_insufficient',
type: 'button',
label: 'Insufficient',
weight: 10,
weight: 300,
suggestedStatus: 'flagged',
severity: 'low',
disablesActions: ['summary_repeat_title'],
message: async () => (await import('../messages/summary/insufficient.md?raw')).default,
} as ButtonAction,
{
id: 'summary_repeat_title',
type: 'button',
label: 'Repeat of title',
weight: 10,
weight: 300,
suggestedStatus: 'flagged',
severity: 'low',
disablesActions: ['summary_insufficient'],
message: async () => (await import('../messages/summary/repeat-title.md?raw')).default,
} as ButtonAction,
{
id: 'summary_formatting',
type: 'button',
label: 'Formatting',
weight: 10,
weight: 301,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/summary/formatting.md?raw')).default,
@@ -39,7 +42,7 @@ const summary: Stage = {
id: 'summary_non_english',
type: 'button',
label: 'Non-english',
weight: 10,
weight: 302,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/summary/non-english.md?raw')).default,

View File

@@ -0,0 +1,77 @@
import { BookOpenIcon } from '@modrinth/assets'
import type { Stage } from '../../types/stage'
const titleSlug: Stage = {
title: 'Are the Name and URL accurate and appropriate?',
id: 'title-&-slug',
text: async () => (await import('../messages/checklist-text/title-slug.md?raw')).default,
icon: BookOpenIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
actions: [
{
id: 'title_useless_info',
type: 'button',
label: 'Contains useless info',
weight: 100,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/title/useless-info.md?raw')).default,
},
{
id: 'title_minecraft_branding',
type: 'button',
label: 'Minecraft title',
weight: 100,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/title/minecraft-branding.md?raw')).default,
},
{
id: 'title_similarities',
type: 'button',
label: 'Title similarities',
weight: 110,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/title/similarities.md?raw')).default,
enablesActions: [
{
id: 'title_similarities_options',
type: 'multi-select-chips',
label: 'Similarities additional info',
options: [
{
label: 'Modpack named after mod',
weight: 111,
shouldShow: (project) => project.project_type === 'modpack',
message: async () =>
(await import('../messages/title/similarities-modpack.md?raw')).default,
},
{
label: 'Forked project',
weight: 112,
message: async () =>
(await import('../messages/title/similarities-fork.md?raw')).default,
},
],
},
],
},
{
id: 'slug_misused_options',
type: 'multi-select-chips',
label: 'Slug issues?',
suggestedStatus: 'rejected',
severity: 'low',
options: [
{
label: 'Misused',
weight: 200,
message: async () => (await import('../messages/slug/misused.md?raw')).default,
},
],
},
],
}
export default titleSlug

View File

@@ -1,41 +0,0 @@
import { BookOpenIcon } from '@modrinth/assets'
import type { Stage } from '../../types/stage'
const titleStage: Stage = {
title: 'Is this title free of useless information?',
text: async () => '**Title:** `%PROJECT_TITLE%`',
id: 'title',
icon: BookOpenIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
actions: [
{
id: 'title_useless_info',
type: 'button',
label: 'Contains useless info',
weight: 100,
suggestedStatus: 'flagged',
severity: 'low',
message: async () => (await import('../messages/title/useless-info.md?raw')).default,
},
{
id: 'title_minecraft_branding',
type: 'button',
label: 'Minecraft title',
weight: 100,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/title/minecraft-branding.md?raw')).default,
},
{
id: 'title_similarities',
type: 'button',
label: 'Title similarities',
weight: 100,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () => (await import('../messages/title/similarities.md?raw')).default,
},
],
}
export default titleStage

View File

@@ -0,0 +1,24 @@
import { XIcon } from '@modrinth/assets'
import type { Stage } from '../../types/stage'
const undefinedProjectStage: Stage = {
title: 'This project is undefined!',
id: 'undefined-project',
icon: XIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
navigate: '/versions',
shouldShow: (project) => project.versions.length === 0,
actions: [
{
id: 'undefined_no_versions',
type: 'button',
label: 'No Versions',
weight: -100,
suggestedStatus: 'rejected',
message: async () =>
(await import('../messages/undefined-project/no_versions.md?raw')).default,
},
],
}
export default undefinedProjectStage

View File

@@ -1,9 +1,9 @@
import type { Stage } from '../../types/stage'
import type { ButtonAction } from '../../types/actions'
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
import { VersionIcon } from '@modrinth/assets'
const versions: Stage = {
title: "Are these project's files correct?",
title: "Are this project's files correct?",
id: 'versions',
icon: VersionIcon,
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
@@ -13,33 +13,144 @@ const versions: Stage = {
id: 'versions_incorrect_additional',
type: 'button',
label: 'Incorrect additional files',
weight: 10,
weight: 1000,
suggestedStatus: 'flagged',
severity: 'medium',
message: async () =>
(await import('../messages/versions/incorrect-additional-files.md?raw')).default,
(await import('../messages/versions/incorrect_additional_files.md?raw')).default,
} as ButtonAction,
{
id: 'versions_invalid_modpacks',
id: 'versions_incorrect_project_type',
type: 'button',
label: 'Invalid file type (modpacks)',
weight: 10,
label: 'Incorrect Project Type',
suggestedStatus: 'rejected',
severity: 'medium',
shouldShow: (project) => project.project_type === 'modpack',
message: async () => (await import('../messages/versions/invalid-modpacks.md?raw')).default,
weight: -999999,
message: async () => '',
enablesActions: [
{
id: 'versions_incorrect_project_type_options',
type: 'dropdown',
label: 'What type should this project be?',
options: [
{
label: 'Modpack',
weight: 1001,
shouldShow: (project) => project.project_type !== 'modpack',
message: async () =>
(await import('../messages/versions/invalid-modpacks.md?raw')).default,
} as DropdownActionOption,
{
label: 'Resource Pack',
weight: 1001,
shouldShow: (project) => project.project_type !== 'resourcepack',
message: async () =>
(await import('../messages/versions/invalid-resourcepacks.md?raw')).default,
} as DropdownActionOption,
{
label: 'Data Pack',
weight: 1001,
shouldShow: (project) => !project.loaders.includes('datapack'),
message: async () =>
(await import('../messages/versions/invalid-datapacks.md?raw')).default,
} as DropdownActionOption,
],
} as DropdownAction,
],
} as ButtonAction,
{
id: 'versions_invalid_resourcepacks',
id: 'versions_alternate_versions',
type: 'button',
label: 'Invalid file type (resourcepacks)',
weight: 10,
suggestedStatus: 'rejected',
label: 'Alternate Versions',
suggestedStatus: 'flagged',
severity: 'medium',
weight: -999999,
message: async () => '',
enablesActions: [
{
id: 'versions_incorrect_project_type_options',
type: 'dropdown',
label: 'How are the alternate versions distributed?',
options: [
{
label: 'Primary Files',
weight: 1002,
message: async () =>
(await import('../messages/versions/alternate_versions-primary.md?raw')).default,
} as DropdownActionOption,
{
label: 'Additional Files',
weight: 1002,
message: async () =>
(await import('../messages/versions/alternate_versions-additional.md?raw')).default,
} as DropdownActionOption,
{
label: 'Monofile',
weight: 1002,
shouldShow: (project) =>
project.project_type === 'resourcepack' || project.loaders.includes('datapack'),
message: async () =>
(await import('../messages/versions/alternate_versions-mono.md?raw')).default,
} as DropdownActionOption,
{
label: 'Server Files (Primary Files)',
weight: 1002,
shouldShow: (project) => project.project_type === 'modpack',
message: async () =>
(await import('../messages/versions/alternate_versions-server.md?raw')).default,
} as DropdownActionOption,
{
label: 'Server Files (Additional Files)',
weight: 1002,
suggestedStatus: 'rejected',
severity: 'high',
shouldShow: (project) => project.project_type === 'modpack',
message: async () =>
(await import('../messages/versions/alternate_versions-server-additional.md?raw'))
.default,
} as DropdownActionOption,
{
label: 'mods.zip',
weight: 1002,
suggestedStatus: 'rejected',
severity: 'high',
shouldShow: (project) => project.project_type === 'modpack',
message: async () =>
(await import('../messages/versions/alternate_versions-zip.md?raw')).default,
} as DropdownActionOption,
],
} as DropdownAction,
],
} as ButtonAction,
{
id: 'versions_vanilla_assets',
type: 'button',
label: 'Vanilla Assets',
suggestedStatus: `rejected`,
severity: `medium`,
weight: 1003,
shouldShow: (project) => project.project_type === 'resourcepack',
message: async () =>
(await import('../messages/versions/invalid-resourcepacks.md?raw')).default,
message: async () => (await import('../messages/versions/vanilla_assets.md?raw')).default,
} as ButtonAction,
{
id: 'versions_redist_libs',
type: 'button',
label: 'Oversized File',
suggestedStatus: `rejected`,
severity: `medium`,
weight: 1003,
shouldShow: (project) => project.project_type === 'mod',
message: async () => (await import('../messages/versions/redist_libs.md?raw')).default,
} as ButtonAction,
{
id: 'versions_duplicate_primary_files',
type: 'button',
label: 'Duplicate Primary Files',
suggestedStatus: 'flagged',
severity: `medium`,
weight: 1004,
message: async () => (await import('../messages/versions/broken_version.md?raw')).default,
},
],
}

Some files were not shown because too many files have changed in this diff Show More