mihomo-party/scripts/prepare.mjs

425 lines
12 KiB
JavaScript
Raw Normal View History

2024-07-31 21:04:30 +08:00
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import fs from 'fs'
import AdmZip from 'adm-zip'
import path from 'path'
import zlib from 'zlib'
import { extract } from 'tar'
import { execSync } from 'child_process'
const cwd = process.cwd()
const TEMP_DIR = path.join(cwd, 'node_modules/.temp')
let arch = process.arch
const platform = process.platform
if (process.argv.slice(2).length !== 0) {
arch = process.argv.slice(2)[0].replace('--', '')
}
/* ======= mihomo alpha======= */
const MIHOMO_ALPHA_VERSION_URL =
'https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt'
const MIHOMO_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha`
let MIHOMO_ALPHA_VERSION
const MIHOMO_ALPHA_MAP = {
'win32-x64': 'mihomo-windows-amd64-compatible',
'win32-ia32': 'mihomo-windows-386',
'win32-arm64': 'mihomo-windows-arm64',
'darwin-x64': 'mihomo-darwin-amd64-compatible',
'darwin-arm64': 'mihomo-darwin-arm64',
'linux-x64': 'mihomo-linux-amd64-compatible',
'linux-arm64': 'mihomo-linux-arm64'
}
// Fetch the latest alpha release version from the version.txt file
async function getLatestAlphaVersion() {
try {
const response = await fetch(MIHOMO_ALPHA_VERSION_URL, {
method: 'GET'
})
let v = await response.text()
MIHOMO_ALPHA_VERSION = v.trim() // Trim to remove extra whitespaces
console.log(`Latest alpha version: ${MIHOMO_ALPHA_VERSION}`)
} catch (error) {
console.error('Error fetching latest alpha version:', error.message)
process.exit(1)
}
}
/* ======= mihomo release ======= */
const MIHOMO_VERSION_URL =
'https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt'
const MIHOMO_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`
let MIHOMO_VERSION
const MIHOMO_MAP = {
'win32-x64': 'mihomo-windows-amd64-compatible',
'win32-ia32': 'mihomo-windows-386',
'win32-arm64': 'mihomo-windows-arm64',
'darwin-x64': 'mihomo-darwin-amd64-compatible',
'darwin-arm64': 'mihomo-darwin-arm64',
'linux-x64': 'mihomo-linux-amd64-compatible',
'linux-arm64': 'mihomo-linux-arm64'
}
// Fetch the latest release version from the version.txt file
async function getLatestReleaseVersion() {
try {
const response = await fetch(MIHOMO_VERSION_URL, {
method: 'GET'
})
let v = await response.text()
MIHOMO_VERSION = v.trim() // Trim to remove extra whitespaces
console.log(`Latest release version: ${MIHOMO_VERSION}`)
} catch (error) {
console.error('Error fetching latest release version:', error.message)
process.exit(1)
}
}
/*
* check available
*/
if (!MIHOMO_MAP[`${platform}-${arch}`]) {
throw new Error(`unsupported platform "${platform}-${arch}"`)
}
if (!MIHOMO_ALPHA_MAP[`${platform}-${arch}`]) {
throw new Error(`unsupported platform "${platform}-${arch}"`)
}
/**
* core info
*/
function MihomoAlpha() {
const name = MIHOMO_ALPHA_MAP[`${platform}-${arch}`]
const isWin = platform === 'win32'
const urlExt = isWin ? 'zip' : 'gz'
const downloadURL = `${MIHOMO_ALPHA_URL_PREFIX}/${name}-${MIHOMO_ALPHA_VERSION}.${urlExt}`
const exeFile = `${name}${isWin ? '.exe' : ''}`
const zipFile = `${name}-${MIHOMO_ALPHA_VERSION}.${urlExt}`
return {
name: 'mihomo-alpha',
targetFile: `mihomo-alpha${isWin ? '.exe' : ''}`,
exeFile,
zipFile,
downloadURL
}
}
function mihomo() {
const name = MIHOMO_MAP[`${platform}-${arch}`]
const isWin = platform === 'win32'
const urlExt = isWin ? 'zip' : 'gz'
const downloadURL = `${MIHOMO_URL_PREFIX}/${MIHOMO_VERSION}/${name}-${MIHOMO_VERSION}.${urlExt}`
const exeFile = `${name}${isWin ? '.exe' : ''}`
const zipFile = `${name}-${MIHOMO_VERSION}.${urlExt}`
return {
name: 'mihomo',
targetFile: `mihomo${isWin ? '.exe' : ''}`,
exeFile,
zipFile,
downloadURL
}
}
/**
* download sidecar and rename
*/
async function resolveSidecar(binInfo) {
const { name, targetFile, zipFile, exeFile, downloadURL } = binInfo
2024-08-17 12:04:05 +08:00
const sidecarDir = path.join(cwd, 'extra', 'sidecar')
2024-07-31 21:04:30 +08:00
const sidecarPath = path.join(sidecarDir, targetFile)
fs.mkdirSync(sidecarDir, { recursive: true })
if (fs.existsSync(sidecarPath)) {
fs.rmSync(sidecarPath)
}
const tempDir = path.join(TEMP_DIR, name)
const tempZip = path.join(tempDir, zipFile)
const tempExe = path.join(tempDir, exeFile)
fs.mkdirSync(tempDir, { recursive: true })
try {
if (!fs.existsSync(tempZip)) {
await downloadFile(downloadURL, tempZip)
}
if (zipFile.endsWith('.zip')) {
const zip = new AdmZip(tempZip)
zip.getEntries().forEach((entry) => {
console.log(`[DEBUG]: "${name}" entry name`, entry.entryName)
})
zip.extractAllTo(tempDir, true)
fs.renameSync(tempExe, sidecarPath)
console.log(`[INFO]: "${name}" unzip finished`)
} else if (zipFile.endsWith('.tgz')) {
// tgz
fs.mkdirSync(tempDir, { recursive: true })
await extract({
cwd: tempDir,
file: tempZip
})
const files = fs.readdirSync(tempDir)
console.log(`[DEBUG]: "${name}" files in tempDir:`, files)
const extractedFile = files.find((file) => file.startsWith('虚空终端-'))
if (extractedFile) {
const extractedFilePath = path.join(tempDir, extractedFile)
fs.renameSync(extractedFilePath, sidecarPath)
console.log(`[INFO]: "${name}" file renamed to "${sidecarPath}"`)
execSync(`chmod 755 ${sidecarPath}`)
console.log(`[INFO]: "${name}" chmod binary finished`)
} else {
throw new Error(`Expected file not found in ${tempDir}`)
}
} else {
// gz
const readStream = fs.createReadStream(tempZip)
const writeStream = fs.createWriteStream(sidecarPath)
await new Promise((resolve, reject) => {
const onError = (error) => {
console.error(`[ERROR]: "${name}" gz failed:`, error.message)
reject(error)
}
readStream
.pipe(zlib.createGunzip().on('error', onError))
.pipe(writeStream)
.on('finish', () => {
console.log(`[INFO]: "${name}" gunzip finished`)
execSync(`chmod 755 ${sidecarPath}`)
console.log(`[INFO]: "${name}" chmod binary finished`)
resolve()
})
.on('error', onError)
})
}
} catch (err) {
// 需要删除文件
fs.rmSync(sidecarPath)
throw err
} finally {
fs.rmSync(tempDir, { recursive: true })
}
}
/**
2024-08-17 12:04:05 +08:00
* download the file to the extra dir
2024-07-31 21:04:30 +08:00
*/
async function resolveResource(binInfo) {
const { file, downloadURL } = binInfo
2024-08-17 12:04:05 +08:00
const resDir = path.join(cwd, 'extra', 'files')
2024-07-31 21:04:30 +08:00
const targetPath = path.join(resDir, file)
if (fs.existsSync(targetPath)) {
fs.rmSync(targetPath)
}
fs.mkdirSync(resDir, { recursive: true })
await downloadFile(downloadURL, targetPath)
console.log(`[INFO]: ${file} finished`)
}
/**
* download file and save to `path`
*/
async function downloadFile(url, path) {
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/octet-stream' }
})
const buffer = await response.arrayBuffer()
fs.writeFileSync(path, new Uint8Array(buffer))
console.log(`[INFO]: download finished "${url}"`)
}
const resolveMmdb = () =>
resolveResource({
2024-08-08 19:30:33 +08:00
file: 'country.mmdb',
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb`
2024-07-31 21:04:30 +08:00
})
const resolveGeosite = () =>
resolveResource({
file: 'geosite.dat',
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat`
})
const resolveGeoIP = () =>
resolveResource({
file: 'geoip.dat',
2024-08-08 19:30:33 +08:00
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat`
})
const resolveASN = () =>
resolveResource({
file: 'ASN.mmdb',
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb`
2024-07-31 21:04:30 +08:00
})
const resolveEnableLoopback = () =>
resolveResource({
file: 'enableLoopback.exe',
downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`
})
2024-08-23 11:29:45 +08:00
const resolveSysproxy = () =>
resolveResource({
file: 'sysproxy.exe',
2024-09-13 20:24:52 +08:00
downloadURL: `https://github.com/mihomo-party-org/sysproxy/releases/download/${arch}/sysproxy.exe`
2024-08-23 11:29:45 +08:00
})
2024-08-29 17:32:42 +08:00
const resolveRunner = () =>
resolveResource({
file: 'mihomo-party-run.exe',
2024-09-13 20:24:52 +08:00
downloadURL: `https://github.com/mihomo-party-org/mihomo-party-run/releases/download/${arch}/mihomo-party-run.exe`
2024-08-29 17:32:42 +08:00
})
2024-09-28 17:36:40 +08:00
const resolveMonitor = async () => {
const tempDir = path.join(TEMP_DIR, 'TrafficMonitor')
const tempZip = path.join(tempDir, `${arch}.zip`)
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true })
}
await downloadFile(
`https://github.com/mihomo-party-org/mihomo-party-run/releases/download/monitor/${arch}.zip`,
tempZip
)
const zip = new AdmZip(tempZip)
const resDir = path.join(cwd, 'extra', 'files')
const targetPath = path.join(resDir, 'TrafficMonitor')
if (fs.existsSync(targetPath)) {
fs.rmSync(targetPath, { recursive: true })
}
2024-09-30 10:44:11 +08:00
zip.extractAllTo(targetPath, true)
2024-09-28 17:36:40 +08:00
console.log(`[INFO]: TrafficMonitor finished`)
}
2024-09-18 22:35:16 +08:00
const resolve7zip = () =>
resolveResource({
file: '7za.exe',
downloadURL: `https://github.com/develar/7zip-bin/raw/master/win/${arch}/7za.exe`
})
2024-09-04 00:03:42 +08:00
const resolveSubstore = () =>
resolveResource({
file: 'sub-store.bundle.js',
downloadURL:
'https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.bundle.js'
})
2024-09-05 10:49:38 +08:00
const resolveSubstoreFrontend = async () => {
const tempDir = path.join(TEMP_DIR, 'substore-frontend')
const tempZip = path.join(tempDir, 'dist.zip')
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true })
}
await downloadFile(
'https://github.com/sub-store-org/Sub-Store-Front-End/releases/latest/download/dist.zip',
tempZip
)
const zip = new AdmZip(tempZip)
const resDir = path.join(cwd, 'extra', 'files')
const targetPath = path.join(resDir, 'sub-store-frontend')
if (fs.existsSync(targetPath)) {
fs.rmSync(targetPath, { recursive: true })
}
zip.extractAllTo(resDir, true)
fs.renameSync(path.join(resDir, 'dist'), targetPath)
console.log(`[INFO]: sub-store-frontend finished`)
}
2024-08-08 16:06:48 +08:00
const resolveFont = async () => {
const targetPath = path.join(cwd, 'src', 'renderer', 'src', 'assets', 'NotoColorEmoji.ttf')
if (fs.existsSync(targetPath)) {
return
}
await downloadFile(
'https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf',
targetPath
)
console.log(`[INFO]: NotoColorEmoji.ttf finished`)
}
2024-07-31 21:04:30 +08:00
const tasks = [
{
2024-08-15 08:57:45 +08:00
name: 'mihomo-alpha',
2024-07-31 21:04:30 +08:00
func: () => getLatestAlphaVersion().then(() => resolveSidecar(MihomoAlpha())),
retry: 5
},
{
2024-08-15 08:57:45 +08:00
name: 'mihomo',
2024-07-31 21:04:30 +08:00
func: () => getLatestReleaseVersion().then(() => resolveSidecar(mihomo())),
retry: 5
},
{ name: 'mmdb', func: resolveMmdb, retry: 5 },
{ name: 'geosite', func: resolveGeosite, retry: 5 },
{ name: 'geoip', func: resolveGeoIP, retry: 5 },
2024-08-08 19:30:33 +08:00
{ name: 'asn', func: resolveASN, retry: 5 },
2024-08-08 16:06:48 +08:00
{
name: 'font',
func: resolveFont,
retry: 5
},
2024-07-31 21:04:30 +08:00
{
name: 'enableLoopback',
func: resolveEnableLoopback,
retry: 5,
winOnly: true
2024-08-23 11:29:45 +08:00
},
{
name: 'sysproxy',
func: resolveSysproxy,
retry: 5,
winOnly: true
2024-08-29 17:32:42 +08:00
},
{
name: 'runner',
func: resolveRunner,
retry: 5,
winOnly: true
2024-09-04 00:03:42 +08:00
},
2024-09-28 17:36:40 +08:00
{
name: 'monitor',
func: resolveMonitor,
retry: 5,
winOnly: true
},
2024-09-04 00:03:42 +08:00
{
name: 'substore',
func: resolveSubstore,
retry: 5
2024-09-05 10:49:38 +08:00
},
{
name: 'substorefrontend',
func: resolveSubstoreFrontend,
retry: 5
2024-09-18 22:35:16 +08:00
},
{
name: '7zip',
func: resolve7zip,
retry: 5,
winOnly: true
2024-07-31 21:04:30 +08:00
}
]
async function runTask() {
const task = tasks.shift()
if (!task) return
if (task.winOnly && platform !== 'win32') return runTask()
if (task.linuxOnly && platform !== 'linux') return runTask()
if (task.unixOnly && platform === 'win32') return runTask()
for (let i = 0; i < task.retry; i++) {
try {
await task.func()
break
} catch (err) {
console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message)
if (i === task.retry - 1) throw err
}
}
return runTask()
}
runTask()
runTask()