This commit is contained in:
145
scripts/finalize-h5-build.cjs
Normal file
145
scripts/finalize-h5-build.cjs
Normal file
@@ -0,0 +1,145 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { spawnSync } = require('node:child_process')
|
||||
|
||||
require('./copy-h5-nav-assets.cjs')
|
||||
|
||||
const projectRoot = path.resolve(__dirname, '..')
|
||||
const h5Root = path.join(projectRoot, 'dist', 'build', 'h5')
|
||||
const placeholder = '__REPLACE_WITH_TENCENT_MAP_WEB_KEY__'
|
||||
const badPatterns = [
|
||||
'http://1.92.206.90:9000',
|
||||
'1.92.206.90',
|
||||
'guide.whaoyue.com',
|
||||
placeholder
|
||||
]
|
||||
const args = new Set(process.argv.slice(2))
|
||||
const requireTencentMapKey = args.has('--require-tencent-map-key')
|
||||
const allowPlaceholder = args.has('--allow-placeholder')
|
||||
const tencentMapKey = (process.env.VITE_TENCENT_MAP_KEY || '').trim()
|
||||
|
||||
if (!fs.existsSync(h5Root)) {
|
||||
throw new Error(`H5 build output not found: ${h5Root}`)
|
||||
}
|
||||
|
||||
const isTextFile = (filePath) => /\.(?:js|mjs|html|json|css|txt|svg|xml)$/i.test(filePath)
|
||||
|
||||
const walkFiles = (dir, files = []) => {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const filePath = path.join(dir, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
walkFiles(filePath, files)
|
||||
continue
|
||||
}
|
||||
|
||||
if (entry.isFile()) {
|
||||
files.push(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
const allFiles = walkFiles(h5Root)
|
||||
const textFiles = allFiles.filter(isTextFile)
|
||||
const placeholderFiles = textFiles.filter((file) => fs.readFileSync(file, 'utf8').includes(placeholder))
|
||||
const hasUsableTencentMapKey = tencentMapKey.length > 0 && tencentMapKey !== placeholder
|
||||
|
||||
if (placeholderFiles.length > 0) {
|
||||
if (!hasUsableTencentMapKey) {
|
||||
const message = [
|
||||
'H5 build output still contains Tencent map key placeholder.',
|
||||
'Please set VITE_TENCENT_MAP_KEY before production build.'
|
||||
].join(' ')
|
||||
|
||||
if (requireTencentMapKey && !allowPlaceholder) {
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
console.warn(`[finalize-h5-build] ${message}`)
|
||||
} else {
|
||||
// 所有构建产物文本替换必须由 Node 按 UTF-8 完成,避免 PowerShell 默认编码破坏中文注释或字符串。
|
||||
for (const file of placeholderFiles) {
|
||||
const text = fs.readFileSync(file, 'utf8')
|
||||
fs.writeFileSync(file, text.split(placeholder).join(tencentMapKey), 'utf8')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const indexPath = path.join(h5Root, 'index.html')
|
||||
const indexHtml = fs.readFileSync(indexPath, 'utf8')
|
||||
const entryMatch = indexHtml.match(/src="\/?([^"]*assets\/index-[^"]+\.js)"/)
|
||||
|
||||
if (!entryMatch) {
|
||||
throw new Error(`Entry JS not found in ${indexPath}`)
|
||||
}
|
||||
|
||||
const entryFile = path.join(h5Root, entryMatch[1])
|
||||
|
||||
if (!fs.existsSync(entryFile)) {
|
||||
throw new Error(`Entry JS file not found: ${entryFile}`)
|
||||
}
|
||||
|
||||
const jsFiles = allFiles.filter((file) => /\.js$/i.test(file))
|
||||
|
||||
for (const file of jsFiles) {
|
||||
const check = spawnSync(process.execPath, ['--check', file], { encoding: 'utf8' })
|
||||
|
||||
if (check.status !== 0) {
|
||||
process.stdout.write(check.stdout || '')
|
||||
process.stderr.write(check.stderr || '')
|
||||
throw new Error(`JavaScript syntax check failed: ${path.relative(projectRoot, file)}`)
|
||||
}
|
||||
}
|
||||
|
||||
const badMatches = []
|
||||
|
||||
for (const file of textFiles) {
|
||||
const text = fs.readFileSync(file, 'utf8')
|
||||
const matchedPattern = badPatterns.find((pattern) => text.includes(pattern))
|
||||
|
||||
if (matchedPattern) {
|
||||
badMatches.push({
|
||||
file: path.relative(projectRoot, file),
|
||||
pattern: matchedPattern
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (badMatches.length > 0) {
|
||||
if (allowPlaceholder && badMatches.every((item) => item.pattern === placeholder)) {
|
||||
console.warn('[finalize-h5-build] Tencent map key placeholder is allowed for this build.')
|
||||
} else {
|
||||
console.error(JSON.stringify(badMatches.slice(0, 20), null, 2))
|
||||
throw new Error('H5 build output contains forbidden test host, old domain, or placeholder.')
|
||||
}
|
||||
}
|
||||
|
||||
const readJson = (relativePath) => JSON.parse(fs.readFileSync(path.join(h5Root, relativePath), 'utf8'))
|
||||
const manifest = readJson('static/guide-data/manifest.json')
|
||||
const guideContents = readJson('static/guide-data/guide-contents.json')
|
||||
const exhibits = readJson('static/guide-data/exhibits.json')
|
||||
const guideStops = readJson('static/guide-data/guide-stops.json')
|
||||
|
||||
const requiredFiles = [
|
||||
'static/guide/museum-brand-icons.webp',
|
||||
'static/icons/poi/shortcut-icons.svg',
|
||||
'static/icons/marker-entrance.svg',
|
||||
'static/Fonts/HarmonyOS_SansSC_Regular.ttf'
|
||||
]
|
||||
const missingFiles = requiredFiles.filter((file) => !fs.existsSync(path.join(h5Root, file)))
|
||||
|
||||
if (missingFiles.length > 0) {
|
||||
throw new Error(`Required H5 static assets are missing: ${missingFiles.join(', ')}`)
|
||||
}
|
||||
|
||||
console.log(`H5_ENTRY=${entryMatch[1]}`)
|
||||
console.log(`H5_ENTRY_SIZE=${fs.statSync(entryFile).size}`)
|
||||
console.log(`H5_JS_SYNTAX_CHECKED=${jsFiles.length}`)
|
||||
console.log(`H5_TENCENT_PLACEHOLDER_REPLACED_FILES=${hasUsableTencentMapKey ? placeholderFiles.length : 0}`)
|
||||
console.log(`H5_BAD_STRING_FILE_COUNT=${badMatches.length}`)
|
||||
console.log(`H5_MANIFEST_SOURCE_HOST=${manifest.source?.sourceHost || ''}`)
|
||||
console.log(`H5_GUIDE_CONTENTS_ROWS=${guideContents.rows?.length || 0}`)
|
||||
console.log(`H5_EXHIBITS_ROWS=${exhibits.rows?.length || 0}`)
|
||||
console.log(`H5_GUIDE_STOPS_ROWS=${guideStops.rows?.length || 0}`)
|
||||
Reference in New Issue
Block a user