You've already forked AstralRinth
forked from didirus/AstralRinth
devex: add icon cmd (#4958)
* feat: icons add cmd * fix: dep * Update packages/assets/build/add-icons.ts Signed-off-by: Calum H. <hendersoncal117@gmail.com> * fix: lint --------- Signed-off-by: Calum H. <hendersoncal117@gmail.com> Signed-off-by: Calum H. <contact@cal.engineer>
This commit is contained in:
@@ -20,7 +20,8 @@
|
||||
"prepr:frontend": "turbo run prepr --filter=@modrinth/frontend --filter=@modrinth/app-frontend",
|
||||
"prepr:frontend:lib": "turbo run prepr --filter=@modrinth/ui --filter=@modrinth/moderation --filter=@modrinth/assets --filter=@modrinth/blog --filter=@modrinth/api-client --filter=@modrinth/utils --filter=@modrinth/tooling-config",
|
||||
"prepr:frontend:web": "turbo run prepr --filter=@modrinth/frontend",
|
||||
"prepr:frontend:app": "turbo run prepr --filter=@modrinth/app-frontend"
|
||||
"prepr:frontend:app": "turbo run prepr --filter=@modrinth/app-frontend",
|
||||
"icons:add": "pnpm --filter @modrinth/assets icons:add"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modrinth/tooling-config": "workspace:*",
|
||||
|
||||
213
packages/assets/build/add-icons.ts
Normal file
213
packages/assets/build/add-icons.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import readline from 'node:readline'
|
||||
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const iconsDir = path.join(packageRoot, 'icons')
|
||||
const lucideIconsDir = path.join(packageRoot, 'node_modules/lucide-static/icons')
|
||||
|
||||
function listAvailableIcons(): string[] {
|
||||
if (!fs.existsSync(lucideIconsDir)) {
|
||||
return []
|
||||
}
|
||||
return fs
|
||||
.readdirSync(lucideIconsDir)
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.map((file) => path.basename(file, '.svg'))
|
||||
.sort()
|
||||
}
|
||||
|
||||
function paginateList(allIcons: string[], pageSize = 20): void {
|
||||
let page = 0
|
||||
let search = ''
|
||||
let filteredIcons = allIcons
|
||||
|
||||
const getFilteredIcons = (): string[] => {
|
||||
if (!search) return allIcons
|
||||
return allIcons.filter((icon) => icon.includes(search))
|
||||
}
|
||||
|
||||
const renderPage = (): void => {
|
||||
console.clear()
|
||||
filteredIcons = getFilteredIcons()
|
||||
const totalPages = Math.max(1, Math.ceil(filteredIcons.length / pageSize))
|
||||
|
||||
if (page >= totalPages) page = Math.max(0, totalPages - 1)
|
||||
|
||||
const start = page * pageSize
|
||||
const end = Math.min(start + pageSize, filteredIcons.length)
|
||||
const pageIcons = filteredIcons.slice(start, end)
|
||||
|
||||
console.log(`\x1b[1mAvailable Lucide Icons\x1b[0m`)
|
||||
console.log(`\x1b[2mSearch: \x1b[0m${search || '\x1b[2m(type to search)\x1b[0m'}\n`)
|
||||
|
||||
if (pageIcons.length === 0) {
|
||||
console.log(` \x1b[2mNo icons found matching "${search}"\x1b[0m`)
|
||||
} else {
|
||||
pageIcons.forEach((icon) => {
|
||||
if (search) {
|
||||
const highlighted = icon.replace(search, `\x1b[33m${search}\x1b[0m`)
|
||||
console.log(` ${highlighted}`)
|
||||
} else {
|
||||
console.log(` ${icon}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n\x1b[2m${filteredIcons.length}/${allIcons.length} icons | Page ${page + 1}/${totalPages} | ← → navigate | :q quit\x1b[0m`,
|
||||
)
|
||||
}
|
||||
|
||||
renderPage()
|
||||
|
||||
readline.emitKeypressEvents(process.stdin)
|
||||
if (process.stdin.isTTY) {
|
||||
process.stdin.setRawMode(true)
|
||||
}
|
||||
|
||||
process.stdin.on('keypress', (str, key) => {
|
||||
if (key.ctrl && key.name === 'c') {
|
||||
console.clear()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// :q to quit
|
||||
if (search === ':' && key.name === 'q') {
|
||||
console.clear()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Navigation
|
||||
if (key.name === 'right') {
|
||||
const totalPages = Math.max(1, Math.ceil(filteredIcons.length / pageSize))
|
||||
if (page < totalPages - 1) {
|
||||
page++
|
||||
renderPage()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (key.name === 'left') {
|
||||
if (page > 0) {
|
||||
page--
|
||||
renderPage()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Backspace
|
||||
if (key.name === 'backspace') {
|
||||
search = search.slice(0, -1)
|
||||
page = 0
|
||||
renderPage()
|
||||
return
|
||||
}
|
||||
|
||||
// Escape to clear search
|
||||
if (key.name === 'escape') {
|
||||
search = ''
|
||||
page = 0
|
||||
renderPage()
|
||||
return
|
||||
}
|
||||
|
||||
// Type to search
|
||||
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
||||
search += str
|
||||
page = 0
|
||||
renderPage()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addIcon(iconId: string, overwrite: boolean): boolean {
|
||||
const sourcePath = path.join(lucideIconsDir, `${iconId}.svg`)
|
||||
const targetPath = path.join(iconsDir, `${iconId}.svg`)
|
||||
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
console.error(`❌ Icon "${iconId}" not found in lucide-static`)
|
||||
console.error(` Run with --list to see available icons`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (fs.existsSync(targetPath) && !overwrite) {
|
||||
console.log(`⏭️ Skipping "${iconId}" (already exists, use --overwrite to replace)`)
|
||||
return false
|
||||
}
|
||||
|
||||
fs.copyFileSync(sourcePath, targetPath)
|
||||
console.log(`✅ Added "${iconId}"`)
|
||||
return true
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`
|
||||
Usage: pnpm icons:add [options] <icon_id> [icon_id...]
|
||||
|
||||
Options:
|
||||
--list, -l Browse all available Lucide icons (interactive)
|
||||
--overwrite, -o Overwrite existing icons
|
||||
--help, -h Show this help message
|
||||
|
||||
Examples:
|
||||
pnpm icons:add heart star settings-2
|
||||
pnpm icons:add --overwrite heart
|
||||
pnpm icons:add --list # Interactive browser
|
||||
pnpm icons:add --list | grep arrow # Pipe to grep
|
||||
|
||||
Interactive controls:
|
||||
Type Search icons
|
||||
← → Navigate pages
|
||||
Escape Clear search
|
||||
:q Quit
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (args.includes('--list') || args.includes('-l')) {
|
||||
const icons = listAvailableIcons()
|
||||
if (icons.length === 0) {
|
||||
console.error('❌ lucide-static not installed. Run pnpm install first.')
|
||||
process.exit(1)
|
||||
}
|
||||
if (process.stdout.isTTY) {
|
||||
paginateList(icons)
|
||||
} else {
|
||||
// Non-interactive mode (piped output)
|
||||
icons.forEach((icon) => console.log(icon))
|
||||
process.exit(0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const overwrite = args.includes('--overwrite') || args.includes('-o')
|
||||
const iconIds = args.filter((arg) => !arg.startsWith('-'))
|
||||
|
||||
if (iconIds.length === 0) {
|
||||
console.error('Usage: pnpm icons:add <icon_id> [icon_id...]')
|
||||
console.error('Example: pnpm icons:add heart star settings-2')
|
||||
console.error('Run with --help for more options')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(lucideIconsDir)) {
|
||||
console.error('❌ lucide-static not installed. Run pnpm install first.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let added = 0
|
||||
for (const iconId of iconIds) {
|
||||
if (addIcon(iconId, overwrite)) {
|
||||
added++
|
||||
}
|
||||
}
|
||||
|
||||
if (added > 0) {
|
||||
console.log(`\n📦 Added ${added} icon(s). Run 'pnpm prepr:frontend:lib' to update exports.`)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -7,13 +7,16 @@
|
||||
"scripts": {
|
||||
"lint": "pnpm run icons:validate && eslint . && prettier --check .",
|
||||
"fix": "pnpm run icons:generate && eslint . --fix && prettier --write .",
|
||||
"icons:add": "jiti build/add-icons.ts",
|
||||
"icons:test": "jiti build/generate-exports.ts --test",
|
||||
"icons:validate": "jiti build/generate-exports.ts --validate",
|
||||
"icons:generate": "jiti build/generate-exports.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modrinth/tooling-config": "workspace:*",
|
||||
"@types/node": "^20.1.0",
|
||||
"jiti": "^2.4.2",
|
||||
"lucide-static": "^0.562.0",
|
||||
"vue": "^3.5.13"
|
||||
}
|
||||
}
|
||||
|
||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -428,9 +428,15 @@ importers:
|
||||
'@modrinth/tooling-config':
|
||||
specifier: workspace:*
|
||||
version: link:../tooling-config
|
||||
'@types/node':
|
||||
specifier: ^20.1.0
|
||||
version: 20.14.11
|
||||
jiti:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
lucide-static:
|
||||
specifier: ^0.562.0
|
||||
version: 0.562.0
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.13(typescript@5.8.3)
|
||||
@@ -5623,6 +5629,9 @@ packages:
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
lucide-static@0.562.0:
|
||||
resolution: {integrity: sha512-TM2vNVOEsO3+ijmno7n/VmxUo0Shr9OXC/UqZc5n4xEVyXX4E4NVvXoRPAZiSsIsdvlQ7alGOcIC/QGtR+OgUQ==}
|
||||
|
||||
magic-string-ast@0.6.2:
|
||||
resolution: {integrity: sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==}
|
||||
engines: {node: '>=16.14.0'}
|
||||
@@ -14324,6 +14333,8 @@ snapshots:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
||||
lucide-static@0.562.0: {}
|
||||
|
||||
magic-string-ast@0.6.2:
|
||||
dependencies:
|
||||
magic-string: 0.30.14
|
||||
|
||||
Reference in New Issue
Block a user