Files
core/src/modules/update/update.service.ts
f57b23e515 feat: auto download admin (#1005
* feat: auto download admin

Signed-off-by: Innei <tukon479@gmail.com>

* fix: update

Signed-off-by: Innei <tukon479@gmail.com>

* fix: update

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2023-03-08 11:40:42 +08:00

153 lines
4.4 KiB
TypeScript

import axios from 'axios'
import { appendFile, rm, writeFile } from 'fs/promises'
import { spawn } from 'node-pty'
import { Observable, Subscriber, catchError } from 'rxjs'
import { inspect } from 'util'
import { Injectable } from '@nestjs/common'
import { dashboard } from '~/../package.json'
import { LOCAL_ADMIN_ASSET_PATH } from '~/constants/path.constant'
import { HttpService } from '~/processors/helper/helper.http.service'
const { repo } = dashboard
@Injectable()
export class UpdateService {
constructor(protected readonly httpService: HttpService) {}
downloadAdminAsset(version: string) {
const observable$ = new Observable<string>((subscriber) => {
;(async () => {
const endpoint = `https://api.github.com/repos/${repo}/releases/tags/v${version}`
subscriber.next(`Geting release info from ${endpoint}.\n`)
const json = await fetch(endpoint)
.then((res) => res.json())
.catch((err) => {
subscriber.next(chalk.red(`Fetching error: ${err.message}`))
subscriber.complete()
return null
})
if (!json) {
subscriber.next(chalk.red('Fetching error, json is empty. \n'))
subscriber.complete()
return
}
const downloadUrl = json.assets?.find(
(asset) => asset.name === 'release.zip',
)?.browser_download_url
if (!downloadUrl) {
subscriber.next(chalk.red('Download url not found.\n'))
subscriber.next(
chalk.red(
`Full json fetched: \n${inspect(json, false, undefined, true)}`,
),
)
subscriber.complete()
return
}
const cdnDownloadUrl = `https://ghproxy.com/${downloadUrl}`
subscriber.next(
`Downloading admin asset v${version}\nFrom: ${cdnDownloadUrl}\n`,
)
const buffer = await axios
.get(cdnDownloadUrl, {
responseType: 'arraybuffer',
})
.then((res) => res.data as ArrayBuffer)
.catch((err) => {
subscriber.next(chalk.red(`Downloading error: ${err.message}`))
subscriber.complete()
return null
})
if (!buffer) {
return
}
await rm('admin-release.zip', { force: true })
await appendFile(
path.resolve(process.cwd(), 'admin-release.zip'),
Buffer.from(buffer),
)
const folder = LOCAL_ADMIN_ASSET_PATH.replace(/\/admin$/, '')
await rm(LOCAL_ADMIN_ASSET_PATH, { force: true, recursive: true })
try {
// @ts-ignore
const cmds: readonly [string, string[]][] = [
`unzip -o admin-release.zip -d ${folder}`,
`mv ${folder}/dist ${LOCAL_ADMIN_ASSET_PATH}`,
`rm -f admin-release.zip`,
// @ts-ignore
].reduce((acc, fullCmd) => {
const [cmd, ...args] = fullCmd.split(' ')
return [...acc, [cmd, args]] as const
}, [])
for (const exec of cmds) {
const [cmd, args] = exec
await this.runShellCommandPipeOutput(cmd, args, subscriber)
}
await writeFile(
path.resolve(LOCAL_ADMIN_ASSET_PATH, 'version'),
version,
{
encoding: 'utf8',
},
)
subscriber.next(chalk.green(`Downloading finished.\n`))
} catch (err) {
subscriber.next(chalk.red(`Updating error: ${err.message}\n`))
} finally {
subscriber.complete()
}
await rm('admin-release.zip', { force: true })
})()
})
return observable$.pipe(
catchError((err) => {
console.error(err)
return observable$
}),
)
}
async getLatestAdminVersion() {
const endpoint = `https://api.github.com/repos/${repo}/releases/latest`
const res = await this.httpService.axiosRef.get(endpoint)
return res.data.tag_name.replace(/^v/, '')
}
private runShellCommandPipeOutput(
command: string,
args: any[],
subscriber: Subscriber<string>,
) {
return new Promise((resolve) => {
subscriber.next(`${chalk.yellow(`$`)} ${command} ${args.join(' ')}\n`)
const pty = spawn(command, args, {})
pty.onData((data) => {
subscriber.next(data.toString())
})
pty.onExit(() => {
resolve(null)
})
})
}
}