You've already forked AstralRinth
forked from didirus/AstralRinth
Refactor folder structure
This commit is contained in:
10
src/plugins/generator/index.d.ts
vendored
Normal file
10
src/plugins/generator/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export default function Generator(options: PluginOptions): {
|
||||
name: string
|
||||
buildStart(): Promise<void>
|
||||
}
|
||||
export interface PluginOptions {
|
||||
projectColors: boolean
|
||||
landingPage: boolean
|
||||
gameVersions: boolean
|
||||
tags: boolean
|
||||
}
|
||||
48
src/plugins/generator/index.js
Normal file
48
src/plugins/generator/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import { landingPage } from './outputs/landingPage.js'
|
||||
import { projectColors } from './outputs/projectColors.js'
|
||||
import { gameVersions } from './outputs/gameVersions.js'
|
||||
import { tags } from './outputs/tags.js'
|
||||
|
||||
const API_URL =
|
||||
process.env.VITE_API_URL && process.env.VITE_API_URL === 'https://staging-api.modrinth.com/v2/'
|
||||
? 'https://staging-api.modrinth.com/v2/'
|
||||
: 'https://api.modrinth.com/v2/'
|
||||
|
||||
// Time to live: 7 days
|
||||
const TTL = 7 * 24 * 60 * 60 * 1000
|
||||
|
||||
export default function Generator(options) {
|
||||
return {
|
||||
name: 'rollup-plugin-omorphia-generator',
|
||||
async buildStart() {
|
||||
let state = {}
|
||||
try {
|
||||
state = JSON.parse(await fs.readFile('./generated/state.json', 'utf8'))
|
||||
} catch {
|
||||
// File doesn't exist, create folder
|
||||
await fs.mkdir('./generated', { recursive: true })
|
||||
}
|
||||
|
||||
// Don't generate if the last generation was less than TTL and the options are the same
|
||||
if (
|
||||
state?.lastGenerated &&
|
||||
new Date(state.lastGenerated).getTime() + TTL > new Date().getTime() &&
|
||||
JSON.stringify(state.options) === JSON.stringify(options)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Write new state
|
||||
state.lastGenerated = new Date().toISOString()
|
||||
state.options = options
|
||||
|
||||
await fs.writeFile('./generated/state.json', JSON.stringify(state, null, 2))
|
||||
|
||||
if (options.tags) await tags(API_URL)
|
||||
if (options.landingPage) await landingPage(API_URL)
|
||||
if (options.gameVersions) await gameVersions(API_URL)
|
||||
if (options.projectColors) await projectColors(API_URL)
|
||||
},
|
||||
}
|
||||
}
|
||||
1
src/plugins/generator/outputs/gameVersions.d.ts
vendored
Normal file
1
src/plugins/generator/outputs/gameVersions.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function gameVersions(API_URL: string): Promise<void>
|
||||
22
src/plugins/generator/outputs/gameVersions.js
Normal file
22
src/plugins/generator/outputs/gameVersions.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { fetch } from 'undici'
|
||||
import { promises as fs } from 'fs'
|
||||
import cliProgress from 'cli-progress'
|
||||
|
||||
export async function gameVersions(API_URL) {
|
||||
const progressBar = new cliProgress.SingleBar({
|
||||
format: 'Generating game versions | {bar} | {percentage}%',
|
||||
barCompleteChar: '\u2588',
|
||||
barIncompleteChar: '\u2591',
|
||||
hideCursor: true,
|
||||
})
|
||||
progressBar.start(2, 0)
|
||||
|
||||
const gameVersions = await (await fetch(API_URL + 'tag/game_version')).json()
|
||||
progressBar.increment()
|
||||
|
||||
// Write JSON file
|
||||
await fs.writeFile('./generated/gameVersions.json', JSON.stringify(gameVersions))
|
||||
progressBar.increment()
|
||||
|
||||
progressBar.stop()
|
||||
}
|
||||
1
src/plugins/generator/outputs/landingPage.d.ts
vendored
Normal file
1
src/plugins/generator/outputs/landingPage.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function landingPage(API_URL: string): Promise<void>
|
||||
40
src/plugins/generator/outputs/landingPage.js
Normal file
40
src/plugins/generator/outputs/landingPage.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { fetch } from 'undici'
|
||||
import { promises as fs } from 'fs'
|
||||
import cliProgress from 'cli-progress'
|
||||
|
||||
export async function landingPage(API_URL) {
|
||||
const progressBar = new cliProgress.SingleBar({
|
||||
format: 'Generating landing page | {bar} | {percentage}% || {value}/{total} mods',
|
||||
barCompleteChar: '\u2588',
|
||||
barIncompleteChar: '\u2591',
|
||||
hideCursor: true,
|
||||
})
|
||||
progressBar.start(100, 0)
|
||||
|
||||
// Fetch top 100 mods
|
||||
const response = await (
|
||||
await fetch(API_URL + 'search?limit=100&facets=[["project_type:mod"]]')
|
||||
).json()
|
||||
|
||||
// Simplified array with the format: ['id', 'slug', 'icon_extension']
|
||||
const compressed = response.hits
|
||||
.filter((project) => project.icon_url)
|
||||
.map((project) => {
|
||||
progressBar.increment()
|
||||
return [
|
||||
project.project_id,
|
||||
project.slug || '',
|
||||
project.icon_url.match(/\.[0-9a-z]+$/i)[0].substring(1),
|
||||
]
|
||||
})
|
||||
|
||||
// Write JSON file
|
||||
await fs.writeFile(
|
||||
'./generated/landingPage.json',
|
||||
JSON.stringify({
|
||||
mods: compressed,
|
||||
random: Math.random(),
|
||||
})
|
||||
)
|
||||
progressBar.stop()
|
||||
}
|
||||
1
src/plugins/generator/outputs/projectColors.d.ts
vendored
Normal file
1
src/plugins/generator/outputs/projectColors.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function projectColors(API_URL: string): Promise<void>
|
||||
86
src/plugins/generator/outputs/projectColors.js
Normal file
86
src/plugins/generator/outputs/projectColors.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { fetch } from 'undici'
|
||||
import { createWriteStream } from 'fs'
|
||||
import cliProgress from 'cli-progress'
|
||||
import Jimp from 'jimp'
|
||||
import { getAverageColor } from 'fast-average-color-node'
|
||||
|
||||
// Note: This function has issues and will occasionally fail with some project icons. It averages at a 99.4% success rate. Most issues are from ECONNRESET errors & Jimp not being able to handle webp & svg images.
|
||||
export async function projectColors(API_URL) {
|
||||
const progressBar = new cliProgress.SingleBar({
|
||||
format: 'Generating project colors | {bar} | {percentage}% || {value}/{total} projects',
|
||||
barCompleteChar: '\u2588',
|
||||
barIncompleteChar: '\u2591',
|
||||
hideCursor: true,
|
||||
})
|
||||
// Get total number of projects
|
||||
const projectCount = (await (await fetch(API_URL + 'search?limit=0')).json()).total_hits
|
||||
progressBar.start(projectCount, 0)
|
||||
const writeStream = createWriteStream('./generated/projects.json')
|
||||
writeStream.write('{')
|
||||
// Used to form the JSON string (so that the first doesn't have a comma prefix)
|
||||
let first = true
|
||||
let completed = 0
|
||||
// Number of pages through search to fetch
|
||||
const requestCount = Math.ceil(projectCount / 100)
|
||||
await Promise.allSettled(
|
||||
Array.from({ length: requestCount }, async (_, index) => {
|
||||
const response = await fetch(API_URL + `search?limit=100&offset=${index * 100}`)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch projects: ${response.statusText}`)
|
||||
}
|
||||
// Get project hits & use map to get rid of extra data
|
||||
const hits = (await response.json()).hits.map((project) => ({
|
||||
project_id: project.project_id,
|
||||
slug: project.slug,
|
||||
title: project.title,
|
||||
icon_url: project.icon_url,
|
||||
}))
|
||||
// Try parsing the icon of each project
|
||||
await Promise.allSettled(
|
||||
hits.map(async (project) => {
|
||||
if (
|
||||
project.icon_url &&
|
||||
// Jimp doesn't support webp or svg
|
||||
!project.icon_url.endsWith('.webp') &&
|
||||
!project.icon_url.endsWith('.svg')
|
||||
) {
|
||||
try {
|
||||
const image = await Jimp.read(
|
||||
project.icon_url.replace('cdn', 'cdn-raw') // Skip redirect to raw CDN
|
||||
)
|
||||
// Resize image before getting average color (faster)
|
||||
image.resize(256, 256)
|
||||
// Get bottom edge of image
|
||||
const edge = image.clone().crop(0, 255, 256, 1)
|
||||
const buffer = await edge.getBufferAsync(Jimp.AUTO)
|
||||
let color = (await getAverageColor(buffer)).hexa
|
||||
// If the edge is transparent, use the average color of the entire image
|
||||
if (color === '#00000000') {
|
||||
const buffer = await image.getBufferAsync(Jimp.AUTO)
|
||||
color = (await getAverageColor(buffer)).hexa
|
||||
}
|
||||
// Remove color transparency
|
||||
color = color.replace(/.{2}$/, '')
|
||||
// Only use comma prefix if not first
|
||||
let prefix = ','
|
||||
if (first) {
|
||||
prefix = ''
|
||||
first = false
|
||||
}
|
||||
writeStream.write(`${prefix}"${project.project_id}":"${color}"`)
|
||||
completed++
|
||||
} catch (error) {
|
||||
// Ignore errors
|
||||
// console.log(error);
|
||||
}
|
||||
}
|
||||
progressBar.increment()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
writeStream.write('}')
|
||||
writeStream.end()
|
||||
progressBar.stop()
|
||||
console.log(`Failed to parse ${projectCount - completed} project icons.`)
|
||||
}
|
||||
1
src/plugins/generator/outputs/tags.d.ts
vendored
Normal file
1
src/plugins/generator/outputs/tags.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function tags(API_URL: string): Promise<void>
|
||||
59
src/plugins/generator/outputs/tags.js
Normal file
59
src/plugins/generator/outputs/tags.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { fetch } from 'undici'
|
||||
import { promises as fs } from 'fs'
|
||||
import cliProgress from 'cli-progress'
|
||||
export async function tags(API_URL) {
|
||||
const progressBar = new cliProgress.SingleBar({
|
||||
format: 'Generating tags | {bar} | {percentage}%',
|
||||
barCompleteChar: '\u2588',
|
||||
barIncompleteChar: '\u2591',
|
||||
hideCursor: true,
|
||||
})
|
||||
progressBar.start(7, 0)
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [categories, loaders, licenses, donationPlatforms, reportTypes] = await Promise.all([
|
||||
await (await fetch(API_URL + 'tag/category')).json(),
|
||||
await (await fetch(API_URL + 'tag/loader')).json(),
|
||||
await (await fetch(API_URL + 'tag/license')).json(),
|
||||
await (await fetch(API_URL + 'tag/donation_platform')).json(),
|
||||
await (await fetch(API_URL + 'tag/report_type')).json(),
|
||||
])
|
||||
progressBar.update(5)
|
||||
|
||||
const tagIconReducer = (a, v) => ({
|
||||
...a,
|
||||
[v.name]: v.icon.replace('<svg', '<svg class="icon"'),
|
||||
})
|
||||
|
||||
// Create single object with icons
|
||||
const tagIcons = {
|
||||
...categories.reduce(tagIconReducer, {}),
|
||||
...loaders.reduce(tagIconReducer, {}),
|
||||
}
|
||||
progressBar.increment()
|
||||
|
||||
// Delete icons from original arrays
|
||||
categories = categories.map(({ icon, ...rest }) => rest)
|
||||
loaders = loaders.map(({ icon, ...rest }) => rest)
|
||||
progressBar.increment()
|
||||
|
||||
// Set project types
|
||||
const projectTypes = ['mod', 'modpack']
|
||||
|
||||
// Write JSON file
|
||||
await fs.writeFile(
|
||||
'./generated/tags.json',
|
||||
JSON.stringify({
|
||||
categories,
|
||||
loaders,
|
||||
projectTypes,
|
||||
licenses,
|
||||
donationPlatforms,
|
||||
reportTypes,
|
||||
tagIcons,
|
||||
})
|
||||
)
|
||||
progressBar.increment()
|
||||
|
||||
progressBar.stop()
|
||||
}
|
||||
Reference in New Issue
Block a user