flnxi-Wu/scripts/cleanup-category-options.ts
WuYongJie d1c6efc79d
Some checks failed
CI Check / check (push) Has been cancelled
feat: NAS 部署与 Harbor 推送流程
2025-12-03 14:39:06 +08:00

203 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 清理 UploadField.optionsStatic 中历史遗留的「数组字符串」脏数据
*
* 运行方式:
* npx ts-node scripts/cleanup-category-options.ts
*
* 会遍历所有存在 optionsStatic 的字段,将形如 "[默认店铺]"、
* "[\"默认店铺\"]"、"[阿猫,阿狗]" 等非规范字符串转换为
* 纯字符串数组(JSON),并自动去重、去空。
*/
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
function isArrayString(str: string): boolean {
const trimmed = str.trim()
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) {
return false
}
try {
const parsed = JSON.parse(trimmed)
return Array.isArray(parsed)
} catch {
return false
}
}
function normalizeOptionValue(option: string): string {
let cleaned = option.trim()
if (!cleaned) return ''
try {
if (
(cleaned.startsWith('"') && cleaned.endsWith('"')) ||
(cleaned.startsWith("'") && cleaned.endsWith("'"))
) {
const parsed = JSON.parse(cleaned)
if (typeof parsed === 'string') {
cleaned = parsed.trim()
}
}
} catch {
// ignore parse errors
}
cleaned = cleaned.replace(/\\+"/g, '"').replace(/\\'/g, "'")
cleaned = cleaned.replace(/["'`“”]/g, '')
cleaned = cleaned.trim()
if (cleaned.startsWith('\\') && cleaned.endsWith('\\')) {
cleaned = cleaned.slice(1, -1).trim()
}
return cleaned.trim()
}
function stripLooseBrackets(value: string): string {
let result = value.trim()
let guard = 0
while (result.startsWith('[') && result.endsWith(']') && guard < 5) {
const inner = result.slice(1, -1).trim()
if (!inner || inner === result) {
break
}
result = inner
guard += 1
}
return result
}
function extractLooseArrayOptions(option: string): string[] | null {
let trimmed = option.trim()
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) {
return null
}
let inner = trimmed
let safeGuard = 0
while (inner.startsWith('[') && inner.endsWith(']') && safeGuard < 5) {
const next = inner.slice(1, -1).trim()
if (!next || next === inner) {
break
}
inner = next
safeGuard += 1
}
if (!inner) return []
const segments = inner.split(/[,,]/).map(seg => normalizeOptionValue(seg)).filter(Boolean)
if (segments.length > 1) {
return segments
}
const single = normalizeOptionValue(inner)
return single ? [single] : null
}
function sanitizeOptions(optionsStatic: string | null): string[] {
if (!optionsStatic) return []
const pushOption = (target: Set<string>, value: string) => {
const normalized = stripLooseBrackets(normalizeOptionValue(value))
if (normalized && !isArrayString(normalized)) {
target.add(normalized)
}
}
const result = new Set<string>()
try {
const parsed = JSON.parse(optionsStatic)
if (Array.isArray(parsed)) {
parsed.forEach(opt => {
const optStr = String(opt).trim()
if (!optStr) return
const loose = extractLooseArrayOptions(optStr)
if (loose && loose.length) {
loose.forEach(item => pushOption(result, item))
return
}
if (isArrayString(optStr)) {
try {
const nested = JSON.parse(optStr)
if (Array.isArray(nested)) {
nested.forEach(item => pushOption(result, String(item)))
return
}
} catch {
// ignore
}
}
pushOption(result, optStr)
})
return Array.from(result)
}
} catch {
// 非 JSON 字符串,走 fallback
}
optionsStatic
.split('\n')
.map(line => line.trim())
.filter(Boolean)
.forEach(line => {
const loose = extractLooseArrayOptions(line)
if (loose && loose.length) {
loose.forEach(item => pushOption(result, item))
} else {
pushOption(result, line)
}
})
return Array.from(result)
}
async function main() {
const fields = await prisma.uploadField.findMany({
where: {
optionsStatic: {
not: null,
},
},
select: {
id: true,
key: true,
label: true,
optionsStatic: true,
},
})
let updated = 0
for (const field of fields) {
const sanitized = sanitizeOptions(field.optionsStatic)
const sanitizedJson = JSON.stringify(sanitized)
const currentTrimmed = (field.optionsStatic ?? '').trim()
if (sanitizedJson && sanitizedJson !== currentTrimmed) {
await prisma.uploadField.update({
where: { id: field.id },
data: { optionsStatic: sanitizedJson },
})
updated += 1
console.log(
`[Cleanup] Updated field ${field.key} (${field.label}) -> ${sanitizedJson}`
)
}
}
console.log(`Cleanup finished. Updated ${updated} field(s).`)
}
main()
.catch((error) => {
console.error('Cleanup failed:', error)
process.exitCode = 1
})
.finally(async () => {
await prisma.$disconnect()
})