mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 10:34:18 +00:00
improve installations steps & support progress
This commit is contained in:
parent
8464620ecf
commit
5b4d81b4c7
@ -25,6 +25,7 @@
|
|||||||
"electron-differential-updater": "^4.3.2",
|
"electron-differential-updater": "^4.3.2",
|
||||||
"electron-updater": "^6.1.1",
|
"electron-updater": "^6.1.1",
|
||||||
"got": "11.8.3",
|
"got": "11.8.3",
|
||||||
|
"human-format": "^1.2.0",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-7z": "^3.0.0",
|
"node-7z": "^3.0.0",
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
|
import lodash from "lodash"
|
||||||
|
|
||||||
|
global.sendToRenderer = (event, data) => {
|
||||||
|
function serializeIpc(data) {
|
||||||
|
const copy = lodash.cloneDeep(data)
|
||||||
|
|
||||||
|
// remove fns
|
||||||
|
if (!Array.isArray(copy)) {
|
||||||
|
Object.keys(copy).forEach((key) => {
|
||||||
|
if (typeof copy[key] === "function") {
|
||||||
|
delete copy[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
global.win.webContents.send(event, serializeIpc(data))
|
||||||
|
}
|
||||||
|
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
|
|
||||||
import { app, shell, BrowserWindow, ipcMain } from "electron"
|
import { app, shell, BrowserWindow, ipcMain } from "electron"
|
||||||
@ -11,8 +32,8 @@ import pkg from "../../package.json"
|
|||||||
|
|
||||||
import setup from "./setup"
|
import setup from "./setup"
|
||||||
|
|
||||||
import PkgManager from "./pkgManager"
|
import PkgManager from "./pkg_mng"
|
||||||
import { readManifest } from "./pkgManager"
|
import { readManifest } from "./utils/readManifest"
|
||||||
|
|
||||||
class ElectronApp {
|
class ElectronApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,655 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
import { pipeline as streamPipeline } from "node:stream/promises"
|
|
||||||
import ChildProcess from "node:child_process"
|
|
||||||
import fs from "node:fs"
|
|
||||||
import os from "node:os"
|
|
||||||
|
|
||||||
import open from "open"
|
|
||||||
import got from "got"
|
|
||||||
import { extractFull } from "node-7z"
|
|
||||||
import { rimraf } from "rimraf"
|
|
||||||
import lodash from "lodash"
|
|
||||||
|
|
||||||
import pkg from "../../package.json"
|
|
||||||
|
|
||||||
global.OS_USERDATA_PATH = path.resolve(
|
|
||||||
process.env.APPDATA ||
|
|
||||||
(process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.HOME + "/.local/share"),
|
|
||||||
)
|
|
||||||
|
|
||||||
global.RUNTIME_PATH = path.join(global.OS_USERDATA_PATH, "rs-bundler")
|
|
||||||
|
|
||||||
const TMP_PATH = path.resolve(os.tmpdir(), "RS-MCPacks")
|
|
||||||
const INSTALLERS_PATH = path.join(global.RUNTIME_PATH, "installers")
|
|
||||||
const MANIFEST_PATH = path.join(global.RUNTIME_PATH, "manifests")
|
|
||||||
|
|
||||||
const RealmDBDefault = {
|
|
||||||
created_at_version: pkg.version,
|
|
||||||
installations: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeIpc(data) {
|
|
||||||
const copy = lodash.cloneDeep(data)
|
|
||||||
|
|
||||||
// remove fns
|
|
||||||
if (!Array.isArray(copy)) {
|
|
||||||
Object.keys(copy).forEach((key) => {
|
|
||||||
if (typeof copy[key] === "function") {
|
|
||||||
delete copy[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendToRenderer(event, data) {
|
|
||||||
global.win.webContents.send(event, serializeIpc(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchAndCreateModule(manifest) {
|
|
||||||
console.log(`Fetching ${manifest}...`)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await got.get(manifest)
|
|
||||||
const moduleCode = response.body
|
|
||||||
|
|
||||||
const newModule = new module.constructor()
|
|
||||||
newModule._compile(moduleCode, manifest)
|
|
||||||
|
|
||||||
return newModule
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readManifest(manifest, { just_read = false } = {}) {
|
|
||||||
// check if manifest is a directory or a url
|
|
||||||
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi
|
|
||||||
|
|
||||||
if (urlRegex.test(manifest)) {
|
|
||||||
const _module = await fetchAndCreateModule(manifest)
|
|
||||||
const remoteUrl = lodash.clone(manifest)
|
|
||||||
|
|
||||||
manifest = _module.exports
|
|
||||||
|
|
||||||
manifest.remote_url = remoteUrl
|
|
||||||
} else {
|
|
||||||
if (!fs.existsSync(manifest)) {
|
|
||||||
throw new Error(`Manifest not found: ${manifest}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.statSync(manifest).isFile()) {
|
|
||||||
throw new Error(`Manifest is not a file: ${manifest}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifestFilePath = lodash.clone(manifest)
|
|
||||||
|
|
||||||
manifest = require(manifest)
|
|
||||||
|
|
||||||
if (!just_read) {
|
|
||||||
// copy manifest
|
|
||||||
fs.copyFileSync(manifestFilePath, path.resolve(MANIFEST_PATH, path.basename(manifest)))
|
|
||||||
|
|
||||||
manifest.remote_url = manifestFilePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return manifest
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PkgManager {
|
|
||||||
constructor() {
|
|
||||||
this.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
get realmDbPath() {
|
|
||||||
return path.join(RUNTIME_PATH, "local_realm.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
get runtimePath() {
|
|
||||||
return RUNTIME_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
if (!fs.existsSync(RUNTIME_PATH)) {
|
|
||||||
fs.mkdirSync(RUNTIME_PATH, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(INSTALLERS_PATH)) {
|
|
||||||
fs.mkdirSync(INSTALLERS_PATH, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(MANIFEST_PATH)) {
|
|
||||||
fs.mkdirSync(MANIFEST_PATH, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(TMP_PATH)) {
|
|
||||||
fs.mkdirSync(TMP_PATH, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(this.realmDbPath)) {
|
|
||||||
console.log(`Creating default realm db...`, this.realmDbPath)
|
|
||||||
|
|
||||||
await this.writeDb(RealmDBDefault)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DB Operations
|
|
||||||
async readDb() {
|
|
||||||
return JSON.parse(await fs.promises.readFile(this.realmDbPath, "utf8"))
|
|
||||||
}
|
|
||||||
|
|
||||||
async writeDb(data) {
|
|
||||||
return fs.promises.writeFile(this.realmDbPath, JSON.stringify(data, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
async appendInstallation(manifest) {
|
|
||||||
const db = await this.readDb()
|
|
||||||
|
|
||||||
const prevIndex = db.installations.findIndex((i) => i.id === manifest.id)
|
|
||||||
|
|
||||||
if (prevIndex !== -1) {
|
|
||||||
db.installations[prevIndex] = manifest
|
|
||||||
} else {
|
|
||||||
db.installations.push(manifest)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.writeDb(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CRUD Operations
|
|
||||||
async getInstallations() {
|
|
||||||
const db = await this.readDb()
|
|
||||||
|
|
||||||
return db.installations
|
|
||||||
}
|
|
||||||
|
|
||||||
async openBundleFolder(manifest_id) {
|
|
||||||
const db = await this.readDb()
|
|
||||||
|
|
||||||
const index = db.installations.findIndex((i) => i.id === manifest_id)
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
const manifest = db.installations[index]
|
|
||||||
|
|
||||||
open(manifest.install_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async initManifest(manifest = {}) {
|
|
||||||
const packPath = path.resolve(INSTALLERS_PATH, manifest.id)
|
|
||||||
|
|
||||||
const osString = `${os.platform()}-${os.arch()}`
|
|
||||||
|
|
||||||
if (typeof manifest.init === "function") {
|
|
||||||
const init_result = await manifest.init({
|
|
||||||
pack_dir: packPath,
|
|
||||||
tmp_dir: TMP_PATH,
|
|
||||||
os_string: osString,
|
|
||||||
})
|
|
||||||
|
|
||||||
manifest = {
|
|
||||||
...manifest,
|
|
||||||
...init_result,
|
|
||||||
}
|
|
||||||
|
|
||||||
delete manifest.init
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...manifest,
|
|
||||||
packPath: packPath,
|
|
||||||
osString: osString,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async install(manifest) {
|
|
||||||
try {
|
|
||||||
let pendingTasks = []
|
|
||||||
|
|
||||||
manifest = await readManifest(manifest).catch((error) => {
|
|
||||||
sendToRenderer("runtime:error", "Cannot fetch this manifest")
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!manifest) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest = await this.initManifest(manifest)
|
|
||||||
|
|
||||||
manifest.status = "installing"
|
|
||||||
|
|
||||||
console.log(`Starting to install ${manifest.pack_name}...`)
|
|
||||||
console.log(`Installing at >`, manifest.packPath)
|
|
||||||
|
|
||||||
sendToRenderer("new:installation", manifest)
|
|
||||||
|
|
||||||
fs.mkdirSync(manifest.packPath, { recursive: true })
|
|
||||||
|
|
||||||
await this.appendInstallation(manifest)
|
|
||||||
|
|
||||||
if (typeof manifest.on_install === "function") {
|
|
||||||
await manifest.on_install({
|
|
||||||
manifest: manifest,
|
|
||||||
pack_dir: manifest.packPath,
|
|
||||||
tmp_dir: TMP_PATH,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.git_clones_steps !== "undefined" && Array.isArray(manifest.git_clones_steps)) {
|
|
||||||
for await (const step of manifest.git_clones_steps) {
|
|
||||||
const _path = path.resolve(manifest.packPath, step.path)
|
|
||||||
|
|
||||||
console.log(`Cloning ${step.url}...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Cloning ${step.url}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
fs.mkdirSync(_path, { recursive: true })
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const process = ChildProcess.exec(`${global.GIT_PATH ?? "git"} clone --recurse-submodules --remote-submodules ${step.url} ${_path}`, {
|
|
||||||
shell: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on("exit", resolve)
|
|
||||||
process.on("error", reject)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.http_downloads !== "undefined" && Array.isArray(manifest.http_downloads)) {
|
|
||||||
for await (const step of manifest.http_downloads) {
|
|
||||||
let _path = path.resolve(manifest.packPath, step.path ?? ".")
|
|
||||||
|
|
||||||
console.log(`Downloading ${step.url}...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Downloading ${step.url}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (step.tmp) {
|
|
||||||
_path = path.resolve(TMP_PATH, String(new Date().getTime()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(path.resolve(_path, ".."), { recursive: true })
|
|
||||||
|
|
||||||
await streamPipeline(
|
|
||||||
got.stream(step.url),
|
|
||||||
fs.createWriteStream(_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (step.execute) {
|
|
||||||
pendingTasks.push(async () => {
|
|
||||||
await new Promise(async (resolve, reject) => {
|
|
||||||
const process = ChildProcess.execFile(_path, {
|
|
||||||
shell: true,
|
|
||||||
}, (error) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on("exit", resolve)
|
|
||||||
process.on("error", reject)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step.extract) {
|
|
||||||
console.log(`Extracting ${step.extract}...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Extracting bundle ${step.extract}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const extract = extractFull(_path, step.extract, {
|
|
||||||
$bin: global.SEVENZIP_PATH
|
|
||||||
})
|
|
||||||
|
|
||||||
extract.on("error", reject)
|
|
||||||
extract.on("end", resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingTasks.length > 0) {
|
|
||||||
console.log(`Performing pending tasks...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Performing pending tasks...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
for await (const task of pendingTasks) {
|
|
||||||
await task()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.after_install === "function") {
|
|
||||||
console.log(`Performing after_install hook...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Performing after_install hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.after_install({
|
|
||||||
manifest,
|
|
||||||
pack_dir: manifest.packPath,
|
|
||||||
tmp_dir: TMP_PATH
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.status = "installed"
|
|
||||||
manifest.install_path = manifest.packPath
|
|
||||||
manifest.installed_at = new Date()
|
|
||||||
manifest.last_update = null
|
|
||||||
|
|
||||||
await this.appendInstallation(manifest)
|
|
||||||
|
|
||||||
console.log(`Successfully installed ${manifest.pack_name}!`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:done`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: "Successfully installed",
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
manifest.status = "failed"
|
|
||||||
|
|
||||||
sendToRenderer(`installation:error`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: error.toString(),
|
|
||||||
})
|
|
||||||
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
fs.rmdirSync(manifest.packPath, { recursive: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async uninstall(manifest_id) {
|
|
||||||
console.log(`Uninstalling >`, manifest_id)
|
|
||||||
|
|
||||||
sendToRenderer("installation:status", {
|
|
||||||
status: "uninstalling",
|
|
||||||
id: manifest_id,
|
|
||||||
statusText: `Uninstalling ${manifest_id}...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const db = await this.readDb()
|
|
||||||
|
|
||||||
const manifest = db.installations.find((i) => i.id === manifest_id)
|
|
||||||
|
|
||||||
if (!manifest) {
|
|
||||||
sendToRenderer("runtime:error", "Manifest not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manifest.remote_url) {
|
|
||||||
const remoteManifest = await readManifest(manifest.remote_url, { just_read: true })
|
|
||||||
|
|
||||||
if (typeof remoteManifest.uninstall === "function") {
|
|
||||||
console.log(`Performing uninstall hook...`)
|
|
||||||
|
|
||||||
await remoteManifest.uninstall({
|
|
||||||
manifest: remoteManifest,
|
|
||||||
pack_dir: remoteManifest.install_path,
|
|
||||||
tmp_dir: TMP_PATH,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await rimraf(manifest.install_path)
|
|
||||||
|
|
||||||
db.installations = db.installations.filter((i) => i.id !== manifest_id)
|
|
||||||
|
|
||||||
await this.writeDb(db)
|
|
||||||
|
|
||||||
sendToRenderer("installation:uninstalled", {
|
|
||||||
id: manifest_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(manifest_id) {
|
|
||||||
try {
|
|
||||||
let pendingTasks = []
|
|
||||||
|
|
||||||
console.log(`Updating >`, manifest_id)
|
|
||||||
|
|
||||||
sendToRenderer("installation:status", {
|
|
||||||
status: "updating",
|
|
||||||
id: manifest_id,
|
|
||||||
statusText: `Updating ${manifest_id}...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const db = await this.readDb()
|
|
||||||
|
|
||||||
let manifest = db.installations.find((i) => i.id === manifest_id)
|
|
||||||
|
|
||||||
if (!manifest) {
|
|
||||||
sendToRenderer("runtime:error", "Manifest not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const packPath = manifest.install_path
|
|
||||||
|
|
||||||
if (manifest.remote_url) {
|
|
||||||
manifest = await readManifest(manifest.remote_url, { just_read: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.status = "updating"
|
|
||||||
|
|
||||||
manifest = await this.initManifest(manifest)
|
|
||||||
|
|
||||||
if (typeof manifest.update === "function") {
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Performing update hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Performing update hook...`)
|
|
||||||
|
|
||||||
await manifest.update({
|
|
||||||
manifest,
|
|
||||||
pack_dir: packPath,
|
|
||||||
tmp_dir: TMP_PATH
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.git_update !== "undefined" && Array.isArray(manifest.git_update)) {
|
|
||||||
for await (const step of manifest.git_update) {
|
|
||||||
const _path = path.resolve(packPath, step.path)
|
|
||||||
|
|
||||||
console.log(`GIT Pulling ${step.url}`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `GIT Pulling ${step.url}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const process = ChildProcess.exec(`${global.GIT_PATH ?? "git"} pull`, {
|
|
||||||
cwd: _path,
|
|
||||||
shell: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on("exit", resolve)
|
|
||||||
process.on("error", reject)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.http_downloads !== "undefined" && Array.isArray(manifest.http_downloads)) {
|
|
||||||
for await (const step of manifest.http_downloads) {
|
|
||||||
let _path = path.resolve(packPath, step.path ?? ".")
|
|
||||||
|
|
||||||
console.log(`Downloading ${step.url}...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Downloading ${step.url}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (step.tmp) {
|
|
||||||
_path = path.resolve(TMP_PATH, String(new Date().getTime()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(path.resolve(_path, ".."), { recursive: true })
|
|
||||||
|
|
||||||
await streamPipeline(
|
|
||||||
got.stream(step.url),
|
|
||||||
fs.createWriteStream(_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (step.execute) {
|
|
||||||
pendingTasks.push(async () => {
|
|
||||||
await new Promise(async (resolve, reject) => {
|
|
||||||
const process = ChildProcess.execFile(_path, {
|
|
||||||
shell: true,
|
|
||||||
}, (error) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on("exit", resolve)
|
|
||||||
process.on("error", reject)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step.extract) {
|
|
||||||
console.log(`Extracting ${step.extract}...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Extracting bundle ${step.extract}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const extract = extractFull(_path, step.extract, {
|
|
||||||
$bin: global.SEVENZIP_PATH
|
|
||||||
})
|
|
||||||
|
|
||||||
extract.on("error", reject)
|
|
||||||
extract.on("end", resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingTasks.length > 0) {
|
|
||||||
console.log(`Performing pending tasks...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Performing pending tasks...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
for await (const task of pendingTasks) {
|
|
||||||
await task()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.after_install === "function") {
|
|
||||||
console.log(`Performing after_install hook...`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:status`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: `Performing after_install hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.after_install({
|
|
||||||
manifest,
|
|
||||||
pack_dir: packPath,
|
|
||||||
tmp_dir: TMP_PATH
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.status = "installed"
|
|
||||||
manifest.install_path = packPath
|
|
||||||
manifest.last_update = new Date()
|
|
||||||
|
|
||||||
await this.appendInstallation(manifest)
|
|
||||||
|
|
||||||
console.log(`Successfully updated ${manifest.pack_name}!`)
|
|
||||||
|
|
||||||
sendToRenderer(`installation:done`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: "Successfully updated",
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
manifest.status = "failed"
|
|
||||||
|
|
||||||
sendToRenderer(`installation:error`, {
|
|
||||||
...manifest,
|
|
||||||
statusText: error.toString(),
|
|
||||||
})
|
|
||||||
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(manifest_id) {
|
|
||||||
console.log(`Executing ${manifest_id}...`)
|
|
||||||
|
|
||||||
sendToRenderer("installation:status", {
|
|
||||||
status: "starting",
|
|
||||||
id: manifest_id,
|
|
||||||
statusText: `Executing ${manifest_id}...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const db = await this.readDb()
|
|
||||||
|
|
||||||
let manifest = db.installations.find((i) => i.id === manifest_id)
|
|
||||||
|
|
||||||
if (!manifest) {
|
|
||||||
sendToRenderer("runtime:error", "Manifest not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manifest.remote_url) {
|
|
||||||
manifest = await readManifest(manifest.remote_url, { just_read: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest = await this.initManifest(manifest)
|
|
||||||
|
|
||||||
if (typeof manifest.execute !== "function") {
|
|
||||||
sendToRenderer("installation:status", {
|
|
||||||
status: "execution_failed",
|
|
||||||
...manifest,
|
|
||||||
})
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
await manifest.execute({
|
|
||||||
manifest,
|
|
||||||
pack_dir: manifest.packPath,
|
|
||||||
tmp_dir: TMP_PATH
|
|
||||||
})
|
|
||||||
|
|
||||||
sendToRenderer("installation:status", {
|
|
||||||
status: "installed",
|
|
||||||
...manifest,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Successfully executed ${manifest_id}!`)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
418
src/main/pkg_mng/index.js
Normal file
418
src/main/pkg_mng/index.js
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
import os from "node:os"
|
||||||
|
|
||||||
|
global.OS_USERDATA_PATH = path.resolve(
|
||||||
|
process.env.APPDATA ||
|
||||||
|
(process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.HOME + "/.local/share"),
|
||||||
|
)
|
||||||
|
global.RUNTIME_PATH = path.join(global.OS_USERDATA_PATH, "rs-bundler")
|
||||||
|
global.TMP_PATH = path.resolve(os.tmpdir(), "rs-bundler")
|
||||||
|
global.INSTALLERS_PATH = path.join(global.RUNTIME_PATH, "installations")
|
||||||
|
global.MANIFEST_PATH = path.join(global.RUNTIME_PATH, "manifests")
|
||||||
|
|
||||||
|
import path from "node:path"
|
||||||
|
import fs from "node:fs"
|
||||||
|
|
||||||
|
import open from "open"
|
||||||
|
import { rimraf } from "rimraf"
|
||||||
|
|
||||||
|
import readManifest from "../utils/readManifest"
|
||||||
|
import initManifest from "../utils/initManifest"
|
||||||
|
|
||||||
|
import ISM_HTTP from "./installs_steps_methods/http"
|
||||||
|
import ISM_GIT from "./installs_steps_methods/git"
|
||||||
|
|
||||||
|
import pkg from "../../../package.json"
|
||||||
|
|
||||||
|
const RealmDBDefault = {
|
||||||
|
created_at_version: pkg.version,
|
||||||
|
installations: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const InstallationStepsMethods = {
|
||||||
|
http: ISM_HTTP,
|
||||||
|
git: ISM_GIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PkgManager {
|
||||||
|
constructor() {
|
||||||
|
this.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
get realmDbPath() {
|
||||||
|
return path.join(RUNTIME_PATH, "local_realm.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
get runtimePath() {
|
||||||
|
return RUNTIME_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
if (!fs.existsSync(RUNTIME_PATH)) {
|
||||||
|
fs.mkdirSync(RUNTIME_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(INSTALLERS_PATH)) {
|
||||||
|
fs.mkdirSync(INSTALLERS_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(MANIFEST_PATH)) {
|
||||||
|
fs.mkdirSync(MANIFEST_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(TMP_PATH)) {
|
||||||
|
fs.mkdirSync(TMP_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.realmDbPath)) {
|
||||||
|
console.log(`Creating default realm db...`, this.realmDbPath)
|
||||||
|
|
||||||
|
await this.writeDb(RealmDBDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB Operations
|
||||||
|
async readDb() {
|
||||||
|
return JSON.parse(await fs.promises.readFile(this.realmDbPath, "utf8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeDb(data) {
|
||||||
|
return fs.promises.writeFile(this.realmDbPath, JSON.stringify(data, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
async appendInstallation(manifest) {
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
const prevIndex = db.installations.findIndex((i) => i.id === manifest.id)
|
||||||
|
|
||||||
|
if (prevIndex !== -1) {
|
||||||
|
db.installations[prevIndex] = manifest
|
||||||
|
} else {
|
||||||
|
db.installations.push(manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.writeDb(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRUD Operations
|
||||||
|
async getInstallations() {
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
return db.installations
|
||||||
|
}
|
||||||
|
|
||||||
|
async openBundleFolder(manifest_id) {
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
const index = db.installations.findIndex((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
const manifest = db.installations[index]
|
||||||
|
|
||||||
|
open(manifest.install_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(manifest) {
|
||||||
|
try {
|
||||||
|
let pendingTasks = []
|
||||||
|
|
||||||
|
manifest = await readManifest(manifest).catch((error) => {
|
||||||
|
global.sendToRenderer("runtime:error", "Cannot fetch this manifest")
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest = await initManifest(manifest)
|
||||||
|
|
||||||
|
manifest.status = "installing"
|
||||||
|
|
||||||
|
console.log(`Starting to install ${manifest.pack_name}...`)
|
||||||
|
console.log(`Installing at >`, manifest.packPath)
|
||||||
|
|
||||||
|
global.sendToRenderer("new:installation", manifest)
|
||||||
|
|
||||||
|
fs.mkdirSync(manifest.packPath, { recursive: true })
|
||||||
|
|
||||||
|
await this.appendInstallation(manifest)
|
||||||
|
|
||||||
|
if (typeof manifest.on_install === "function") {
|
||||||
|
await manifest.on_install({
|
||||||
|
manifest: manifest,
|
||||||
|
pack_dir: manifest.packPath,
|
||||||
|
tmp_dir: TMP_PATH,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.git_clones_steps !== "undefined" && Array.isArray(manifest.git_clones_steps)) {
|
||||||
|
for await (const step of manifest.git_clones_steps) {
|
||||||
|
await InstallationStepsMethods.git(manifest, step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.http_downloads !== "undefined" && Array.isArray(manifest.http_downloads)) {
|
||||||
|
for await (const step of manifest.http_downloads) {
|
||||||
|
await InstallationStepsMethods.http(manifest, step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingTasks.length > 0) {
|
||||||
|
console.log(`Performing pending tasks...`)
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing pending tasks...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (const task of pendingTasks) {
|
||||||
|
await task()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.after_install === "function") {
|
||||||
|
console.log(`Performing after_install hook...`)
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing after_install hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await manifest.after_install({
|
||||||
|
manifest,
|
||||||
|
pack_dir: manifest.packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "installed"
|
||||||
|
manifest.install_path = manifest.packPath
|
||||||
|
manifest.installed_at = new Date()
|
||||||
|
manifest.last_update = null
|
||||||
|
|
||||||
|
await this.appendInstallation(manifest)
|
||||||
|
|
||||||
|
console.log(`Successfully installed ${manifest.pack_name}!`)
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:done`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: "Successfully installed",
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
manifest.status = "failed"
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:error`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
fs.rmdirSync(manifest.packPath, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall(manifest_id) {
|
||||||
|
console.log(`Uninstalling >`, manifest_id)
|
||||||
|
|
||||||
|
global.sendToRenderer("installation:status", {
|
||||||
|
status: "uninstalling",
|
||||||
|
id: manifest_id,
|
||||||
|
statusText: `Uninstalling ${manifest_id}...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
const manifest = db.installations.find((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
global.sendToRenderer("runtime:error", "Manifest not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.remote_url) {
|
||||||
|
const remoteManifest = await readManifest(manifest.remote_url, { just_read: true })
|
||||||
|
|
||||||
|
if (typeof remoteManifest.uninstall === "function") {
|
||||||
|
console.log(`Performing uninstall hook...`)
|
||||||
|
|
||||||
|
await remoteManifest.uninstall({
|
||||||
|
manifest: remoteManifest,
|
||||||
|
pack_dir: remoteManifest.install_path,
|
||||||
|
tmp_dir: TMP_PATH,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await rimraf(manifest.install_path)
|
||||||
|
|
||||||
|
db.installations = db.installations.filter((i) => i.id !== manifest_id)
|
||||||
|
|
||||||
|
await this.writeDb(db)
|
||||||
|
|
||||||
|
global.sendToRenderer("installation:uninstalled", {
|
||||||
|
id: manifest_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(manifest_id) {
|
||||||
|
try {
|
||||||
|
let pendingTasks = []
|
||||||
|
|
||||||
|
console.log(`Updating >`, manifest_id)
|
||||||
|
|
||||||
|
global.sendToRenderer("installation:status", {
|
||||||
|
status: "updating",
|
||||||
|
id: manifest_id,
|
||||||
|
statusText: `Updating ${manifest_id}...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
let manifest = db.installations.find((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
global.sendToRenderer("runtime:error", "Manifest not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const packPath = manifest.install_path
|
||||||
|
|
||||||
|
if (manifest.remote_url) {
|
||||||
|
manifest = await readManifest(manifest.remote_url, { just_read: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "updating"
|
||||||
|
|
||||||
|
manifest = await initManifest(manifest)
|
||||||
|
|
||||||
|
if (typeof manifest.update === "function") {
|
||||||
|
global.sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing update hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Performing update hook...`)
|
||||||
|
|
||||||
|
await manifest.update({
|
||||||
|
manifest,
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.git_update !== "undefined" && Array.isArray(manifest.git_update)) {
|
||||||
|
for await (const step of manifest.git_update) {
|
||||||
|
await InstallationStepsMethods.git(manifest, step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.http_downloads !== "undefined" && Array.isArray(manifest.http_downloads)) {
|
||||||
|
for await (const step of manifest.http_downloads) {
|
||||||
|
await InstallationStepsMethods.http(manifest, step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingTasks.length > 0) {
|
||||||
|
console.log(`Performing pending tasks...`)
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing pending tasks...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (const task of pendingTasks) {
|
||||||
|
await task()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.after_update === "function") {
|
||||||
|
console.log(`Performing after_update hook...`)
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing after_update hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await manifest.after_update({
|
||||||
|
manifest,
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "installed"
|
||||||
|
manifest.install_path = packPath
|
||||||
|
manifest.last_update = new Date()
|
||||||
|
|
||||||
|
await this.appendInstallation(manifest)
|
||||||
|
|
||||||
|
console.log(`Successfully updated ${manifest.pack_name}!`)
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:done`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: "Successfully updated",
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
manifest.status = "failed"
|
||||||
|
|
||||||
|
global.sendToRenderer(`installation:error`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(manifest_id) {
|
||||||
|
console.log(`Executing ${manifest_id}...`)
|
||||||
|
|
||||||
|
global.sendToRenderer("installation:status", {
|
||||||
|
status: "starting",
|
||||||
|
id: manifest_id,
|
||||||
|
statusText: `Executing ${manifest_id}...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
let manifest = db.installations.find((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
global.sendToRenderer("runtime:error", "Manifest not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.remote_url) {
|
||||||
|
manifest = await readManifest(manifest.remote_url, { just_read: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest = await initManifest(manifest)
|
||||||
|
|
||||||
|
if (typeof manifest.execute !== "function") {
|
||||||
|
global.sendToRenderer("installation:status", {
|
||||||
|
status: "execution_failed",
|
||||||
|
...manifest,
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
await manifest.execute({
|
||||||
|
manifest,
|
||||||
|
pack_dir: manifest.packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
|
||||||
|
global.sendToRenderer("installation:status", {
|
||||||
|
status: "installed",
|
||||||
|
...manifest,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Successfully executed ${manifest_id}!`)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
25
src/main/pkg_mng/installs_steps_methods/git.js
Normal file
25
src/main/pkg_mng/installs_steps_methods/git.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import fs from "node:fs"
|
||||||
|
import ChildProcess from "node:child_process"
|
||||||
|
|
||||||
|
export default async (manifest, step) => {
|
||||||
|
const _path = path.resolve(manifest.packPath, step.path)
|
||||||
|
|
||||||
|
console.log(`Cloning ${step.url}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Cloning ${step.url}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.mkdirSync(_path, { recursive: true })
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const process = ChildProcess.exec(`${global.GIT_PATH ?? "git"} clone --recurse-submodules --remote-submodules ${step.url} ${_path}`, {
|
||||||
|
shell: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("exit", resolve)
|
||||||
|
process.on("error", reject)
|
||||||
|
})
|
||||||
|
}
|
90
src/main/pkg_mng/installs_steps_methods/http.js
Normal file
90
src/main/pkg_mng/installs_steps_methods/http.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import fs from "node:fs"
|
||||||
|
import { pipeline as streamPipeline } from "node:stream/promises"
|
||||||
|
|
||||||
|
import humanFormat from "human-format"
|
||||||
|
|
||||||
|
import got from "got"
|
||||||
|
|
||||||
|
import extractFile from "../../utils/extractFile"
|
||||||
|
|
||||||
|
function convertSize(size) {
|
||||||
|
return `${humanFormat(size, {
|
||||||
|
decimals: 2,
|
||||||
|
})}B`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (manifest, step) => {
|
||||||
|
let _path = path.resolve(manifest.packPath, step.path ?? ".")
|
||||||
|
|
||||||
|
console.log(`Downloading ${step.url} to ${_path}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Downloading ${step.url}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (step.tmp) {
|
||||||
|
_path = path.resolve(TMP_PATH, String(new Date().getTime()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(path.resolve(_path, ".."), { recursive: true })
|
||||||
|
|
||||||
|
if (step.progress) {
|
||||||
|
const remoteStream = got.stream(step.url)
|
||||||
|
const localStream = fs.createWriteStream(_path)
|
||||||
|
|
||||||
|
let progress = null
|
||||||
|
let lastTransferred = 0
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Starting download...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
remoteStream.pipe(localStream)
|
||||||
|
|
||||||
|
remoteStream.on("downloadProgress", (_progress) => {
|
||||||
|
progress = _progress
|
||||||
|
})
|
||||||
|
|
||||||
|
const progressInterval = setInterval(() => {
|
||||||
|
progress.speed = (progress.transferred - lastTransferred) / 1
|
||||||
|
|
||||||
|
lastTransferred = progress.transferred
|
||||||
|
|
||||||
|
sendToRenderer(`installation:${manifest.id}:status`, {
|
||||||
|
...manifest,
|
||||||
|
progress: progress,
|
||||||
|
statusText: `Downloaded ${convertSize(progress.transferred)} / ${convertSize(progress.total)} | ${convertSize(progress.speed)}/s`,
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
localStream.on("finish", resolve)
|
||||||
|
localStream.on("error", reject)
|
||||||
|
})
|
||||||
|
|
||||||
|
clearInterval(progressInterval)
|
||||||
|
} else {
|
||||||
|
await streamPipeline(
|
||||||
|
got.stream(step.url),
|
||||||
|
fs.createWriteStream(_path)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.extract) {
|
||||||
|
if (typeof step.extract === "string") {
|
||||||
|
step.extract = path.resolve(manifest.packPath, step.extract)
|
||||||
|
} else {
|
||||||
|
step.extract = path.resolve(manifest.packPath, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Extracting file...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await extractFile(_path, step.extract)
|
||||||
|
}
|
||||||
|
}
|
36
src/main/utils/extractFile.js
Normal file
36
src/main/utils/extractFile.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
import path from "node:path"
|
||||||
|
import { pipeline as streamPipeline } from "node:stream/promises"
|
||||||
|
|
||||||
|
import { extractFull } from "node-7z"
|
||||||
|
import unzipper from "unzipper"
|
||||||
|
|
||||||
|
export async function extractFile(file, dest) {
|
||||||
|
const ext = path.extname(file)
|
||||||
|
|
||||||
|
console.log(`Extracting ${file} to ${dest}...`)
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case ".zip": {
|
||||||
|
await streamPipeline(
|
||||||
|
fs.createReadStream(file),
|
||||||
|
unzipper.Extract({
|
||||||
|
path: dest,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ".7z": {
|
||||||
|
await extractFull(file, dest, {
|
||||||
|
$bin: SEVENZIP_PATH
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported file extension: ${ext}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
export default extractFile
|
29
src/main/utils/initManifest.js
Normal file
29
src/main/utils/initManifest.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import os from "node:os"
|
||||||
|
|
||||||
|
export default async (manifest = {}) => {
|
||||||
|
const packPath = path.resolve(INSTALLERS_PATH, manifest.id)
|
||||||
|
|
||||||
|
const osString = `${os.platform()}-${os.arch()}`
|
||||||
|
|
||||||
|
if (typeof manifest.init === "function") {
|
||||||
|
const init_result = await manifest.init({
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH,
|
||||||
|
os_string: osString,
|
||||||
|
})
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
...manifest,
|
||||||
|
...init_result,
|
||||||
|
}
|
||||||
|
|
||||||
|
delete manifest.init
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...manifest,
|
||||||
|
packPath: packPath,
|
||||||
|
osString: osString,
|
||||||
|
}
|
||||||
|
}
|
58
src/main/utils/readManifest.js
Normal file
58
src/main/utils/readManifest.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import fs from "node:fs"
|
||||||
|
|
||||||
|
import lodash from "lodash"
|
||||||
|
import got from "got"
|
||||||
|
|
||||||
|
export async function fetchAndCreateModule(manifest) {
|
||||||
|
console.log(`Fetching ${manifest}...`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await got.get(manifest)
|
||||||
|
const moduleCode = response.body
|
||||||
|
|
||||||
|
const newModule = new module.constructor()
|
||||||
|
newModule._compile(moduleCode, manifest)
|
||||||
|
|
||||||
|
return newModule
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readManifest(manifest, { just_read = false } = {}) {
|
||||||
|
// check if manifest is a directory or a url
|
||||||
|
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi
|
||||||
|
|
||||||
|
if (urlRegex.test(manifest)) {
|
||||||
|
const _module = await fetchAndCreateModule(manifest)
|
||||||
|
const remoteUrl = lodash.clone(manifest)
|
||||||
|
|
||||||
|
manifest = _module.exports
|
||||||
|
|
||||||
|
manifest.remote_url = remoteUrl
|
||||||
|
} else {
|
||||||
|
if (!fs.existsSync(manifest)) {
|
||||||
|
throw new Error(`Manifest not found: ${manifest}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.statSync(manifest).isFile()) {
|
||||||
|
throw new Error(`Manifest is not a file: ${manifest}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifestFilePath = lodash.clone(manifest)
|
||||||
|
|
||||||
|
manifest = require(manifest)
|
||||||
|
|
||||||
|
if (!just_read) {
|
||||||
|
// copy manifest
|
||||||
|
fs.copyFileSync(manifestFilePath, path.resolve(MANIFEST_PATH, path.basename(manifest)))
|
||||||
|
|
||||||
|
manifest.remote_url = manifestFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
export default readManifest
|
@ -39,7 +39,7 @@ const NewInstallation = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const InstallationItem = (props) => {
|
const InstallationItem = (props) => {
|
||||||
const { manifest } = props
|
const [manifest, setManifest] = React.useState(props.manifest)
|
||||||
|
|
||||||
const isLoading = manifest.status === "installing" || manifest.status === "uninstalling" || manifest.status === "updating"
|
const isLoading = manifest.status === "installing" || manifest.status === "uninstalling" || manifest.status === "updating"
|
||||||
const isInstalled = manifest.status === "installed"
|
const isInstalled = manifest.status === "installed"
|
||||||
@ -61,6 +61,33 @@ const InstallationItem = (props) => {
|
|||||||
ipc.exec("bundle:uninstall", manifest.id)
|
ipc.exec("bundle:uninstall", manifest.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleUpdate(event, data) {
|
||||||
|
setManifest({
|
||||||
|
...manifest,
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStatusLine(manifest) {
|
||||||
|
if (isLoading) {
|
||||||
|
return manifest.status
|
||||||
|
}
|
||||||
|
|
||||||
|
return `v${manifest.version}` ?? "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
ipc.on(`installation:${manifest.id}:status`, handleUpdate)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc.off(`installation:${manifest.id}:status`, handleUpdate)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setManifest(props.manifest)
|
||||||
|
}, [props.manifest])
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
"installation_item_wrapper",
|
"installation_item_wrapper",
|
||||||
@ -80,7 +107,7 @@ const InstallationItem = (props) => {
|
|||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
{
|
{
|
||||||
isLoading ? manifest.status : `v${manifest.version}` ?? "N/A"
|
renderStatusLine(manifest)
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -138,6 +165,7 @@ const InstallationItem = (props) => {
|
|||||||
{
|
{
|
||||||
isLoading && <BarLoader color={getRootCssVar("--primary-color")} className="app_loader" />
|
isLoading && <BarLoader color={getRootCssVar("--primary-color")} className="app_loader" />
|
||||||
}
|
}
|
||||||
|
|
||||||
<p>{manifest.statusText ?? "Unknown status"}</p>
|
<p>{manifest.statusText ?? "Unknown status"}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user