mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-07 08:12:47 +08:00
📦 Chore(custom): update scripts and migrate to esm
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"productName": "PicList",
|
||||
"appId": "com.kuingsmile.piclist",
|
||||
"afterSign": "scripts/notarize.js",
|
||||
"afterSign": "scripts/notarize.cjs",
|
||||
"directories": {
|
||||
"output": "release/${version}",
|
||||
"buildResources": "build"
|
||||
|
||||
@@ -11,7 +11,7 @@ export default tseslint.config(
|
||||
files: ['./src/*.{ts,tsx,cts,mts,js,cjs,mjs}', './scripts/*.{ts,js,mjs}', './test/*.{ts,js,mjs}']
|
||||
},
|
||||
{
|
||||
ignores: ['**/node_modules/**', '**/out/**', '**/webpack.config.js', 'vitest.workspace.mjs']
|
||||
ignores: ['**/node_modules/**', '**/out/**', '**/webpack.config.js', 'vitest.workspace.mjs', '**/dist/**']
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
18
package.json
18
package.json
@@ -22,13 +22,13 @@
|
||||
"build:linux": "electron-vite build && electron-builder --linux",
|
||||
"bump": "bump-version",
|
||||
"cz": "git-cz",
|
||||
"dev": "electron-vite dev -- --trace-deprecation",
|
||||
"dev": "electron-vite dev",
|
||||
"i18n": "node ./scripts/gen-i18n-types.js",
|
||||
"i18n:check": "node ./scripts/find-unused-i18n.mjs",
|
||||
"i18n:check:verbose": "node ./scripts/find-unused-i18n.mjs --verbose",
|
||||
"i18n:clean": "node ./scripts/find-unused-i18n.mjs --delete",
|
||||
"i18n:clean:dry-run": "node ./scripts/find-unused-i18n.mjs --delete --dry-run",
|
||||
"link": "node ./scripts/link.js",
|
||||
"i18n:check": "node ./scripts/find-unused-i18n.js",
|
||||
"i18n:check:verbose": "node ./scripts/find-unused-i18n.js --verbose",
|
||||
"i18n:clean": "node ./scripts/find-unused-i18n.js --delete",
|
||||
"i18n:clean:dry-run": "node ./scripts/find-unused-i18n.js --delete --dry-run",
|
||||
"link": "node ./scripts/link.cjs",
|
||||
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/main/index.ts",
|
||||
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
@@ -38,9 +38,9 @@
|
||||
"prebuild": "electron-vite build",
|
||||
"preview": "electron-vite preview",
|
||||
"release": "electron-vite build && electron-builder --publish always",
|
||||
"sha256": "node ./scripts/gen-sha256.js",
|
||||
"upload-beta": "node ./scripts/upload-beta.js",
|
||||
"upload-dist": "node ./scripts/upload-dist-to-r2.js"
|
||||
"sha256": "node ./scripts/gen-sha256.cjs",
|
||||
"upload-beta": "node ./scripts/upload-beta.cjs",
|
||||
"upload-dist": "node ./scripts/upload-dist-to-r2.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.856.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const ncu = require('npm-check-updates')
|
||||
const axios = require('axios')
|
||||
import axios from 'axios'
|
||||
import { run } from 'npm-check-updates'
|
||||
|
||||
async function getRepositoryInfo(packageName) {
|
||||
async function getRepositoryInfo (packageName) {
|
||||
try {
|
||||
const { data } = await axios.get(`https://registry.npmjs.org/${packageName}`)
|
||||
const repository = data.repository
|
||||
@@ -17,8 +17,8 @@ async function getRepositoryInfo(packageName) {
|
||||
return null
|
||||
}
|
||||
|
||||
async function checkUpdates() {
|
||||
const updated = await ncu.run({
|
||||
async function checkUpdates () {
|
||||
const updated = await run({
|
||||
packageFile: './package.json',
|
||||
upgrade: false
|
||||
})
|
||||
|
||||
@@ -59,7 +59,7 @@ const win32 = [
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
darwin,
|
||||
linux,
|
||||
win32
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { basename, dirname, extname, join, relative } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { dump, load } from 'js-yaml'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const LOCALE_DIR = join(__dirname, '../public/i18n')
|
||||
const SRC_DIR = join(__dirname, '../src')
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2)
|
||||
const isVerbose = args.includes('--verbose') || args.includes('-v')
|
||||
const shouldDelete = args.includes('--delete') || args.includes('-d')
|
||||
const isDryRun = args.includes('--dry-run')
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m'
|
||||
}
|
||||
|
||||
function colorize (text, color) {
|
||||
return `${colors[color]}${text}${colors.reset}`
|
||||
}
|
||||
|
||||
console.log(`\n🔍 Analyzing i18n keys in ${LOCALE_DIR} and source files in ${SRC_DIR}`)
|
||||
if (shouldDelete) {
|
||||
console.log(colorize(`🗑️ Delete mode enabled ${isDryRun ? '(DRY RUN)' : ''}`, 'yellow'))
|
||||
}
|
||||
console.log('')
|
||||
|
||||
function flattenKeys (obj, prefix = '') {
|
||||
const keys = []
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key
|
||||
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
keys.push(...flattenKeys(value, fullKey))
|
||||
} else {
|
||||
keys.push(fullKey)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
function readLocaleFile (filePath) {
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
return load(content) || {}
|
||||
} catch (error) {
|
||||
console.error(colorize(`Error reading ${filePath}: ${error.message}`, 'red'))
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function getAllI18nKeys () {
|
||||
const localeFiles = readdirSync(LOCALE_DIR).filter(file => file.endsWith('.yml'))
|
||||
const allKeys = new Set()
|
||||
const localeData = {}
|
||||
|
||||
console.log(colorize('\n📁 Found locale files:', 'blue'))
|
||||
|
||||
for (const file of localeFiles) {
|
||||
const filePath = join(LOCALE_DIR, file)
|
||||
const locale = basename(file, '.yml')
|
||||
const data = readLocaleFile(filePath)
|
||||
const keys = flattenKeys(data)
|
||||
|
||||
localeData[locale] = {
|
||||
file: filePath,
|
||||
keys,
|
||||
data
|
||||
}
|
||||
|
||||
keys.forEach(key => allKeys.add(key))
|
||||
|
||||
console.log(` ${colorize('✓', 'green')} ${file} (${keys.length} keys)`)
|
||||
}
|
||||
|
||||
return {
|
||||
allKeys: Array.from(allKeys).sort(),
|
||||
localeData
|
||||
}
|
||||
}
|
||||
|
||||
function findFiles (dir, extensions = ['.vue', '.ts', '.js']) {
|
||||
const files = []
|
||||
|
||||
function walk (currentDir) {
|
||||
try {
|
||||
const entries = readdirSync(currentDir, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(currentDir, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (!['node_modules', '.git', 'dist', 'build', 'target', 'dist_electron'].includes(entry.name)) {
|
||||
walk(fullPath)
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
const ext = extname(entry.name)
|
||||
if (extensions.includes(ext)) {
|
||||
files.push(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(colorize(`Warning: Cannot read directory ${currentDir}: ${error.message}`, 'yellow'))
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir)
|
||||
return files
|
||||
}
|
||||
|
||||
function findKeyUsage (keys) {
|
||||
const usage = {}
|
||||
const dynamicPatterns = []
|
||||
|
||||
keys.forEach(key => {
|
||||
usage[key] = {
|
||||
used: false,
|
||||
files: [],
|
||||
patterns: [],
|
||||
dynamicMatch: false
|
||||
}
|
||||
})
|
||||
|
||||
console.log(colorize('\n🔍 Searching for key usage in source files...', 'blue'))
|
||||
|
||||
const sourceFiles = findFiles(SRC_DIR)
|
||||
|
||||
console.log(` Found ${sourceFiles.length} source files to analyze`)
|
||||
|
||||
const searchPatterns = [
|
||||
/\$T\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
||||
/(?:^|[^a-zA-Z$])T\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
||||
/\{\{\s*\$T\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
||||
/i18nManager\.T\s*\(\s*['"`]([^'"`]+)['"`]/g
|
||||
]
|
||||
|
||||
const dynamicPattern = /\$T\s*\(\s*`([^`]*\$\{[^}]+\}[^`]*)`/g
|
||||
|
||||
sourceFiles.forEach(filePath => {
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
const relativePath = relative(join(__dirname, '..'), filePath)
|
||||
|
||||
searchPatterns.forEach((pattern, patternIndex) => {
|
||||
let match
|
||||
while ((match = pattern.exec(content)) !== null) {
|
||||
const key = match[1]
|
||||
if (usage[key]) {
|
||||
usage[key].used = true
|
||||
if (!usage[key].files.includes(relativePath)) {
|
||||
usage[key].files.push(relativePath)
|
||||
}
|
||||
if (!usage[key].patterns.includes(patternIndex)) {
|
||||
usage[key].patterns.push(patternIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let dynamicMatch
|
||||
while ((dynamicMatch = dynamicPattern.exec(content)) !== null) {
|
||||
const templateString = dynamicMatch[1]
|
||||
|
||||
const staticParts = templateString.split(/\$\{[^}]+\}/)
|
||||
|
||||
const patternInfo = {
|
||||
template: templateString,
|
||||
file: relativePath,
|
||||
staticParts
|
||||
}
|
||||
|
||||
if (!dynamicPatterns.some(p => p.template === templateString && p.file === relativePath)) {
|
||||
dynamicPatterns.push(patternInfo)
|
||||
}
|
||||
|
||||
keys.forEach(key => {
|
||||
if (matchesDynamicPattern(key, staticParts)) {
|
||||
if (usage[key]) {
|
||||
usage[key].used = true
|
||||
usage[key].dynamicMatch = true
|
||||
if (!usage[key].files.includes(relativePath)) {
|
||||
usage[key].files.push(relativePath)
|
||||
}
|
||||
if (!usage[key].patterns.includes('dynamic')) {
|
||||
usage[key].patterns.push('dynamic')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(colorize(`Error reading ${filePath}: ${error.message}`, 'red'))
|
||||
}
|
||||
})
|
||||
|
||||
usage._dynamicPatterns = dynamicPatterns
|
||||
|
||||
return usage
|
||||
}
|
||||
|
||||
function matchesDynamicPattern (key, staticParts) {
|
||||
if (staticParts.length === 0) return false
|
||||
|
||||
let keyIndex = 0
|
||||
|
||||
for (let i = 0; i < staticParts.length; i++) {
|
||||
const part = staticParts[i]
|
||||
|
||||
if (part === '') {
|
||||
if (i < staticParts.length - 1) {
|
||||
const nextPart = staticParts[i + 1]
|
||||
if (nextPart) {
|
||||
const nextIndex = key.indexOf(nextPart, keyIndex)
|
||||
if (nextIndex === -1) return false
|
||||
keyIndex = nextIndex
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
if (!key.startsWith(part)) return false
|
||||
keyIndex = part.length
|
||||
} else if (i === staticParts.length - 1) {
|
||||
if (part && !key.endsWith(part)) return false
|
||||
} else {
|
||||
const index = key.indexOf(part, keyIndex)
|
||||
if (index === -1) return false
|
||||
keyIndex = index + part.length
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function findLocaleInconsistencies (localeData) {
|
||||
const locales = Object.keys(localeData)
|
||||
const inconsistencies = {}
|
||||
|
||||
if (locales.length < 2) {
|
||||
return inconsistencies
|
||||
}
|
||||
|
||||
locales.forEach(locale => {
|
||||
const currentKeys = new Set(localeData[locale].keys)
|
||||
inconsistencies[locale] = {
|
||||
missing: [],
|
||||
extra: []
|
||||
}
|
||||
|
||||
locales.forEach(otherLocale => {
|
||||
if (locale !== otherLocale) {
|
||||
localeData[otherLocale].keys.forEach(key => {
|
||||
if (!currentKeys.has(key) && !inconsistencies[locale].missing.includes(key)) {
|
||||
inconsistencies[locale].missing.push(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
localeData[locale].keys.forEach(key => {
|
||||
const existsInOthers = locales.some(
|
||||
otherLocale => locale !== otherLocale && localeData[otherLocale].keys.includes(key)
|
||||
)
|
||||
if (!existsInOthers) {
|
||||
inconsistencies[locale].extra.push(key)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return inconsistencies
|
||||
}
|
||||
|
||||
function deleteUnusedKeys (unusedKeys, localeData, isDryRun = false) {
|
||||
const results = {}
|
||||
|
||||
Object.entries(localeData).forEach(([locale, data]) => {
|
||||
results[locale] = {
|
||||
deleted: [],
|
||||
errors: []
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedData = { ...data.data }
|
||||
|
||||
unusedKeys.forEach(key => {
|
||||
if (key in updatedData) {
|
||||
if (!isDryRun) {
|
||||
delete updatedData[key]
|
||||
}
|
||||
results[locale].deleted.push(key)
|
||||
}
|
||||
})
|
||||
|
||||
if (!isDryRun && results[locale].deleted.length > 0) {
|
||||
const yamlContent = dump(updatedData, {
|
||||
indent: 2,
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
sortKeys: false
|
||||
})
|
||||
writeFileSync(data.file, yamlContent, 'utf8')
|
||||
}
|
||||
} catch (error) {
|
||||
results[locale].errors.push(`Failed to process ${locale}: ${error.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
function main () {
|
||||
console.log(colorize('🌐 PicList - I18n Usage Analyzer', 'cyan'))
|
||||
console.log(colorize('==================================', 'cyan'))
|
||||
|
||||
const { allKeys, localeData } = getAllI18nKeys()
|
||||
|
||||
console.log(colorize(`\n📊 Total unique keys found: ${allKeys.length}`, 'yellow'))
|
||||
const usage = findKeyUsage(allKeys)
|
||||
const dynamicPatterns = usage._dynamicPatterns || []
|
||||
delete usage._dynamicPatterns
|
||||
|
||||
const usedKeys = allKeys.filter(key => usage[key].used)
|
||||
const unusedKeys = allKeys.filter(key => !usage[key].used)
|
||||
const dynamicallyUsedKeys = usedKeys.filter(key => usage[key].dynamicMatch)
|
||||
const staticUsedKeys = usedKeys.filter(key => !usage[key].dynamicMatch)
|
||||
|
||||
const inconsistencies = findLocaleInconsistencies(localeData)
|
||||
|
||||
console.log(colorize('\n📈 Usage Summary:', 'blue'))
|
||||
console.log(` ${colorize('✓', 'green')} Used keys: ${usedKeys.length}`)
|
||||
console.log(` ${colorize('→', 'cyan')} Static usage: ${staticUsedKeys.length}`)
|
||||
console.log(` ${colorize('→', 'magenta')} Dynamic usage: ${dynamicallyUsedKeys.length}`)
|
||||
console.log(` ${colorize('✗', 'red')} Unused keys: ${unusedKeys.length}`)
|
||||
console.log(` ${colorize('📊', 'yellow')} Usage rate: ${((usedKeys.length / allKeys.length) * 100).toFixed(1)}%`)
|
||||
|
||||
if (dynamicPatterns.length > 0) {
|
||||
console.log(colorize('\n🔮 Dynamic I18n Patterns Detected:', 'magenta'))
|
||||
console.log(colorize('===================================', 'magenta'))
|
||||
|
||||
dynamicPatterns.forEach((pattern, index) => {
|
||||
console.log(colorize(`\n${index + 1}. Template: \`${pattern.template}\``, 'cyan'))
|
||||
console.log(` File: ${pattern.file}`)
|
||||
console.log(` Static parts: [${pattern.staticParts.map(p => `"${p}"`).join(', ')}]`)
|
||||
|
||||
const matchingKeys = allKeys.filter(key => matchesDynamicPattern(key, pattern.staticParts))
|
||||
if (matchingKeys.length > 0) {
|
||||
console.log(
|
||||
` ${colorize('Matches', 'green')} (${matchingKeys.length}): ${matchingKeys.slice(0, 5).join(', ')}${
|
||||
matchingKeys.length > 5 ? '...' : ''
|
||||
}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (unusedKeys.length > 0) {
|
||||
console.log(colorize('\n🗑️ Unused I18n Keys:', 'red'))
|
||||
console.log(colorize('====================', 'red'))
|
||||
|
||||
const groupedUnused = {}
|
||||
unusedKeys.forEach(key => {
|
||||
const namespace = key.includes('.') ? key.split('.')[0] : 'ROOT'
|
||||
if (!groupedUnused[namespace]) {
|
||||
groupedUnused[namespace] = []
|
||||
}
|
||||
groupedUnused[namespace].push(key)
|
||||
})
|
||||
|
||||
Object.entries(groupedUnused).forEach(([namespace, keys]) => {
|
||||
console.log(colorize(`\n[${namespace}] - ${keys.length} unused keys:`, 'yellow'))
|
||||
keys.forEach(key => {
|
||||
console.log(` ${colorize('✗', 'red')} ${key}`)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.log(colorize('\n🎉 No unused keys found! All i18n keys are being used.', 'green'))
|
||||
}
|
||||
|
||||
const hasInconsistencies = Object.values(inconsistencies).some(inc => inc.missing.length > 0 || inc.extra.length > 0)
|
||||
|
||||
if (hasInconsistencies) {
|
||||
console.log(colorize('\n⚠️ Locale Inconsistencies:', 'yellow'))
|
||||
console.log(colorize('=========================', 'yellow'))
|
||||
|
||||
Object.entries(inconsistencies).forEach(([locale, data]) => {
|
||||
if (data.missing.length > 0 || data.extra.length > 0) {
|
||||
console.log(colorize(`\n[${locale}.yml]:`, 'cyan'))
|
||||
|
||||
if (data.missing.length > 0) {
|
||||
console.log(colorize(` Missing ${data.missing.length} keys:`, 'red'))
|
||||
data.missing.forEach(key => {
|
||||
console.log(` ${colorize('✗', 'red')} ${key}`)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.extra.length > 0) {
|
||||
console.log(colorize(` Extra ${data.extra.length} keys:`, 'blue'))
|
||||
data.extra.forEach(key => {
|
||||
console.log(` ${colorize('!', 'blue')} ${key}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isVerbose) {
|
||||
console.log(colorize('\n📋 Sample Used Keys (first 10):', 'blue'))
|
||||
console.log(colorize('=================================', 'blue'))
|
||||
|
||||
usedKeys.slice(0, 10).forEach(key => {
|
||||
const files = usage[key].files.slice(0, 3)
|
||||
const moreFiles = usage[key].files.length > 3 ? ` (+${usage[key].files.length - 3} more)` : ''
|
||||
const usageType = usage[key].dynamicMatch ? colorize('(dynamic)', 'magenta') : colorize('(static)', 'cyan')
|
||||
console.log(` ${colorize('✓', 'green')} ${key} ${usageType}`)
|
||||
console.log(` Used in: ${files.join(', ')}${moreFiles}`)
|
||||
})
|
||||
|
||||
if (dynamicallyUsedKeys.length > 0) {
|
||||
console.log(colorize('\n🔮 Dynamic Key Usage Details:', 'magenta'))
|
||||
console.log(colorize('=============================', 'magenta'))
|
||||
|
||||
dynamicallyUsedKeys.slice(0, 5).forEach(key => {
|
||||
const files = usage[key].files.slice(0, 2)
|
||||
console.log(` ${colorize('✨', 'magenta')} ${key}`)
|
||||
console.log(` Files: ${files.join(', ')}`)
|
||||
})
|
||||
|
||||
if (dynamicallyUsedKeys.length > 5) {
|
||||
console.log(` ... and ${dynamicallyUsedKeys.length - 5} more dynamic keys`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelete && unusedKeys.length > 0) {
|
||||
console.log(colorize('\n🗑️ Deleting Unused Keys:', 'yellow'))
|
||||
console.log(colorize('=========================', 'yellow'))
|
||||
|
||||
if (isDryRun) {
|
||||
console.log(colorize('🔍 DRY RUN MODE - No files will be modified', 'cyan'))
|
||||
}
|
||||
|
||||
const deletionResults = deleteUnusedKeys(unusedKeys, localeData, isDryRun)
|
||||
|
||||
Object.entries(deletionResults).forEach(([locale, result]) => {
|
||||
console.log(colorize(`\n[${locale}.yml]:`, 'cyan'))
|
||||
if (result.deleted.length > 0) {
|
||||
console.log(colorize(` Deleted ${result.deleted.length} keys:`, 'green'))
|
||||
result.deleted.forEach(key => {
|
||||
console.log(` ${colorize('✓', 'green')} ${key}`)
|
||||
})
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
console.log(colorize(` Errors ${result.errors.length}:`, 'red'))
|
||||
result.errors.forEach(error => {
|
||||
console.log(` ${colorize('✗', 'red')} ${error}`)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (!isDryRun) {
|
||||
console.log(colorize('\n✨ Deletion complete! Remember to regenerate type definitions.', 'green'))
|
||||
console.log(colorize('💡 Run `yarn i18n` to update TypeScript types', 'blue'))
|
||||
}
|
||||
}
|
||||
|
||||
console.log(colorize('\n✨ Analysis complete!', 'cyan'))
|
||||
|
||||
if (unusedKeys.length > 0 && !shouldDelete) {
|
||||
console.log(colorize('\n💡 Tips:', 'blue'))
|
||||
console.log(colorize('- Run with --verbose (-v) flag to see usage details of used keys', 'blue'))
|
||||
console.log(colorize('- Run with --delete (-d) flag to automatically remove unused keys', 'blue'))
|
||||
console.log(colorize('- Run with --delete --dry-run to preview what would be deleted', 'blue'))
|
||||
} else if (unusedKeys.length === 0) {
|
||||
console.log(colorize('\n💡 Tip: Run with --verbose (-v) flag to see usage details of used keys', 'blue'))
|
||||
}
|
||||
|
||||
if (unusedKeys.length > 0 && !shouldDelete) {
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,499 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { basename, dirname, extname, join, relative } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { load, dump } from 'js-yaml'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const LOCALE_DIR = join(__dirname, '../public/i18n')
|
||||
const SRC_DIR = join(__dirname, '../src')
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2)
|
||||
const isVerbose = args.includes('--verbose') || args.includes('-v')
|
||||
const shouldDelete = args.includes('--delete') || args.includes('-d')
|
||||
const isDryRun = args.includes('--dry-run')
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m'
|
||||
}
|
||||
|
||||
function colorize(text, color) {
|
||||
return `${colors[color]}${text}${colors.reset}`
|
||||
}
|
||||
|
||||
console.log(`\n🔍 Analyzing i18n keys in ${LOCALE_DIR} and source files in ${SRC_DIR}`)
|
||||
if (shouldDelete) {
|
||||
console.log(colorize(`🗑️ Delete mode enabled ${isDryRun ? '(DRY RUN)' : ''}`, 'yellow'))
|
||||
}
|
||||
console.log('')
|
||||
|
||||
function flattenKeys(obj, prefix = '') {
|
||||
const keys = []
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key
|
||||
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
keys.push(...flattenKeys(value, fullKey))
|
||||
} else {
|
||||
keys.push(fullKey)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
function readLocaleFile(filePath) {
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
return load(content) || {}
|
||||
} catch (error) {
|
||||
console.error(colorize(`Error reading ${filePath}: ${error.message}`, 'red'))
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function getAllI18nKeys() {
|
||||
const localeFiles = readdirSync(LOCALE_DIR).filter(file => file.endsWith('.yml'))
|
||||
const allKeys = new Set()
|
||||
const localeData = {}
|
||||
|
||||
console.log(colorize('\n📁 Found locale files:', 'blue'))
|
||||
|
||||
for (const file of localeFiles) {
|
||||
const filePath = join(LOCALE_DIR, file)
|
||||
const locale = basename(file, '.yml')
|
||||
const data = readLocaleFile(filePath)
|
||||
const keys = flattenKeys(data)
|
||||
|
||||
localeData[locale] = {
|
||||
file: filePath,
|
||||
keys,
|
||||
data
|
||||
}
|
||||
|
||||
keys.forEach(key => allKeys.add(key))
|
||||
|
||||
console.log(` ${colorize('✓', 'green')} ${file} (${keys.length} keys)`)
|
||||
}
|
||||
|
||||
return {
|
||||
allKeys: Array.from(allKeys).sort(),
|
||||
localeData
|
||||
}
|
||||
}
|
||||
|
||||
function findFiles(dir, extensions = ['.vue', '.ts', '.js']) {
|
||||
const files = []
|
||||
|
||||
function walk(currentDir) {
|
||||
try {
|
||||
const entries = readdirSync(currentDir, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(currentDir, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (!['node_modules', '.git', 'dist', 'build', 'target', 'dist_electron'].includes(entry.name)) {
|
||||
walk(fullPath)
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
const ext = extname(entry.name)
|
||||
if (extensions.includes(ext)) {
|
||||
files.push(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(colorize(`Warning: Cannot read directory ${currentDir}: ${error.message}`, 'yellow'))
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir)
|
||||
return files
|
||||
}
|
||||
|
||||
function findKeyUsage(keys) {
|
||||
const usage = {}
|
||||
const dynamicPatterns = []
|
||||
|
||||
keys.forEach(key => {
|
||||
usage[key] = {
|
||||
used: false,
|
||||
files: [],
|
||||
patterns: [],
|
||||
dynamicMatch: false
|
||||
}
|
||||
})
|
||||
|
||||
console.log(colorize('\n🔍 Searching for key usage in source files...', 'blue'))
|
||||
|
||||
const sourceFiles = findFiles(SRC_DIR)
|
||||
|
||||
console.log(` Found ${sourceFiles.length} source files to analyze`)
|
||||
|
||||
const searchPatterns = [
|
||||
/\$T\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
||||
/(?:^|[^a-zA-Z$])T\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
||||
/\{\{\s*\$T\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
||||
/i18nManager\.T\s*\(\s*['"`]([^'"`]+)['"`]/g
|
||||
]
|
||||
|
||||
const dynamicPattern = /\$T\s*\(\s*`([^`]*\$\{[^}]+\}[^`]*)`/g
|
||||
|
||||
sourceFiles.forEach(filePath => {
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
const relativePath = relative(join(__dirname, '..'), filePath)
|
||||
|
||||
searchPatterns.forEach((pattern, patternIndex) => {
|
||||
let match
|
||||
while ((match = pattern.exec(content)) !== null) {
|
||||
const key = match[1]
|
||||
if (usage[key]) {
|
||||
usage[key].used = true
|
||||
if (!usage[key].files.includes(relativePath)) {
|
||||
usage[key].files.push(relativePath)
|
||||
}
|
||||
if (!usage[key].patterns.includes(patternIndex)) {
|
||||
usage[key].patterns.push(patternIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let dynamicMatch
|
||||
while ((dynamicMatch = dynamicPattern.exec(content)) !== null) {
|
||||
const templateString = dynamicMatch[1]
|
||||
|
||||
const staticParts = templateString.split(/\$\{[^}]+\}/)
|
||||
|
||||
const patternInfo = {
|
||||
template: templateString,
|
||||
file: relativePath,
|
||||
staticParts
|
||||
}
|
||||
|
||||
if (!dynamicPatterns.some(p => p.template === templateString && p.file === relativePath)) {
|
||||
dynamicPatterns.push(patternInfo)
|
||||
}
|
||||
|
||||
keys.forEach(key => {
|
||||
if (matchesDynamicPattern(key, staticParts)) {
|
||||
if (usage[key]) {
|
||||
usage[key].used = true
|
||||
usage[key].dynamicMatch = true
|
||||
if (!usage[key].files.includes(relativePath)) {
|
||||
usage[key].files.push(relativePath)
|
||||
}
|
||||
if (!usage[key].patterns.includes('dynamic')) {
|
||||
usage[key].patterns.push('dynamic')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(colorize(`Error reading ${filePath}: ${error.message}`, 'red'))
|
||||
}
|
||||
})
|
||||
|
||||
usage._dynamicPatterns = dynamicPatterns
|
||||
|
||||
return usage
|
||||
}
|
||||
|
||||
function matchesDynamicPattern(key, staticParts) {
|
||||
if (staticParts.length === 0) return false
|
||||
|
||||
let keyIndex = 0
|
||||
|
||||
for (let i = 0; i < staticParts.length; i++) {
|
||||
const part = staticParts[i]
|
||||
|
||||
if (part === '') {
|
||||
if (i < staticParts.length - 1) {
|
||||
const nextPart = staticParts[i + 1]
|
||||
if (nextPart) {
|
||||
const nextIndex = key.indexOf(nextPart, keyIndex)
|
||||
if (nextIndex === -1) return false
|
||||
keyIndex = nextIndex
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
if (!key.startsWith(part)) return false
|
||||
keyIndex = part.length
|
||||
} else if (i === staticParts.length - 1) {
|
||||
if (part && !key.endsWith(part)) return false
|
||||
} else {
|
||||
const index = key.indexOf(part, keyIndex)
|
||||
if (index === -1) return false
|
||||
keyIndex = index + part.length
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function findLocaleInconsistencies(localeData) {
|
||||
const locales = Object.keys(localeData)
|
||||
const inconsistencies = {}
|
||||
|
||||
if (locales.length < 2) {
|
||||
return inconsistencies
|
||||
}
|
||||
|
||||
locales.forEach(locale => {
|
||||
const currentKeys = new Set(localeData[locale].keys)
|
||||
inconsistencies[locale] = {
|
||||
missing: [],
|
||||
extra: []
|
||||
}
|
||||
|
||||
locales.forEach(otherLocale => {
|
||||
if (locale !== otherLocale) {
|
||||
localeData[otherLocale].keys.forEach(key => {
|
||||
if (!currentKeys.has(key) && !inconsistencies[locale].missing.includes(key)) {
|
||||
inconsistencies[locale].missing.push(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
localeData[locale].keys.forEach(key => {
|
||||
const existsInOthers = locales.some(
|
||||
otherLocale => locale !== otherLocale && localeData[otherLocale].keys.includes(key)
|
||||
)
|
||||
if (!existsInOthers) {
|
||||
inconsistencies[locale].extra.push(key)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return inconsistencies
|
||||
}
|
||||
|
||||
function deleteUnusedKeys(unusedKeys, localeData, isDryRun = false) {
|
||||
const results = {}
|
||||
|
||||
Object.entries(localeData).forEach(([locale, data]) => {
|
||||
results[locale] = {
|
||||
deleted: [],
|
||||
errors: []
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedData = { ...data.data }
|
||||
|
||||
unusedKeys.forEach(key => {
|
||||
if (key in updatedData) {
|
||||
if (!isDryRun) {
|
||||
delete updatedData[key]
|
||||
}
|
||||
results[locale].deleted.push(key)
|
||||
}
|
||||
})
|
||||
|
||||
if (!isDryRun && results[locale].deleted.length > 0) {
|
||||
const yamlContent = dump(updatedData, {
|
||||
indent: 2,
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
sortKeys: false
|
||||
})
|
||||
writeFileSync(data.file, yamlContent, 'utf8')
|
||||
}
|
||||
} catch (error) {
|
||||
results[locale].errors.push(`Failed to process ${locale}: ${error.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log(colorize('🌐 PicList - I18n Usage Analyzer', 'cyan'))
|
||||
console.log(colorize('==================================', 'cyan'))
|
||||
|
||||
const { allKeys, localeData } = getAllI18nKeys()
|
||||
|
||||
console.log(colorize(`\n📊 Total unique keys found: ${allKeys.length}`, 'yellow'))
|
||||
const usage = findKeyUsage(allKeys)
|
||||
const dynamicPatterns = usage._dynamicPatterns || []
|
||||
delete usage._dynamicPatterns
|
||||
|
||||
const usedKeys = allKeys.filter(key => usage[key].used)
|
||||
const unusedKeys = allKeys.filter(key => !usage[key].used)
|
||||
const dynamicallyUsedKeys = usedKeys.filter(key => usage[key].dynamicMatch)
|
||||
const staticUsedKeys = usedKeys.filter(key => !usage[key].dynamicMatch)
|
||||
|
||||
const inconsistencies = findLocaleInconsistencies(localeData)
|
||||
|
||||
console.log(colorize('\n📈 Usage Summary:', 'blue'))
|
||||
console.log(` ${colorize('✓', 'green')} Used keys: ${usedKeys.length}`)
|
||||
console.log(` ${colorize('→', 'cyan')} Static usage: ${staticUsedKeys.length}`)
|
||||
console.log(` ${colorize('→', 'magenta')} Dynamic usage: ${dynamicallyUsedKeys.length}`)
|
||||
console.log(` ${colorize('✗', 'red')} Unused keys: ${unusedKeys.length}`)
|
||||
console.log(` ${colorize('📊', 'yellow')} Usage rate: ${((usedKeys.length / allKeys.length) * 100).toFixed(1)}%`)
|
||||
|
||||
if (dynamicPatterns.length > 0) {
|
||||
console.log(colorize('\n🔮 Dynamic I18n Patterns Detected:', 'magenta'))
|
||||
console.log(colorize('===================================', 'magenta'))
|
||||
|
||||
dynamicPatterns.forEach((pattern, index) => {
|
||||
console.log(colorize(`\n${index + 1}. Template: \`${pattern.template}\``, 'cyan'))
|
||||
console.log(` File: ${pattern.file}`)
|
||||
console.log(` Static parts: [${pattern.staticParts.map(p => `"${p}"`).join(', ')}]`)
|
||||
|
||||
const matchingKeys = allKeys.filter(key => matchesDynamicPattern(key, pattern.staticParts))
|
||||
if (matchingKeys.length > 0) {
|
||||
console.log(
|
||||
` ${colorize('Matches', 'green')} (${matchingKeys.length}): ${matchingKeys.slice(0, 5).join(', ')}${
|
||||
matchingKeys.length > 5 ? '...' : ''
|
||||
}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (unusedKeys.length > 0) {
|
||||
console.log(colorize('\n🗑️ Unused I18n Keys:', 'red'))
|
||||
console.log(colorize('====================', 'red'))
|
||||
|
||||
const groupedUnused = {}
|
||||
unusedKeys.forEach(key => {
|
||||
const namespace = key.includes('.') ? key.split('.')[0] : 'ROOT'
|
||||
if (!groupedUnused[namespace]) {
|
||||
groupedUnused[namespace] = []
|
||||
}
|
||||
groupedUnused[namespace].push(key)
|
||||
})
|
||||
|
||||
Object.entries(groupedUnused).forEach(([namespace, keys]) => {
|
||||
console.log(colorize(`\n[${namespace}] - ${keys.length} unused keys:`, 'yellow'))
|
||||
keys.forEach(key => {
|
||||
console.log(` ${colorize('✗', 'red')} ${key}`)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.log(colorize('\n🎉 No unused keys found! All i18n keys are being used.', 'green'))
|
||||
}
|
||||
|
||||
const hasInconsistencies = Object.values(inconsistencies).some(inc => inc.missing.length > 0 || inc.extra.length > 0)
|
||||
|
||||
if (hasInconsistencies) {
|
||||
console.log(colorize('\n⚠️ Locale Inconsistencies:', 'yellow'))
|
||||
console.log(colorize('=========================', 'yellow'))
|
||||
|
||||
Object.entries(inconsistencies).forEach(([locale, data]) => {
|
||||
if (data.missing.length > 0 || data.extra.length > 0) {
|
||||
console.log(colorize(`\n[${locale}.yml]:`, 'cyan'))
|
||||
|
||||
if (data.missing.length > 0) {
|
||||
console.log(colorize(` Missing ${data.missing.length} keys:`, 'red'))
|
||||
data.missing.forEach(key => {
|
||||
console.log(` ${colorize('✗', 'red')} ${key}`)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.extra.length > 0) {
|
||||
console.log(colorize(` Extra ${data.extra.length} keys:`, 'blue'))
|
||||
data.extra.forEach(key => {
|
||||
console.log(` ${colorize('!', 'blue')} ${key}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isVerbose) {
|
||||
console.log(colorize('\n📋 Sample Used Keys (first 10):', 'blue'))
|
||||
console.log(colorize('=================================', 'blue'))
|
||||
|
||||
usedKeys.slice(0, 10).forEach(key => {
|
||||
const files = usage[key].files.slice(0, 3)
|
||||
const moreFiles = usage[key].files.length > 3 ? ` (+${usage[key].files.length - 3} more)` : ''
|
||||
const usageType = usage[key].dynamicMatch ? colorize('(dynamic)', 'magenta') : colorize('(static)', 'cyan')
|
||||
console.log(` ${colorize('✓', 'green')} ${key} ${usageType}`)
|
||||
console.log(` Used in: ${files.join(', ')}${moreFiles}`)
|
||||
})
|
||||
|
||||
if (dynamicallyUsedKeys.length > 0) {
|
||||
console.log(colorize('\n🔮 Dynamic Key Usage Details:', 'magenta'))
|
||||
console.log(colorize('=============================', 'magenta'))
|
||||
|
||||
dynamicallyUsedKeys.slice(0, 5).forEach(key => {
|
||||
const files = usage[key].files.slice(0, 2)
|
||||
console.log(` ${colorize('✨', 'magenta')} ${key}`)
|
||||
console.log(` Files: ${files.join(', ')}`)
|
||||
})
|
||||
|
||||
if (dynamicallyUsedKeys.length > 5) {
|
||||
console.log(` ... and ${dynamicallyUsedKeys.length - 5} more dynamic keys`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelete && unusedKeys.length > 0) {
|
||||
console.log(colorize('\n🗑️ Deleting Unused Keys:', 'yellow'))
|
||||
console.log(colorize('=========================', 'yellow'))
|
||||
|
||||
if (isDryRun) {
|
||||
console.log(colorize('🔍 DRY RUN MODE - No files will be modified', 'cyan'))
|
||||
}
|
||||
|
||||
const deletionResults = deleteUnusedKeys(unusedKeys, localeData, isDryRun)
|
||||
|
||||
Object.entries(deletionResults).forEach(([locale, result]) => {
|
||||
console.log(colorize(`\n[${locale}.yml]:`, 'cyan'))
|
||||
if (result.deleted.length > 0) {
|
||||
console.log(colorize(` Deleted ${result.deleted.length} keys:`, 'green'))
|
||||
result.deleted.forEach(key => {
|
||||
console.log(` ${colorize('✓', 'green')} ${key}`)
|
||||
})
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
console.log(colorize(` Errors ${result.errors.length}:`, 'red'))
|
||||
result.errors.forEach(error => {
|
||||
console.log(` ${colorize('✗', 'red')} ${error}`)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (!isDryRun) {
|
||||
console.log(colorize('\n✨ Deletion complete! Remember to regenerate type definitions.', 'green'))
|
||||
console.log(colorize('💡 Run `yarn i18n` to update TypeScript types', 'blue'))
|
||||
}
|
||||
}
|
||||
|
||||
console.log(colorize('\n✨ Analysis complete!', 'cyan'))
|
||||
|
||||
if (unusedKeys.length > 0 && !shouldDelete) {
|
||||
console.log(colorize('\n💡 Tips:', 'blue'))
|
||||
console.log(colorize('- Run with --verbose (-v) flag to see usage details of used keys', 'blue'))
|
||||
console.log(colorize('- Run with --delete (-d) flag to automatically remove unused keys', 'blue'))
|
||||
console.log(colorize('- Run with --delete --dry-run to preview what would be deleted', 'blue'))
|
||||
} else if (unusedKeys.length === 0) {
|
||||
console.log(colorize('\n💡 Tip: Run with --verbose (-v) flag to see usage details of used keys', 'blue'))
|
||||
}
|
||||
|
||||
if (unusedKeys.length > 0 && !shouldDelete) {
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -1,21 +1,27 @@
|
||||
const yaml = require('js-yaml')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const languageFileName = 'zh-CN.yml' // use zh-CN for type is OK
|
||||
const i18nFolder = path.join(__dirname, '../public/i18n')
|
||||
const typeFolder = path.join(__dirname, '../src/universal/types')
|
||||
const languageFile = path.join(i18nFolder, languageFileName)
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const langFile = fs.readFileSync(languageFile, 'utf8')
|
||||
import { load } from 'js-yaml'
|
||||
const languageFileName = 'zh-CN.yml'
|
||||
|
||||
const obj = yaml.load(langFile)
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const i18nFolder = join(__dirname, '../public/i18n')
|
||||
const typeFolder = join(__dirname, '../src/universal/types')
|
||||
const languageFile = join(i18nFolder, languageFileName)
|
||||
|
||||
const langFile = readFileSync(languageFile, 'utf8')
|
||||
|
||||
const obj = load(langFile)
|
||||
|
||||
const keys = Object.keys(obj)
|
||||
|
||||
const types = `interface ILocales {
|
||||
const types = `export interface ILocales {
|
||||
${keys.map(key => `${key}: string`).join('\n ')}
|
||||
}
|
||||
type ILocalesKey = keyof ILocales
|
||||
export type ILocalesKey = keyof ILocales
|
||||
`
|
||||
|
||||
fs.writeFileSync(path.join(typeFolder, 'i18n.d.ts'), types)
|
||||
writeFileSync(join(typeFolder, 'i18n.ts'), types)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const crypto = require('crypto')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const crypto = require('node:crypto')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const axios = require('axios')
|
||||
const fs = require('fs-extra')
|
||||
const pkg = require('../package.json')
|
||||
@@ -24,7 +24,7 @@ const files = [
|
||||
/**
|
||||
* Create progress bar string
|
||||
*/
|
||||
function getProgressBar(current, total, length = 20) {
|
||||
function getProgressBar (current, total, length = 20) {
|
||||
const progress = Math.round((current / total) * length)
|
||||
const percentage = Math.round((current / total) * 100)
|
||||
const bar = '█'.repeat(progress) + '░'.repeat(length - progress)
|
||||
@@ -34,7 +34,7 @@ function getProgressBar(current, total, length = 20) {
|
||||
/**
|
||||
* Format bytes to human-readable format
|
||||
*/
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
function formatBytes (bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
||||
@@ -45,7 +45,7 @@ function formatBytes(bytes, decimals = 2) {
|
||||
/**
|
||||
* Download file and calculate SHA256 hash
|
||||
*/
|
||||
async function downloadAndHash(fileInfo) {
|
||||
async function downloadAndHash (fileInfo) {
|
||||
const { url, name } = fileInfo
|
||||
const filePath = path.join(DOWNLOAD_DIR, name)
|
||||
|
||||
@@ -101,7 +101,7 @@ async function downloadAndHash(fileInfo) {
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
async function main() {
|
||||
async function main () {
|
||||
console.log(`Generating SHA256 hashes for PicList v${version}`)
|
||||
console.log(`Download directory: ${DOWNLOAD_DIR}`)
|
||||
|
||||
@@ -5,7 +5,7 @@ require('dotenv').config()
|
||||
const { notarize } = require('@electron/notarize')
|
||||
const { ELECTRON_SKIP_NOTARIZATION, XCODE_APP_LOADER_EMAIL, XCODE_APP_LOADER_PASSWORD, XCODE_TEAM_ID } = process.env
|
||||
|
||||
async function main(context) {
|
||||
async function main (context) {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
|
||||
if (
|
||||
@@ -4,8 +4,8 @@ const S3Client = require('@aws-sdk/client-s3')
|
||||
const Upload = require('@aws-sdk/lib-storage')
|
||||
const pkg = require('../package.json')
|
||||
const configList = require('./config')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const BUCKET = 'piclist-dl'
|
||||
const VERSION = pkg.version
|
||||
@@ -5,8 +5,8 @@ const S3Client = require('@aws-sdk/client-s3')
|
||||
const Upload = require('@aws-sdk/lib-storage')
|
||||
const pkg = require('../package.json')
|
||||
const configList = require('./config')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const yaml = require('js-yaml')
|
||||
const mime = require('mime-types')
|
||||
|
||||
Reference in New Issue
Block a user