Compare commits
No commits in common. "main" and "0.13.0" have entirely different histories.
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
56
.github/workflows/release.yml
vendored
@ -1,56 +0,0 @@
|
|||||||
name: Build & Release GUI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*.*.*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out Git repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm install -f
|
|
||||||
|
|
||||||
- name: build-linux
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
run: cd ./packages/gui && npm run build:linux
|
|
||||||
|
|
||||||
- name: build-mac
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
run: cd ./packages/gui && npm run build:mac
|
|
||||||
|
|
||||||
- name: build-win
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
run: cd ./packages/gui && npm run build:win
|
|
||||||
|
|
||||||
- name: release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
draft: true
|
|
||||||
files: |
|
|
||||||
dist/*.exe
|
|
||||||
dist/*.zip
|
|
||||||
dist/*.dmg
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.snap
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.rpm
|
|
||||||
dist/*.tar.gz
|
|
||||||
dist/*.yml
|
|
||||||
dist/*.blockmap
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
|
0
.gitignore
vendored
Executable file → Normal file
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
out
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
|
LICENSE.md
|
||||||
|
tsconfig.json
|
||||||
|
tsconfig.*.json
|
4
.prettierrc.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
singleQuote: false
|
||||||
|
semi: false
|
||||||
|
printWidth: 100
|
||||||
|
trailingComma: none
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||||
|
}
|
39
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Main Process",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
|
||||||
|
},
|
||||||
|
"runtimeArgs": ["--sourcemap"],
|
||||||
|
"env": {
|
||||||
|
"REMOTE_DEBUGGING_PORT": "9222"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Renderer Process",
|
||||||
|
"port": 9222,
|
||||||
|
"request": "attach",
|
||||||
|
"type": "chrome",
|
||||||
|
"webRoot": "${workspaceFolder}/src/renderer",
|
||||||
|
"timeout": 60000,
|
||||||
|
"presentation": {
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Debug All",
|
||||||
|
"configurations": ["Debug Main Process", "Debug Renderer Process"],
|
||||||
|
"presentation": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
|
}
|
9
TODO.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[] auto install java on setup
|
||||||
|
[x] support install ask configs
|
||||||
|
[] DEVLOGS
|
||||||
|
[] improve package last task view (statusText)
|
||||||
|
[] show git clone status
|
||||||
|
|
||||||
|
[] fix app update modal
|
||||||
|
[] fix update removes "options.txt"
|
||||||
|
[] improve child process on management
|
3
dev-app-update.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
provider: generic
|
||||||
|
url: https://example.com/auto-updates
|
||||||
|
updaterCacheDirName: rs-bundler-updater
|
43
electron-builder.yml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
appId: com.ragestudio.bundler
|
||||||
|
productName: rs-bundler
|
||||||
|
directories:
|
||||||
|
buildResources: build
|
||||||
|
files:
|
||||||
|
- '!**/.vscode/*'
|
||||||
|
- '!src/*'
|
||||||
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
|
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||||
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
|
asarUnpack:
|
||||||
|
- resources/**
|
||||||
|
win:
|
||||||
|
executableName: rs-bundler
|
||||||
|
icon: resources/icon.ico
|
||||||
|
nsis:
|
||||||
|
artifactName: ${name}-${version}-setup.${ext}
|
||||||
|
shortcutName: ${productName}
|
||||||
|
uninstallDisplayName: ${productName}
|
||||||
|
createDesktopShortcut: always
|
||||||
|
mac:
|
||||||
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
|
extendInfo:
|
||||||
|
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||||
|
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||||
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
|
notarize: false
|
||||||
|
dmg:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
- snap
|
||||||
|
- deb
|
||||||
|
maintainer: electronjs.org
|
||||||
|
category: Utility
|
||||||
|
appImage:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
npmRebuild: false
|
||||||
|
publish:
|
||||||
|
provider: generic
|
||||||
|
url: https://storage.ragestudio.net/rs-bundler/release
|
17
packages/gui/electron.vite.config.js → electron.vite.config.js
Executable file → Normal file
@ -5,14 +5,25 @@ import react from "@vitejs/plugin-react"
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
main: {
|
main: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
|
// build: {
|
||||||
|
// rollupOptions: {
|
||||||
|
// output: {
|
||||||
|
// format: "es"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
|
// build: {
|
||||||
|
// rollupOptions: {
|
||||||
|
// output: {
|
||||||
|
// format: "es"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
server: {
|
|
||||||
port: 1040,
|
|
||||||
},
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"config": resolve("src/renderer/config"),
|
"config": resolve("src/renderer/config"),
|
74
package.json
Executable file → Normal file
@ -1,9 +1,75 @@
|
|||||||
{
|
{
|
||||||
"name": "@ragestudio/relic-core",
|
"name": "rs-bundler",
|
||||||
"repository": "https://github.com/srgooglo/rs_bundler",
|
"version": "0.13.0",
|
||||||
"author": "SrGooglo <srgooglo@ragestudio.net>",
|
"description": "RageStudio Bundler Utility GUI",
|
||||||
|
"main": "./out/main/index.js",
|
||||||
|
"author": "RageStudio",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node scripts/postinstall.js"
|
"format": "prettier --write .",
|
||||||
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||||
|
"start": "electron-vite preview",
|
||||||
|
"dev": "electron-vite dev",
|
||||||
|
"build": "electron-vite build",
|
||||||
|
"postinstall": "electron-builder install-app-deps",
|
||||||
|
"pack:win": "electron-builder --win --config",
|
||||||
|
"pack:mac": "electron-builder --mac --config",
|
||||||
|
"pack:linux": "electron-builder --linux --config",
|
||||||
|
"build:win": "npm run build && npm run pack:win",
|
||||||
|
"build:mac": "npm run build && npm run pack:mac",
|
||||||
|
"build:linux": "npm run build && npm run pack:linux"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@electron-toolkit/preload": "^2.0.0",
|
||||||
|
"@electron-toolkit/utils": "^2.0.0",
|
||||||
|
"@getstation/electron-google-oauth2": "^14.0.0",
|
||||||
|
"@imjs/electron-differential-updater": "^5.1.7",
|
||||||
|
"@loadable/component": "^5.16.3",
|
||||||
|
"@ragestudio/hermes": "^0.1.1",
|
||||||
|
"adm-zip": "^0.5.10",
|
||||||
|
"antd": "^5.13.2",
|
||||||
|
"checksum": "^1.0.0",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
|
"electron-differential-updater": "^4.3.2",
|
||||||
|
"electron-is-dev": "^2.0.0",
|
||||||
|
"electron-store": "^8.1.0",
|
||||||
|
"electron-updater": "^6.1.1",
|
||||||
|
"googleapis": "^105.0.0",
|
||||||
|
"got": "11.8.3",
|
||||||
|
"human-format": "^1.2.0",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"merge-stream": "^2.0.0",
|
||||||
|
"node-7z": "^3.0.0",
|
||||||
|
"open": "8.4.2",
|
||||||
|
"progress-stream": "^2.0.0",
|
||||||
|
"protocol-registry": "^1.4.1",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-router-dom": "6.6.2",
|
||||||
|
"react-spinners": "^0.13.8",
|
||||||
|
"react-spring": "^9.7.3",
|
||||||
|
"react-motion": "0.5.2",
|
||||||
|
"request": "^2.88.2",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"signal-exit": "^4.1.0",
|
||||||
|
"unzipper": "^0.10.14",
|
||||||
|
"upath": "^2.0.1",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
|
"which": "^4.0.0",
|
||||||
|
"winreg": "^1.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron-toolkit/eslint-config": "^1.0.1",
|
||||||
|
"@electron-toolkit/eslint-config-prettier": "^1.0.1",
|
||||||
|
"@vitejs/plugin-react": "^4.0.4",
|
||||||
|
"electron": "^25.6.0",
|
||||||
|
"electron-builder": "^24.6.3",
|
||||||
|
"electron-vite": "^1.0.27",
|
||||||
|
"eslint": "^8.47.0",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"prettier": "^3.0.2",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"vite": "^4.4.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
require("./dist/index.js")
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@ragestudio/relic-cli",
|
|
||||||
"version": "0.17.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"author": "RageStudio",
|
|
||||||
"description": "RageStudio Relic, yet another package manager.",
|
|
||||||
"main": "./dist/index.js",
|
|
||||||
"bin": {
|
|
||||||
"relic": "./bin.js"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"dev": "hermes-node ./src/index.js",
|
|
||||||
"build": "hermes build"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"commander": "^12.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@ragestudio/hermes": "^0.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
import RelicCore from "@ragestudio/relic-core"
|
|
||||||
import { program, Command, Argument } from "commander"
|
|
||||||
|
|
||||||
import pkg from "../package.json"
|
|
||||||
|
|
||||||
const commands = [
|
|
||||||
{
|
|
||||||
cmd: "install",
|
|
||||||
description: "Install a package manifest from a path or URL",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "package_manifest",
|
|
||||||
description: "Path or URL to a package manifest",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
fn: async (package_manifest, options) => {
|
|
||||||
await core.initialize()
|
|
||||||
await core.setup()
|
|
||||||
|
|
||||||
return await core.package.install(package_manifest, options)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd: "run",
|
|
||||||
description: "Execute a package",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "id",
|
|
||||||
description: "The id of the package to execute",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
fn: async (pkg_id, options) => {
|
|
||||||
await core.initialize()
|
|
||||||
await core.setup()
|
|
||||||
|
|
||||||
return await core.package.execute(pkg_id, options)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd: "update",
|
|
||||||
description: "Update a package",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "id",
|
|
||||||
description: "The id of the package to update",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
fn: async (pkg_id, options) => {
|
|
||||||
await core.initialize()
|
|
||||||
await core.setup()
|
|
||||||
|
|
||||||
return await core.package.update(pkg_id, options)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd: "uninstall",
|
|
||||||
description: "Uninstall a package",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "id",
|
|
||||||
description: "The id of the package to uninstall",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
fn: async (pkg_id, options) => {
|
|
||||||
await core.initialize()
|
|
||||||
|
|
||||||
return await core.package.uninstall(pkg_id, options)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd: "apply",
|
|
||||||
description: "Apply changes to a installed package",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "id",
|
|
||||||
description: "The id of the package to apply changes to",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "add_patches",
|
|
||||||
description: "Add patches to the package",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remove_patches",
|
|
||||||
description: "Remove patches from the package",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fn: async (pkg_id, options) => {
|
|
||||||
await core.initialize()
|
|
||||||
|
|
||||||
return await core.package.apply(pkg_id, options)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd: "list",
|
|
||||||
description: "List installed package manifests",
|
|
||||||
fn: async () => {
|
|
||||||
await core.initialize()
|
|
||||||
|
|
||||||
return console.log(await core.package.list())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd: "open-path",
|
|
||||||
description: "Open the base path or a package path",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "pkg_id",
|
|
||||||
description: "Path to open",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
fn: async (options) => {
|
|
||||||
await core.initialize()
|
|
||||||
|
|
||||||
await core.openPath(options.pkg_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
global.core = new RelicCore()
|
|
||||||
|
|
||||||
program
|
|
||||||
.name(pkg.name)
|
|
||||||
.description(pkg.description)
|
|
||||||
.version(pkg.version)
|
|
||||||
|
|
||||||
for await (const command of commands) {
|
|
||||||
const cmd = new Command(command.cmd).action(command.fn)
|
|
||||||
|
|
||||||
if (command.description) {
|
|
||||||
cmd.description(command.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(command.arguments)) {
|
|
||||||
for await (const argument of command.arguments) {
|
|
||||||
if (typeof argument === "string") {
|
|
||||||
cmd.addArgument(new Argument(argument))
|
|
||||||
} else {
|
|
||||||
const arg = new Argument(argument.name, argument.description)
|
|
||||||
|
|
||||||
if (argument.default) {
|
|
||||||
arg.default(argument.default)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.addArgument(arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(command.options)) {
|
|
||||||
for await (const option of command.options) {
|
|
||||||
if (typeof option === "string") {
|
|
||||||
cmd.option(option)
|
|
||||||
} else {
|
|
||||||
cmd.option(option.name, option.description, option.default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
program.addCommand(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
program.parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json.schemastore.org/swcrc",
|
|
||||||
"module": {
|
|
||||||
"type": "commonjs",
|
|
||||||
// These are defaults.
|
|
||||||
"strict": false,
|
|
||||||
"strictMode": true,
|
|
||||||
"lazy": false,
|
|
||||||
"noInterop": false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@ragestudio/relic-core",
|
|
||||||
"version": "0.20.3",
|
|
||||||
"license": "MIT",
|
|
||||||
"author": "RageStudio",
|
|
||||||
"description": "RageStudio Relic, yet another package manager.",
|
|
||||||
"main": "./dist/index.js",
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "hermes build",
|
|
||||||
"build:swc": "npx swc ./src --out-dir ./dist --strip-leading-paths"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@foxify/events": "^2.1.0",
|
|
||||||
"adm-zip": "^0.5.12",
|
|
||||||
"aria2": "^4.1.2",
|
|
||||||
"axios": "^1.6.8",
|
|
||||||
"checksum": "^1.0.0",
|
|
||||||
"cli-color": "^2.0.4",
|
|
||||||
"cli-progress": "^3.12.0",
|
|
||||||
"deep-object-diff": "^1.1.9",
|
|
||||||
"extends-classes": "^1.0.5",
|
|
||||||
"googleapis": "^134.0.0",
|
|
||||||
"human-format": "^1.2.0",
|
|
||||||
"merge-stream": "^2.0.0",
|
|
||||||
"module-alias": "^2.2.3",
|
|
||||||
"node-7z": "^3.0.0",
|
|
||||||
"open": "8.4.2",
|
|
||||||
"request": "^2.88.2",
|
|
||||||
"rimraf": "^5.0.5",
|
|
||||||
"signal-exit": "^4.1.0",
|
|
||||||
"unzipper": "^0.10.14",
|
|
||||||
"upath": "^2.0.1",
|
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"webtorrent": "^2.4.1",
|
|
||||||
"winston": "^3.13.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@swc/cli": "^0.3.12",
|
|
||||||
"@swc/core": "^1.4.11"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import path from "path"
|
|
||||||
import { JSONFilePreset } from "../libraries/lowdb/presets/node"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
//! WARNING: Please DO NOT storage any password or sensitive data here,
|
|
||||||
// cause its not use any encryption method, and it will be stored in plain text.
|
|
||||||
// This is intended to store session tokens among other vars.
|
|
||||||
|
|
||||||
export default class ManifestAuthService {
|
|
||||||
static vaultPath = path.resolve(Vars.runtime_path, "auth.json")
|
|
||||||
|
|
||||||
static async withDB() {
|
|
||||||
return await JSONFilePreset(ManifestAuthService.vaultPath, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
static has = async (pkg_id) => {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
return !!db.data[pkg_id]
|
|
||||||
}
|
|
||||||
|
|
||||||
static set = async (pkg_id, value) => {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
return await db.update((data) => {
|
|
||||||
data[pkg_id] = value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static get = async (pkg_id) => {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
return await db.data[pkg_id]
|
|
||||||
}
|
|
||||||
|
|
||||||
static delete = async (pkg_id) => {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
return await db.update((data) => {
|
|
||||||
delete data[pkg_id]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import DB from "../db"
|
|
||||||
|
|
||||||
export default class ManifestConfigManager {
|
|
||||||
constructor(pkg_id) {
|
|
||||||
this.pkg_id = pkg_id
|
|
||||||
this.config = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
const pkg = await DB.getPackages(this.pkg_id) ?? {}
|
|
||||||
|
|
||||||
this.config = pkg.config
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key, value) {
|
|
||||||
this.config[key] = value
|
|
||||||
|
|
||||||
DB.updatePackageById(pkg_id, { config: this.config })
|
|
||||||
|
|
||||||
return this.config
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key) {
|
|
||||||
return this.config[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(key) {
|
|
||||||
delete this.config[key]
|
|
||||||
|
|
||||||
DB.updatePackageById(pkg_id, { config: this.config })
|
|
||||||
|
|
||||||
return this.config
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
import GenericSteps from "../generic_steps"
|
|
||||||
import parseStringVars from "../utils/parseStringVars"
|
|
||||||
|
|
||||||
export default class PatchManager {
|
|
||||||
constructor(pkg, manifest) {
|
|
||||||
this.pkg = pkg
|
|
||||||
this.manifest = manifest
|
|
||||||
|
|
||||||
this.log = Logger.child({ service: `PATCH-MANAGER|${pkg.id}` })
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(select) {
|
|
||||||
if (!this.manifest.patches) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
let list = []
|
|
||||||
|
|
||||||
if (typeof select === "undefined") {
|
|
||||||
list = this.manifest.patches
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(select)) {
|
|
||||||
for await (let id of select) {
|
|
||||||
const patch = this.manifest.patches.find((patch) => patch.id === id)
|
|
||||||
|
|
||||||
if (patch) {
|
|
||||||
list.push(patch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
async reapply() {
|
|
||||||
if (Array.isArray(this.pkg.applied_patches)) {
|
|
||||||
return await this.patch(this.pkg.applied_patches)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async patch(select) {
|
|
||||||
const list = await this.get(select)
|
|
||||||
|
|
||||||
for await (let patch of list) {
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: this.pkg.id,
|
|
||||||
status_text: `Applying patch [${patch.id}]...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.log.info(`Applying patch [${patch.id}]...`)
|
|
||||||
|
|
||||||
if (Array.isArray(patch.additions)) {
|
|
||||||
this.log.info(`Applying ${patch.additions.length} Additions...`)
|
|
||||||
|
|
||||||
for await (let addition of patch.additions) {
|
|
||||||
// resolve patch file
|
|
||||||
addition.file = await parseStringVars(addition.file, this.pkg)
|
|
||||||
|
|
||||||
if (fs.existsSync(addition.file)) {
|
|
||||||
this.log.info(`Addition [${addition.file}] already exists. Skipping...`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log.info(`Applying addition [${addition.file}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: this.pkg.id,
|
|
||||||
status_text: `Applying addition [${addition.file}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await GenericSteps(this.pkg, addition.steps, this.log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.pkg.applied_patches.includes(patch.id)) {
|
|
||||||
this.pkg.applied_patches.push(patch.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.updatePackageById(this.pkg.id, { applied_patches: this.pkg.applied_patches })
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: this.pkg.id,
|
|
||||||
status_text: `${list.length} Patches applied`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.log.info(`${list.length} Patches applied`)
|
|
||||||
|
|
||||||
return this.pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(select) {
|
|
||||||
const list = await this.get(select)
|
|
||||||
|
|
||||||
for await (let patch of list) {
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: this.pkg.id,
|
|
||||||
status_text: `Removing patch [${patch.id}]...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.log.info(`Removing patch [${patch.id}]...`)
|
|
||||||
|
|
||||||
if (Array.isArray(patch.additions)) {
|
|
||||||
this.log.info(`Removing ${patch.additions.length} Additions...`)
|
|
||||||
|
|
||||||
for await (let addition of patch.additions) {
|
|
||||||
addition.file = await parseStringVars(addition.file, this.pkg)
|
|
||||||
|
|
||||||
if (!fs.existsSync(addition.file)) {
|
|
||||||
this.log.info(`Addition [${addition.file}] does not exist. Skipping...`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log.info(`Removing addition [${addition.file}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: this.pkg.id,
|
|
||||||
status_text: `Removing addition [${addition.file}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.unlink(addition.file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pkg.applied_patches = this.pkg.applied_patches.filter((p) => {
|
|
||||||
return p !== patch.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.updatePackageById(this.pkg.id, { applied_patches: this.pkg.applied_patches })
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: this.pkg.id,
|
|
||||||
status_text: `${list.length} Patches removed`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.log.info(`${list.length} Patches removed`)
|
|
||||||
|
|
||||||
return this.pkg
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
const settingsPath = path.resolve(Vars.runtime_path, "settings.json")
|
|
||||||
|
|
||||||
export default class Settings {
|
|
||||||
static filePath = settingsPath
|
|
||||||
|
|
||||||
static async initialize() {
|
|
||||||
if (!fs.existsSync(settingsPath)) {
|
|
||||||
await fs.promises.writeFile(settingsPath, "{}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async read() {
|
|
||||||
return JSON.parse(await fs.promises.readFile(settingsPath, "utf8"))
|
|
||||||
}
|
|
||||||
|
|
||||||
static async write(data) {
|
|
||||||
await fs.promises.writeFile(settingsPath, JSON.stringify(data, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
static async get(key) {
|
|
||||||
const data = await this.read()
|
|
||||||
|
|
||||||
if (key) {
|
|
||||||
return data[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
static async has(key) {
|
|
||||||
const data = await this.read()
|
|
||||||
|
|
||||||
return key in data
|
|
||||||
}
|
|
||||||
|
|
||||||
static async set(key, value) {
|
|
||||||
const data = await this.read()
|
|
||||||
|
|
||||||
data[key] = value
|
|
||||||
|
|
||||||
await this.write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
static async delete(key) {
|
|
||||||
const data = await this.read()
|
|
||||||
|
|
||||||
delete data[key]
|
|
||||||
|
|
||||||
await this.write(data)
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
import { JSONFilePreset } from "./libraries/lowdb/presets/node"
|
|
||||||
import Vars from "./vars"
|
|
||||||
import pkg from "../package.json"
|
|
||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
export default class DB {
|
|
||||||
static get defaultRoot() {
|
|
||||||
return {
|
|
||||||
created_at_version: pkg.version,
|
|
||||||
packages: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultPackageState({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
icon,
|
|
||||||
version,
|
|
||||||
author,
|
|
||||||
install_path,
|
|
||||||
description,
|
|
||||||
license,
|
|
||||||
last_status,
|
|
||||||
remote_manifest,
|
|
||||||
local_manifest,
|
|
||||||
config,
|
|
||||||
executable,
|
|
||||||
}) {
|
|
||||||
return {
|
|
||||||
id: id,
|
|
||||||
name: name,
|
|
||||||
version: version,
|
|
||||||
icon: icon,
|
|
||||||
install_path: install_path,
|
|
||||||
description: description,
|
|
||||||
author: author,
|
|
||||||
license: license ?? "unlicensed",
|
|
||||||
local_manifest: local_manifest ?? null,
|
|
||||||
remote_manifest: remote_manifest ?? null,
|
|
||||||
applied_patches: [],
|
|
||||||
config: typeof config === "object" ? config : {},
|
|
||||||
last_status: last_status ?? "installing",
|
|
||||||
last_update: null,
|
|
||||||
installed_at: null,
|
|
||||||
executable: executable ?? false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async withDB() {
|
|
||||||
return await JSONFilePreset(Vars.db_path, DB.defaultRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
static async initialize() {
|
|
||||||
await this.cleanOrphans()
|
|
||||||
}
|
|
||||||
|
|
||||||
static async cleanOrphans() {
|
|
||||||
const list = await this.getPackages()
|
|
||||||
|
|
||||||
for (const pkg of list) {
|
|
||||||
if (!fs.existsSync(pkg.install_path)) {
|
|
||||||
await this.deletePackage(pkg.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getPackages(pkg_id) {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
if (pkg_id) {
|
|
||||||
return db.data["packages"].find((i) => i.id === pkg_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.data["packages"]
|
|
||||||
}
|
|
||||||
|
|
||||||
static async writePackage(pkg) {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
const prevIndex = db.data["packages"].findIndex((i) => i.id === pkg.id)
|
|
||||||
|
|
||||||
if (prevIndex !== -1) {
|
|
||||||
db.data["packages"][prevIndex] = pkg
|
|
||||||
} else {
|
|
||||||
db.data["packages"].push(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.write()
|
|
||||||
|
|
||||||
return db.data
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updatePackageById(pkg_id, obj) {
|
|
||||||
let pkg = await this.getPackages(pkg_id)
|
|
||||||
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
throw new Error("Package not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.writePackage({
|
|
||||||
...pkg,
|
|
||||||
...obj,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static async deletePackage(pkg_id) {
|
|
||||||
const db = await this.withDB()
|
|
||||||
|
|
||||||
await db.update((data) => {
|
|
||||||
data["packages"] = data["packages"].filter((i) => i.id !== pkg_id)
|
|
||||||
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
|
|
||||||
return pkg_id
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import path from "node:path"
|
|
||||||
import fs from "node:fs"
|
|
||||||
import upath from "upath"
|
|
||||||
import { execa } from "../libraries/execa"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
export default async (pkg, step) => {
|
|
||||||
if (!step.path) {
|
|
||||||
step.path = `.`
|
|
||||||
}
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: `GIT|${pkg.id}` })
|
|
||||||
|
|
||||||
const gitCMD = fs.existsSync(Vars.git_bin) ? `${Vars.git_bin}` : "git"
|
|
||||||
const final_path = upath.normalizeSafe(path.resolve(pkg.install_path, step.path))
|
|
||||||
|
|
||||||
if (!fs.existsSync(final_path)) {
|
|
||||||
fs.mkdirSync(final_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Cloning from [${step.url}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Cloning from [${step.url}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const args = [
|
|
||||||
"clone",
|
|
||||||
//`--depth ${step.depth ?? 1}`,
|
|
||||||
//"--filter=blob:none",
|
|
||||||
//"--filter=tree:0",
|
|
||||||
"--progress",
|
|
||||||
"--recurse-submodules",
|
|
||||||
"--remote-submodules",
|
|
||||||
step.url,
|
|
||||||
final_path,
|
|
||||||
]
|
|
||||||
|
|
||||||
await execa(gitCMD, args, {
|
|
||||||
cwd: final_path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import path from "node:path"
|
|
||||||
import fs from "node:fs"
|
|
||||||
import { execa } from "../libraries/execa"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
export default async (pkg, step) => {
|
|
||||||
if (!step.path) {
|
|
||||||
step.path = `.`
|
|
||||||
}
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: `GIT|${pkg.id}` })
|
|
||||||
|
|
||||||
const gitCMD = fs.existsSync(Vars.git_bin) ? `${Vars.git_bin}` : "git"
|
|
||||||
const _path = path.resolve(pkg.install_path, step.path)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Pulling...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.info(`Pulling from HEAD...`)
|
|
||||||
|
|
||||||
await execa(gitCMD, ["pull", "--rebase"], {
|
|
||||||
cwd: _path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import path from "node:path"
|
|
||||||
import fs from "node:fs"
|
|
||||||
import { execa } from "../libraries/execa"
|
|
||||||
|
|
||||||
import git_pull from "./git_pull"
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
export default async (pkg, step) => {
|
|
||||||
if (!step.path) {
|
|
||||||
step.path = `.`
|
|
||||||
}
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: `GIT|${pkg.id}` })
|
|
||||||
|
|
||||||
const gitCMD = fs.existsSync(Vars.git_bin) ? `${Vars.git_bin}` : "git"
|
|
||||||
|
|
||||||
const _path = path.resolve(pkg.install_path, step.path)
|
|
||||||
const from = step.from ?? "HEAD"
|
|
||||||
|
|
||||||
if (!fs.existsSync(_path)) {
|
|
||||||
fs.mkdirSync(_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Fetching from origin`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Fetching from origin...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
// fetch from origin
|
|
||||||
await execa(gitCMD, ["fetch", "origin"], {
|
|
||||||
cwd: _path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.info(`Cleaning untracked files...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Cleaning untracked files...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await execa(gitCMD, ["clean", "-df"], {
|
|
||||||
cwd: _path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.info(`Resetting to ${from}`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Resetting to ${from}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await execa(gitCMD, ["reset", "--hard", from], {
|
|
||||||
cwd: _path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
// pull the latest
|
|
||||||
await git_pull(pkg, step)
|
|
||||||
|
|
||||||
Log.info(`Checkout to HEAD`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Checkout to HEAD`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await execa(gitCMD, ["checkout", "HEAD"], {
|
|
||||||
cwd: _path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
import fs from "node:fs"
|
|
||||||
import os from "node:os"
|
|
||||||
|
|
||||||
import downloadHttpFile from "../helpers/downloadHttpFile"
|
|
||||||
import parseStringVars from "../utils/parseStringVars"
|
|
||||||
import extractFile from "../utils/extractFile"
|
|
||||||
|
|
||||||
export default async (pkg, step, logger, abortController) => {
|
|
||||||
if (!step.path) {
|
|
||||||
step.path = `./${path.basename(step.url)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
step.path = await parseStringVars(step.path, pkg)
|
|
||||||
|
|
||||||
let _path = path.resolve(pkg.install_path, step.path)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Downloading [${step.url}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.info(`Downloading [${step.url} to ${_path}]`)
|
|
||||||
|
|
||||||
if (step.tmp) {
|
|
||||||
_path = path.resolve(os.tmpdir(), String(new Date().getTime()), path.basename(step.url))
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(path.resolve(_path, ".."), { recursive: true })
|
|
||||||
|
|
||||||
await downloadHttpFile(
|
|
||||||
step.url,
|
|
||||||
_path,
|
|
||||||
(progress) => {
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
use_id_only: true,
|
|
||||||
status_text: `Downloaded ${progress.transferredString} / ${progress.totalString} | ${progress.speedString}/s`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
abortController
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(`Downloaded finished.`)
|
|
||||||
|
|
||||||
if (step.extract) {
|
|
||||||
if (typeof step.extract === "string") {
|
|
||||||
step.extract = path.resolve(pkg.install_path, step.extract)
|
|
||||||
} else {
|
|
||||||
step.extract = path.resolve(pkg.install_path, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Extracting bundle...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await extractFile(_path, step.extract)
|
|
||||||
|
|
||||||
if (step.deleteAfterExtract !== false) {
|
|
||||||
logger.info(`Deleting temporal file [${_path}]...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Deleting temporal files...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.rm(_path, { recursive: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import ISM_GIT_CLONE from "./git_clone"
|
|
||||||
import ISM_GIT_PULL from "./git_pull"
|
|
||||||
import ISM_GIT_RESET from "./git_reset"
|
|
||||||
import ISM_HTTP from "./http"
|
|
||||||
import ISM_TORRENT from "./torrent"
|
|
||||||
|
|
||||||
const InstallationStepsMethods = {
|
|
||||||
git_clone: ISM_GIT_CLONE,
|
|
||||||
git_pull: ISM_GIT_PULL,
|
|
||||||
git_reset: ISM_GIT_RESET,
|
|
||||||
http_file: ISM_HTTP,
|
|
||||||
torrent: ISM_TORRENT,
|
|
||||||
}
|
|
||||||
|
|
||||||
const StepsOrders = [
|
|
||||||
"git_clones",
|
|
||||||
"git_pull",
|
|
||||||
"git_reset",
|
|
||||||
"torrent",
|
|
||||||
"http_file",
|
|
||||||
]
|
|
||||||
|
|
||||||
export default async function processGenericSteps(pkg, steps, logger = Logger, abortController) {
|
|
||||||
logger.info(`Processing generic steps...`)
|
|
||||||
|
|
||||||
if (!Array.isArray(steps)) {
|
|
||||||
throw new Error(`Steps must be an array`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (steps.length === 0) {
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
steps = steps.sort((a, b) => {
|
|
||||||
return StepsOrders.indexOf(a.type) - StepsOrders.indexOf(b.type)
|
|
||||||
})
|
|
||||||
|
|
||||||
for await (let step of steps) {
|
|
||||||
step.type = step.type.toLowerCase()
|
|
||||||
|
|
||||||
if (abortController?.signal?.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!InstallationStepsMethods[step.type]) {
|
|
||||||
throw new Error(`Unknown step: ${step.type}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
await InstallationStepsMethods[step.type](pkg, step, logger, abortController)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
import parseStringVars from "../utils/parseStringVars"
|
|
||||||
import downloadTorrent from "../helpers/downloadTorrent"
|
|
||||||
|
|
||||||
export default async (pkg, step, logger, abortController) => {
|
|
||||||
if (!step.magnet) {
|
|
||||||
throw new Error(`Magnet is required for torrent step`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof step.path === "undefined") {
|
|
||||||
step.path = `.`
|
|
||||||
}
|
|
||||||
|
|
||||||
step.path = await parseStringVars(step.path, pkg)
|
|
||||||
|
|
||||||
let _path = path.resolve(pkg.install_path, step.path)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Preparing torrent...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.info(`Preparing torrent with magnet => [${step.magnet}]`)
|
|
||||||
|
|
||||||
if (step.tmp) {
|
|
||||||
_path = path.resolve(os.tmpdir(), String(new Date().getTime()))
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentDir = path.resolve(_path, "..")
|
|
||||||
|
|
||||||
if (!fs.existsSync(parentDir)) {
|
|
||||||
fs.mkdirSync(parentDir, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
await downloadTorrent(step.magnet, _path, {
|
|
||||||
onProgress: (progress) => {
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
use_id_only: true,
|
|
||||||
status_text: `Downloaded ${progress.transferredString} / ${progress.totalString} | ${progress.speedString}/s`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
taskId: pkg.id
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import PatchManager from "../classes/PatchManager"
|
|
||||||
import ManifestReader from "../manifest/reader"
|
|
||||||
import ManifestVM from "../manifest/vm"
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "APPLIER" })
|
|
||||||
|
|
||||||
function findPatch(patches, applied_patches, changes, mustBeInstalled) {
|
|
||||||
return patches.filter((patch) => {
|
|
||||||
const patchID = patch.id
|
|
||||||
|
|
||||||
if (typeof changes.patches[patchID] === "undefined") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mustBeInstalled === true && !applied_patches.includes(patch.id) && changes.patches[patchID] === true) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mustBeInstalled === false && applied_patches.includes(patch.id) && changes.patches[patchID] === false) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}).map((patch) => patch.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function apply(pkg_id, changes = {}) {
|
|
||||||
try {
|
|
||||||
let pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
BaseLog.error(`Package not found [${pkg_id}]`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let manifest = await ManifestReader(pkg.local_manifest)
|
|
||||||
manifest = await ManifestVM(manifest.code)
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: `APPLIER|${pkg.id}` })
|
|
||||||
|
|
||||||
Log.info(`Applying changes to package...`)
|
|
||||||
Log.info(`Changes: ${JSON.stringify(changes)}`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Applying changes to package...`,
|
|
||||||
last_status: "loading",
|
|
||||||
})
|
|
||||||
|
|
||||||
if (changes.patches) {
|
|
||||||
if (!Array.isArray(pkg.applied_patches)) {
|
|
||||||
pkg.applied_patches = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const patches = new PatchManager(pkg, manifest)
|
|
||||||
|
|
||||||
await patches.remove(findPatch(manifest.patches, pkg.applied_patches, changes, false))
|
|
||||||
await patches.patch(findPatch(manifest.patches, pkg.applied_patches, changes, true))
|
|
||||||
|
|
||||||
pkg = await DB.getPackages(pkg_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.config) {
|
|
||||||
Log.info(`Applying config to package...`)
|
|
||||||
|
|
||||||
if (Object.keys(changes.config).length !== 0) {
|
|
||||||
Object.entries(changes.config).forEach(([key, value]) => {
|
|
||||||
pkg.config[key] = value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.writePackage(pkg)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: "All changes applied",
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.info(`All changes applied to package.`)
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "apply",
|
|
||||||
id: pkg_id,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
|
|
||||||
BaseLog.error(`Failed to apply changes to package [${pkg_id}]`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import ManifestAuthDB from "../classes/ManifestAuthDB"
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: "AUTH" })
|
|
||||||
|
|
||||||
export default async (pkg_id, value) => {
|
|
||||||
if (!pkg_id) {
|
|
||||||
Log.error("pkg_id is required")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
Log.error("value is required")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
Log.error("Package not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Setting auth for [${pkg_id}]`)
|
|
||||||
|
|
||||||
await ManifestAuthDB.set(pkg_id, value)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("pkg:authorized", pkg)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import UninstallHandler from "./uninstall"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "CANCEL_INSTALL" })
|
|
||||||
|
|
||||||
export default async function reinstall(pkg_id) {
|
|
||||||
try {
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
BaseLog.info(`Package not found [${pkg_id}]`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:install:cancel`, pkg_id)
|
|
||||||
global._relic_eventBus.emit(`pkg:install:cancel:${pkg_id}`, pkg_id)
|
|
||||||
global._relic_eventBus.emit(`task:cancel:${pkg_id}`, pkg_id)
|
|
||||||
|
|
||||||
const task = globalThis.relic_core.tasks.find((task) => task.id === pkg_id)
|
|
||||||
|
|
||||||
if (task) {
|
|
||||||
BaseLog.warn(`Task not found [${pkg_id}]`)
|
|
||||||
await task.abortController.abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
await UninstallHandler(pkg_id)
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "cancel_install",
|
|
||||||
id: pkg_id,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
|
|
||||||
BaseLog.error(`Failed to cancel installation package [${pkg_id}]`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import softRead from "./read"
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: "CHECK_UPDATE" })
|
|
||||||
|
|
||||||
export default async function checkUpdate(pkg_id) {
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
Log.error("Package not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Checking update for [${pkg_id}]`)
|
|
||||||
|
|
||||||
const remoteSoftManifest = await softRead(pkg.remote_manifest, {
|
|
||||||
soft: true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!remoteSoftManifest) {
|
|
||||||
Log.error("Cannot read remote manifest")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pkg.version === remoteSoftManifest.version) {
|
|
||||||
Log.info("No update available")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info("Update available")
|
|
||||||
Log.info("Local:", pkg.version)
|
|
||||||
Log.info("Remote:", remoteSoftManifest.version)
|
|
||||||
Log.info("Changelog:", remoteSoftManifest.changelog_url)
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: pkg.id,
|
|
||||||
local: pkg.version,
|
|
||||||
remote: remoteSoftManifest.version,
|
|
||||||
changelog: remoteSoftManifest.changelog_url,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import ManifestAuthDB from "../classes/ManifestAuthDB"
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: "AUTH" })
|
|
||||||
|
|
||||||
export default async (pkg_id) => {
|
|
||||||
if (!pkg_id) {
|
|
||||||
Log.error("pkg_id is required")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
Log.error("Package not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Deleting auth for [${pkg_id}]`)
|
|
||||||
|
|
||||||
await ManifestAuthDB.delete(pkg_id)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("pkg:deauthorized", pkg)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
import ManifestReader from "../manifest/reader"
|
|
||||||
import ManifestVM from "../manifest/vm"
|
|
||||||
import parseStringVars from "../utils/parseStringVars"
|
|
||||||
import { execa } from "../libraries/execa"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "EXECUTER" })
|
|
||||||
|
|
||||||
export default async function execute(pkg_id, { useRemote = false, force = false } = {}) {
|
|
||||||
try {
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
BaseLog.info(`Package not found [${pkg_id}]`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pkg.last_status !== "installed") {
|
|
||||||
if (!force) {
|
|
||||||
BaseLog.info(`Package not installed [${pkg_id}], aborting execution`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
id: pkg_id,
|
|
||||||
event: "execute",
|
|
||||||
error: new Error("Package not valid or not installed"),
|
|
||||||
})
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifestPath = useRemote ? pkg.remote_manifest : pkg.local_manifest
|
|
||||||
|
|
||||||
if (!fs.existsSync(manifestPath)) {
|
|
||||||
BaseLog.error(`Manifest not found in expected path [${manifestPath}]
|
|
||||||
\nMaybe the package installation has not been completed yet or corrupted.
|
|
||||||
`)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseLog.info(`Executing manifest > [${manifestPath}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
last_status: "loading",
|
|
||||||
status_text: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
const ManifestRead = await ManifestReader(manifestPath)
|
|
||||||
|
|
||||||
const manifest = await ManifestVM(ManifestRead.code)
|
|
||||||
|
|
||||||
if (typeof manifest.execute === "function") {
|
|
||||||
await manifest.execute(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.execute === "string") {
|
|
||||||
manifest.execute = parseStringVars(manifest.execute, pkg)
|
|
||||||
|
|
||||||
BaseLog.info(`Executing binary > [${manifest.execute}]`)
|
|
||||||
|
|
||||||
const args = Array.isArray(manifest.execute_args) ? manifest.execute_args : []
|
|
||||||
|
|
||||||
await execa(manifest.execute, args, {
|
|
||||||
cwd: pkg.install_path,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
last_status: "installed",
|
|
||||||
status_text: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
id: pkg_id,
|
|
||||||
event: "execute",
|
|
||||||
last_status: "installed",
|
|
||||||
error,
|
|
||||||
})
|
|
||||||
|
|
||||||
BaseLog.error(`Failed to execute package [${pkg_id}]`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,234 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
import ManifestReader from "../manifest/reader"
|
|
||||||
import ManifestVM from "../manifest/vm"
|
|
||||||
import GenericSteps from "../generic_steps"
|
|
||||||
import Apply from "../handlers/apply"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "INSTALLER" })
|
|
||||||
|
|
||||||
export default async function install(manifest, options = {}) {
|
|
||||||
let id = null
|
|
||||||
let abortController = new AbortController()
|
|
||||||
|
|
||||||
try {
|
|
||||||
BaseLog.info(`Invoking new installation...`)
|
|
||||||
BaseLog.info(`Fetching manifest [${manifest}]`)
|
|
||||||
|
|
||||||
const ManifestRead = await ManifestReader(manifest)
|
|
||||||
|
|
||||||
manifest = await ManifestVM(ManifestRead.code)
|
|
||||||
|
|
||||||
id = manifest.constructor.id
|
|
||||||
|
|
||||||
globalThis.relic_core.tasks.push({
|
|
||||||
type: "install",
|
|
||||||
id: id,
|
|
||||||
abortController: abortController,
|
|
||||||
})
|
|
||||||
|
|
||||||
const Log = BaseLog.child({ service: `INSTALLER|${id}` })
|
|
||||||
|
|
||||||
Log.info(`Creating install path [${manifest.install_path}]`)
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(manifest.install_path)) {
|
|
||||||
Log.info(`Package already exists, removing...`)
|
|
||||||
await fs.rmSync(manifest.install_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.mkdirSync(manifest.install_path, { recursive: true })
|
|
||||||
|
|
||||||
Log.info(`Initializing manifest...`)
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.initialize === "function") {
|
|
||||||
await manifest.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Appending to db...`)
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let pkg = DB.defaultPackageState({
|
|
||||||
...manifest.constructor,
|
|
||||||
id: id,
|
|
||||||
name: manifest.constructor.pkg_name,
|
|
||||||
version: manifest.constructor.version,
|
|
||||||
install_path: manifest.install_path,
|
|
||||||
description: manifest.constructor.description,
|
|
||||||
license: manifest.constructor.license,
|
|
||||||
last_status: "installing",
|
|
||||||
remote_manifest: ManifestRead.remote_manifest,
|
|
||||||
local_manifest: ManifestRead.local_manifest,
|
|
||||||
executable: !!manifest.execute
|
|
||||||
})
|
|
||||||
|
|
||||||
await DB.writePackage(pkg)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("pkg:new", pkg)
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manifest.configuration) {
|
|
||||||
Log.info(`Applying default config to package...`)
|
|
||||||
|
|
||||||
pkg.config = Object.entries(manifest.configuration).reduce((acc, [key, value]) => {
|
|
||||||
acc[key] = value.default
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.beforeInstall === "function") {
|
|
||||||
Log.info(`Executing beforeInstall hook...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing beforeInstall hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.beforeInstall(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(manifest.installSteps) && !options.noInstallSteps) {
|
|
||||||
Log.info(`Executing generic install steps...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing generic install steps...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await GenericSteps(pkg, manifest.installSteps, Log, abortController)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.afterInstall === "function") {
|
|
||||||
Log.info(`Executing afterInstall hook...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing afterInstall hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.afterInstall(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Finishing up...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.info(`Copying manifest to the final location...`)
|
|
||||||
|
|
||||||
const finalPath = `${manifest.install_path}/.rmanifest`
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(finalPath)) {
|
|
||||||
await fs.promises.unlink(finalPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.promises.copyFile(ManifestRead.local_manifest, finalPath)
|
|
||||||
|
|
||||||
if (ManifestRead.is_catched) {
|
|
||||||
Log.info(`Removing cache manifest...`)
|
|
||||||
await fs.promises.unlink(ManifestRead.local_manifest)
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg.local_manifest = finalPath
|
|
||||||
pkg.last_status = "loading"
|
|
||||||
pkg.installed_at = Date.now()
|
|
||||||
|
|
||||||
await DB.writePackage(pkg)
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manifest.patches) {
|
|
||||||
const defaultPatches = manifest.patches.filter((patch) => patch.default)
|
|
||||||
|
|
||||||
if (defaultPatches.length > 0) {
|
|
||||||
Log.info(`Applying default patches...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Applying default patches...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
pkg = await Apply(id, {
|
|
||||||
patches: Object.fromEntries(defaultPatches.map((patch) => [patch.id, true])),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg.last_status = "installed"
|
|
||||||
|
|
||||||
if (abortController.signal.aborted) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.writePackage(pkg)
|
|
||||||
|
|
||||||
globalThis.relic_core.tasks.filter((task) => task.id !== id)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
...pkg,
|
|
||||||
id: pkg.id,
|
|
||||||
last_status: "installed",
|
|
||||||
status_text: `Installation completed successfully`,
|
|
||||||
})
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:new:done`, pkg)
|
|
||||||
|
|
||||||
Log.info(`Package installed successfully!`)
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
id: id ?? manifest.constructor.id,
|
|
||||||
event: "install",
|
|
||||||
error,
|
|
||||||
})
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: id ?? manifest.constructor.id,
|
|
||||||
last_status: "failed",
|
|
||||||
status_text: `Installation failed`,
|
|
||||||
})
|
|
||||||
|
|
||||||
globalThis.relic_core.tasks.filter((task) => task.id !== id)
|
|
||||||
|
|
||||||
BaseLog.error(`Error during installation of package [${id}] >`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
import Logger from "../logger"
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import PackageInstall from "./install"
|
|
||||||
import PackageUpdate from "./update"
|
|
||||||
import PackageUninstall from "./uninstall"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
export default async function lastOperationRetry(pkg_id) {
|
|
||||||
try {
|
|
||||||
const Log = Logger.child({ service: `OPERATION_RETRY|${pkg_id}` })
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
Log.error(`This package doesn't exist`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Try performing last operation retry...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing last operation retry...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
switch (pkg.last_status) {
|
|
||||||
case "installing":
|
|
||||||
await PackageInstall(pkg.local_manifest)
|
|
||||||
break
|
|
||||||
case "updating":
|
|
||||||
await PackageUpdate(pkg_id)
|
|
||||||
break
|
|
||||||
case "uninstalling":
|
|
||||||
await PackageUninstall(pkg_id)
|
|
||||||
break
|
|
||||||
case "failed": {
|
|
||||||
// copy pkg.local_manifest to cache after uninstall
|
|
||||||
const cachedManifest = path.join(Vars.cache_path, `${Date.now()}${path.basename(pkg.local_manifest)}`)
|
|
||||||
|
|
||||||
if (!fs.existsSync(Vars.cache_path)) {
|
|
||||||
await fs.promises.mkdir(Vars.cache_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.promises.copyFile(pkg.local_manifest, cachedManifest)
|
|
||||||
|
|
||||||
await PackageUninstall(pkg_id)
|
|
||||||
await PackageInstall(cachedManifest)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
Log.error(`Invalid last status: ${pkg.last_status}`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
id: pkg.id,
|
|
||||||
event: "retrying last operation",
|
|
||||||
status_text: `Performing last operation retry...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
Logger.error(`Failed to perform last operation retry of [${pkg_id}]`)
|
|
||||||
Logger.error(error)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "retrying last operation",
|
|
||||||
id: pkg_id,
|
|
||||||
error: error,
|
|
||||||
})
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
import DB from "../db"
|
|
||||||
|
|
||||||
export default async function list() {
|
|
||||||
return await DB.getPackages()
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import ManifestReader from "../manifest/reader"
|
|
||||||
import ManifestVM from "../manifest/vm"
|
|
||||||
|
|
||||||
export default async function softRead(manifest, options = {}) {
|
|
||||||
const Reader = await ManifestReader(manifest)
|
|
||||||
const VM = await ManifestVM(Reader.code, options)
|
|
||||||
|
|
||||||
return VM
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import UninstallHandler from "./uninstall"
|
|
||||||
import InstallHandler from "./install"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "REINSTALL" })
|
|
||||||
|
|
||||||
export default async function reinstall(pkg_id) {
|
|
||||||
try {
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
BaseLog.info(`Package not found [${pkg_id}]`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
await UninstallHandler(pkg_id)
|
|
||||||
await InstallHandler(pkg.remote_manifest)
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "reinstall",
|
|
||||||
id: pkg_id,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
|
|
||||||
BaseLog.error(`Failed to reinstall package [${pkg_id}]`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
import ManifestReader from "../manifest/reader"
|
|
||||||
import ManifestVM from "../manifest/vm"
|
|
||||||
|
|
||||||
import { rimraf } from "rimraf"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "UNINSTALLER" })
|
|
||||||
|
|
||||||
export default async function uninstall(pkg_id) {
|
|
||||||
try {
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
BaseLog.info(`Package not found [${pkg_id}]`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: `UNINSTALLER|${pkg.id}` })
|
|
||||||
|
|
||||||
Log.info(`Uninstalling package...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Uninstalling package...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const ManifestRead = await ManifestReader(pkg.local_manifest)
|
|
||||||
const manifest = await ManifestVM(ManifestRead.code)
|
|
||||||
|
|
||||||
if (typeof manifest.uninstall === "function") {
|
|
||||||
Log.info(`Performing uninstall hook...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing uninstall hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.uninstall(pkg)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
Log.error(`Failed to perform uninstall hook`, error)
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "uninstall",
|
|
||||||
id: pkg.id,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Deleting package directory...`)
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Deleting package directory...`,
|
|
||||||
})
|
|
||||||
await rimraf(pkg.install_path)
|
|
||||||
|
|
||||||
Log.info(`Removing package from database...`)
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Removing package from database...`,
|
|
||||||
})
|
|
||||||
await DB.deletePackage(pkg.id)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
last_status: "deleted",
|
|
||||||
status_text: `Uninstalling package...`,
|
|
||||||
})
|
|
||||||
global._relic_eventBus.emit(`pkg:remove`, pkg)
|
|
||||||
Log.info(`Package uninstalled successfully!`)
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "uninstall",
|
|
||||||
id: pkg_id,
|
|
||||||
error
|
|
||||||
})
|
|
||||||
|
|
||||||
BaseLog.error(`Failed to uninstall package [${pkg_id}]`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
|
|
||||||
import ManifestReader from "../manifest/reader"
|
|
||||||
import ManifestVM from "../manifest/vm"
|
|
||||||
|
|
||||||
import GenericSteps from "../generic_steps"
|
|
||||||
import PatchManager from "../classes/PatchManager"
|
|
||||||
|
|
||||||
const BaseLog = Logger.child({ service: "UPDATER" })
|
|
||||||
|
|
||||||
const AllowedPkgChanges = [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"version",
|
|
||||||
"description",
|
|
||||||
"author",
|
|
||||||
"license",
|
|
||||||
"icon",
|
|
||||||
"core_minimum_version",
|
|
||||||
"remote_manifest",
|
|
||||||
]
|
|
||||||
|
|
||||||
const ManifestKeysMap = {
|
|
||||||
"name": "pkg_name",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function update(pkg_id) {
|
|
||||||
try {
|
|
||||||
const pkg = await DB.getPackages(pkg_id)
|
|
||||||
|
|
||||||
if (!pkg) {
|
|
||||||
BaseLog.error(`Package not found [${pkg_id}]`)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const Log = BaseLog.child({ service: `UPDATER|${pkg.id}` })
|
|
||||||
|
|
||||||
let ManifestRead = await ManifestReader(pkg.local_manifest)
|
|
||||||
let manifest = await ManifestVM(ManifestRead.code)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
last_status: "updating",
|
|
||||||
status_text: `Updating package...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
pkg.last_status = "updating"
|
|
||||||
|
|
||||||
await DB.writePackage(pkg)
|
|
||||||
|
|
||||||
if (typeof manifest.update === "function") {
|
|
||||||
Log.info(`Performing update hook...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing update hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.update(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manifest.updateSteps) {
|
|
||||||
Log.info(`Performing update steps...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing update steps...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await GenericSteps(pkg, manifest.updateSteps, Log)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(pkg.applied_patches)) {
|
|
||||||
const patchManager = new PatchManager(pkg, manifest)
|
|
||||||
|
|
||||||
await patchManager.reapply()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof manifest.afterUpdate === "function") {
|
|
||||||
Log.info(`Performing after update hook...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
id: pkg.id,
|
|
||||||
status_text: `Performing after update hook...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await manifest.afterUpdate(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
ManifestRead = await ManifestReader(pkg.local_manifest)
|
|
||||||
manifest = await ManifestVM(ManifestRead.code)
|
|
||||||
|
|
||||||
// override public static values
|
|
||||||
for await (const key of AllowedPkgChanges) {
|
|
||||||
if (key in manifest.constructor) {
|
|
||||||
const mapKey = ManifestKeysMap[key] || key
|
|
||||||
pkg[key] = manifest.constructor[mapKey]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg.last_status = "installed"
|
|
||||||
pkg.last_update = Date.now()
|
|
||||||
|
|
||||||
await DB.writePackage(pkg)
|
|
||||||
|
|
||||||
Log.info(`Package updated successfully`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
|
||||||
...pkg,
|
|
||||||
id: pkg.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit(`pkg:error`, {
|
|
||||||
event: "update",
|
|
||||||
id: pkg_id,
|
|
||||||
error,
|
|
||||||
last_status: "failed"
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
await DB.updatePackageById(pkg_id, {
|
|
||||||
last_status: "failed",
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
BaseLog.error(`Failed to update status of pkg [${pkg_id}]`)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseLog.error(`Failed to update package [${pkg_id}]`, error)
|
|
||||||
BaseLog.error(error.stack)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import axios from "axios"
|
|
||||||
import humanFormat from "human-format"
|
|
||||||
import cliProgress from "cli-progress"
|
|
||||||
|
|
||||||
function convertSize(size) {
|
|
||||||
return `${humanFormat(size, {
|
|
||||||
decimals: 2,
|
|
||||||
})}B`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async (url, destination, progressCallback, abortController) => {
|
|
||||||
const progressBar = new cliProgress.SingleBar({
|
|
||||||
format: "[{bar}] {percentage}% | {total_formatted} | {speed}/s | {eta_formatted}",
|
|
||||||
barCompleteChar: "\u2588",
|
|
||||||
barIncompleteChar: "\u2591",
|
|
||||||
hideCursor: true
|
|
||||||
}, cliProgress.Presets.shades_classic)
|
|
||||||
|
|
||||||
const { data: remoteStream, headers } = await axios.get(url, {
|
|
||||||
responseType: "stream",
|
|
||||||
signal: abortController?.signal,
|
|
||||||
})
|
|
||||||
|
|
||||||
const localStream = fs.createWriteStream(destination)
|
|
||||||
|
|
||||||
let progress = {
|
|
||||||
total: Number(headers["content-length"] ?? 0),
|
|
||||||
transferred: 0,
|
|
||||||
speed: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastTickTransferred = 0
|
|
||||||
|
|
||||||
progressBar.start(progress.total, 0, {
|
|
||||||
speed: "0B/s",
|
|
||||||
total_formatted: convertSize(progress.total),
|
|
||||||
})
|
|
||||||
|
|
||||||
remoteStream.pipe(localStream)
|
|
||||||
|
|
||||||
remoteStream.on("data", (data) => {
|
|
||||||
progress.transferred = progress.transferred + Buffer.byteLength(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
const progressInterval = setInterval(() => {
|
|
||||||
progress.speed = ((progress.transferred ?? 0) - lastTickTransferred) / 1
|
|
||||||
|
|
||||||
lastTickTransferred = progress.transferred ?? 0
|
|
||||||
|
|
||||||
progress.transferredString = convertSize(progress.transferred ?? 0)
|
|
||||||
progress.totalString = convertSize(progress.total)
|
|
||||||
progress.speedString = convertSize(progress.speed)
|
|
||||||
|
|
||||||
progressBar.update(progress.transferred, {
|
|
||||||
speed: progress.speedString,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (typeof progressCallback === "function") {
|
|
||||||
progressCallback(progress)
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
localStream.on("finish", resolve)
|
|
||||||
localStream.on("error", reject)
|
|
||||||
})
|
|
||||||
|
|
||||||
progressBar.stop()
|
|
||||||
|
|
||||||
clearInterval(progressInterval)
|
|
||||||
|
|
||||||
return destination
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
import humanFormat from "human-format"
|
|
||||||
import aria2 from "aria2"
|
|
||||||
|
|
||||||
function convertSize(size) {
|
|
||||||
return `${humanFormat(size, {
|
|
||||||
decimals: 2,
|
|
||||||
})}B`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function downloadTorrent(
|
|
||||||
magnet,
|
|
||||||
destination,
|
|
||||||
{
|
|
||||||
onStart,
|
|
||||||
onProgress,
|
|
||||||
onDone,
|
|
||||||
onError,
|
|
||||||
taskId,
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
let progressInterval = null
|
|
||||||
let tickProgress = {
|
|
||||||
total: 0,
|
|
||||||
transferred: 0,
|
|
||||||
speed: 0,
|
|
||||||
|
|
||||||
totalString: "0B",
|
|
||||||
transferredString: "0B",
|
|
||||||
speedString: "0B/s",
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new aria2({
|
|
||||||
host: "localhost",
|
|
||||||
port: 6800,
|
|
||||||
secure: false,
|
|
||||||
secret: "",
|
|
||||||
path: "/jsonrpc"
|
|
||||||
})
|
|
||||||
|
|
||||||
await client.open()
|
|
||||||
|
|
||||||
let downloadId = await client.call(
|
|
||||||
"addUri",
|
|
||||||
[magnet],
|
|
||||||
{
|
|
||||||
dir: destination,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
async function stopDownload() {
|
|
||||||
await client.call("remove", downloadId)
|
|
||||||
clearInterval(progressInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(async (resolve, reject) => {
|
|
||||||
if (typeof onStart === "function") {
|
|
||||||
onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (taskId) {
|
|
||||||
global._relic_eventBus.once(`task:cancel:${taskId}`, stopDownload)
|
|
||||||
}
|
|
||||||
|
|
||||||
progressInterval = setInterval(async () => {
|
|
||||||
const data = await client.call("tellStatus", downloadId)
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
if (data.status === "complete") {
|
|
||||||
if (Array.isArray(data.followedBy) && data.followedBy[0]) {
|
|
||||||
// replace downloadId
|
|
||||||
downloadId = data.followedBy[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tickProgress.total = parseInt(data.totalLength)
|
|
||||||
tickProgress.speed = parseInt(data.downloadSpeed)
|
|
||||||
tickProgress.transferred = parseInt(data.completedLength)
|
|
||||||
tickProgress.connections = data.connections
|
|
||||||
|
|
||||||
tickProgress.transferredString = convertSize(tickProgress.transferred)
|
|
||||||
tickProgress.totalString = convertSize(tickProgress.total)
|
|
||||||
tickProgress.speedString = convertSize(tickProgress.speed)
|
|
||||||
|
|
||||||
if (typeof onProgress === "function") {
|
|
||||||
onProgress(tickProgress)
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
client.on("onDownloadStart", async ([{ gid }]) => {
|
|
||||||
const data = await client.call("tellStatus", gid)
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
if (typeof data.following !== "undefined") {
|
|
||||||
if (data.following === downloadId) {
|
|
||||||
downloadId = data.gid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
client.on("onBtDownloadComplete", ([{ gid }]) => {
|
|
||||||
if (gid !== downloadId) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof onDone === "function") {
|
|
||||||
onDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
stopDownload()
|
|
||||||
|
|
||||||
return resolve({
|
|
||||||
downloadId,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
client.on("onDownloadError", ([{ gid }]) => {
|
|
||||||
if (gid !== downloadId) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
stopDownload()
|
|
||||||
|
|
||||||
if (typeof onError === "function") {
|
|
||||||
onError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await client.call("remove", downloadId)
|
|
||||||
|
|
||||||
if (taskId) {
|
|
||||||
global._relic_eventBus.off(`task:cancel:${taskId}`, stopDownload)
|
|
||||||
}
|
|
||||||
|
|
||||||
return downloadId
|
|
||||||
}
|
|
@ -1,201 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: "SETUP" })
|
|
||||||
|
|
||||||
import path from "node:path"
|
|
||||||
import fs from "node:fs"
|
|
||||||
import os from "node:os"
|
|
||||||
import admzip from "adm-zip"
|
|
||||||
import resolveOs from "../utils/resolveOs"
|
|
||||||
import chmodRecursive from "../utils/chmodRecursive"
|
|
||||||
|
|
||||||
import downloadFile from "../helpers/downloadHttpFile"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
import Prerequisites from "../prerequisites"
|
|
||||||
|
|
||||||
export default async () => {
|
|
||||||
if (!fs.existsSync(Vars.binaries_path)) {
|
|
||||||
Log.info(`Creating binaries directory: ${Vars.binaries_path}...`)
|
|
||||||
await fs.promises.mkdir(Vars.binaries_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (let prerequisite of Prerequisites) {
|
|
||||||
try {
|
|
||||||
Log.info(`Checking prerequisite: ${prerequisite.id}...`)
|
|
||||||
|
|
||||||
if (Array.isArray(prerequisite.requireOs) && !prerequisite.requireOs.includes(os.platform())) {
|
|
||||||
Log.info(`Prerequisite: ${prerequisite.id} is not required for this os.`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(prerequisite.finalBin)) {
|
|
||||||
Log.info(`Missing prerequisite: ${prerequisite.id}, installing...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Installing ${prerequisite.id}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (fs.existsSync(prerequisite.destination)) {
|
|
||||||
Log.info(`Deleting temporal file [${prerequisite.destination}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Deleting temporal file [${prerequisite.destination}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.rm(prerequisite.destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(prerequisite.extract)) {
|
|
||||||
Log.info(`Deleting temporal directory [${prerequisite.extract}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Deleting temporal directory [${prerequisite.extract}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.rm(prerequisite.extract, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Creating base directory: ${Vars.binaries_path}/${prerequisite.id}...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Creating base directory: ${Vars.binaries_path}/${prerequisite.id}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.mkdir(path.resolve(Vars.binaries_path, prerequisite.id), { recursive: true })
|
|
||||||
|
|
||||||
if (typeof prerequisite.url === "function") {
|
|
||||||
prerequisite.url = await prerequisite.url(os.platform(), os.arch())
|
|
||||||
Log.info(`Resolved url: ${prerequisite.url}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Downloading ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Starting download ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
await downloadFile(
|
|
||||||
prerequisite.url,
|
|
||||||
prerequisite.destination,
|
|
||||||
(progress) => {
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Downloaded ${progress.transferredString} / ${progress.totalString} | ${progress.speedString}/s`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
if (fs.existsSync(prerequisite.destination)) {
|
|
||||||
await fs.promises.rm(prerequisite.destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof prerequisite.extract === "string") {
|
|
||||||
Log.info(`Extracting ${prerequisite.id} to destination [${prerequisite.extract}]...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Extracting ${prerequisite.id} to destination [${prerequisite.extract}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const zip = new admzip(prerequisite.destination)
|
|
||||||
|
|
||||||
await zip.extractAllTo(prerequisite.extract, true)
|
|
||||||
|
|
||||||
Log.info(`Extraction ok...`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prerequisite.extractTargetFromName === true) {
|
|
||||||
let name = path.basename(prerequisite.url)
|
|
||||||
const ext = path.extname(name)
|
|
||||||
|
|
||||||
name = name.replace(ext, "")
|
|
||||||
|
|
||||||
if (fs.existsSync(path.resolve(prerequisite.extract, name))) {
|
|
||||||
await fs.promises.rename(path.resolve(prerequisite.extract, name), `${prerequisite.extract}_old`)
|
|
||||||
await fs.promises.rm(prerequisite.extract, { recursive: true })
|
|
||||||
await fs.promises.rename(`${prerequisite.extract}_old`, prerequisite.extract)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prerequisite.deleteBeforeExtract === true) {
|
|
||||||
Log.info(`Deleting temporal file [${prerequisite.destination}]`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Deleting temporal file [${prerequisite.destination}]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.unlink(prerequisite.destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof prerequisite.rewriteExecutionPermission !== "undefined") {
|
|
||||||
const to = typeof prerequisite.rewriteExecutionPermission === "string" ?
|
|
||||||
prerequisite.rewriteExecutionPermission :
|
|
||||||
prerequisite.finalBin
|
|
||||||
|
|
||||||
Log.info(`Rewriting permissions to ${to}...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Rewriting permissions to ${to}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await chmodRecursive(to, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(prerequisite.moveDirs)) {
|
|
||||||
for (const dir of prerequisite.moveDirs) {
|
|
||||||
if (Array.isArray(dir.requireOs)) {
|
|
||||||
if (!dir.requireOs.includes(resolveOs())) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`Moving ${dir.from} to ${dir.to}...`)
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
message: `Moving ${dir.from} to ${dir.to}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await fs.promises.rename(dir.from, dir.to)
|
|
||||||
|
|
||||||
if (dir.deleteParentBefore === true) {
|
|
||||||
await fs.promises.rm(path.dirname(dir.from), { recursive: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: true,
|
|
||||||
message: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.info(`Prerequisite: ${prerequisite.id} is ready!`)
|
|
||||||
} catch (error) {
|
|
||||||
global._relic_eventBus.emit("app:setup", {
|
|
||||||
installed: false,
|
|
||||||
error: error,
|
|
||||||
message: error.message,
|
|
||||||
})
|
|
||||||
|
|
||||||
Log.error("Aborting setup due to an error...")
|
|
||||||
Log.error(error)
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(`All prerequisites are ready!`)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import { EventEmitter } from "@foxify/events"
|
|
||||||
import { onExit } from "signal-exit"
|
|
||||||
import open from "open"
|
|
||||||
|
|
||||||
import SetupHelper from "./helpers/setup"
|
|
||||||
import { execa } from "./libraries/execa"
|
|
||||||
import Logger from "./logger"
|
|
||||||
|
|
||||||
import Settings from "./classes/Settings"
|
|
||||||
import Vars from "./vars"
|
|
||||||
import DB from "./db"
|
|
||||||
|
|
||||||
import PackageInstall from "./handlers/install"
|
|
||||||
import PackageExecute from "./handlers/execute"
|
|
||||||
import PackageUninstall from "./handlers/uninstall"
|
|
||||||
import PackageReinstall from "./handlers/reinstall"
|
|
||||||
import PackageCancelInstall from "./handlers/cancelInstall"
|
|
||||||
import PackageUpdate from "./handlers/update"
|
|
||||||
import PackageApply from "./handlers/apply"
|
|
||||||
import PackageList from "./handlers/list"
|
|
||||||
import PackageRead from "./handlers/read"
|
|
||||||
import PackageAuthorize from "./handlers/authorize"
|
|
||||||
import PackageDeauthorize from "./handlers/deauthorize"
|
|
||||||
import PackageCheckUpdate from "./handlers/checkUpdate"
|
|
||||||
import PackageLastOperationRetry from "./handlers/lastOperationRetry"
|
|
||||||
|
|
||||||
export default class RelicCore {
|
|
||||||
constructor(params) {
|
|
||||||
this.params = params
|
|
||||||
}
|
|
||||||
|
|
||||||
eventBus = (global._relic_eventBus = new EventEmitter())
|
|
||||||
|
|
||||||
logger = Logger
|
|
||||||
|
|
||||||
db = DB
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
globalThis.relic_core = {
|
|
||||||
tasks: [],
|
|
||||||
vars: Vars,
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Checking runtime_path >`, Vars.runtime_path)
|
|
||||||
|
|
||||||
if (!fs.existsSync(Vars.runtime_path)) {
|
|
||||||
fs.mkdirSync(Vars.runtime_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.initialize()
|
|
||||||
|
|
||||||
await Settings.initialize()
|
|
||||||
|
|
||||||
if (!(await Settings.get("packages_path"))) {
|
|
||||||
await Settings.set("packages_path", Vars.packages_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.aria2c_instance = execa(
|
|
||||||
Vars.aria2_bin,
|
|
||||||
[
|
|
||||||
"--enable-rpc",
|
|
||||||
"--rpc-listen-all=true",
|
|
||||||
"--rpc-allow-origin-all",
|
|
||||||
"--file-allocation=none",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
onExit(this.onExit)
|
|
||||||
}
|
|
||||||
|
|
||||||
onExit = () => {
|
|
||||||
if (fs.existsSync(Vars.cache_path)) {
|
|
||||||
fs.rmSync(Vars.cache_path, { recursive: true, force: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.aria2c_instance) {
|
|
||||||
this.aria2c_instance.kill("SIGINT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setup() {
|
|
||||||
return await SetupHelper()
|
|
||||||
}
|
|
||||||
|
|
||||||
package = {
|
|
||||||
install: PackageInstall,
|
|
||||||
execute: PackageExecute,
|
|
||||||
uninstall: PackageUninstall,
|
|
||||||
reinstall: PackageReinstall,
|
|
||||||
cancelInstall: PackageCancelInstall,
|
|
||||||
update: PackageUpdate,
|
|
||||||
apply: PackageApply,
|
|
||||||
list: PackageList,
|
|
||||||
read: PackageRead,
|
|
||||||
authorize: PackageAuthorize,
|
|
||||||
deauthorize: PackageDeauthorize,
|
|
||||||
checkUpdate: PackageCheckUpdate,
|
|
||||||
lastOperationRetry: PackageLastOperationRetry,
|
|
||||||
}
|
|
||||||
|
|
||||||
async openPath(pkg_id) {
|
|
||||||
if (!pkg_id) {
|
|
||||||
return open(Vars.runtime_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const packagesPath =
|
|
||||||
(await Settings.get("packages_path")) ?? Vars.packages_path
|
|
||||||
|
|
||||||
return open(packagesPath + "/" + pkg_id)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
export class Memory {
|
|
||||||
#data = null
|
|
||||||
|
|
||||||
read() {
|
|
||||||
return Promise.resolve(this.#data)
|
|
||||||
}
|
|
||||||
|
|
||||||
write(obj) {
|
|
||||||
this.#data = obj
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MemorySync {
|
|
||||||
#data = null
|
|
||||||
|
|
||||||
read() {
|
|
||||||
return this.#data || null
|
|
||||||
}
|
|
||||||
|
|
||||||
write(obj) {
|
|
||||||
this.#data = obj
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
import { TextFile, TextFileSync } from "./TextFile.js"
|
|
||||||
|
|
||||||
export class DataFile {
|
|
||||||
#adapter
|
|
||||||
#parse
|
|
||||||
#stringify
|
|
||||||
|
|
||||||
constructor(filename, { parse, stringify }) {
|
|
||||||
this.#adapter = new TextFile(filename)
|
|
||||||
this.#parse = parse
|
|
||||||
this.#stringify = stringify
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
|
||||||
const data = await this.#adapter.read()
|
|
||||||
if (data === null) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return this.#parse(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write(obj) {
|
|
||||||
return this.#adapter.write(this.#stringify(obj))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataFileSync {
|
|
||||||
#adapter
|
|
||||||
#parse
|
|
||||||
#stringify
|
|
||||||
|
|
||||||
constructor(filename, { parse, stringify }) {
|
|
||||||
this.#adapter = new TextFileSync(filename)
|
|
||||||
this.#parse = parse
|
|
||||||
this.#stringify = stringify
|
|
||||||
}
|
|
||||||
|
|
||||||
read() {
|
|
||||||
const data = this.#adapter.read()
|
|
||||||
if (data === null) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return this.#parse(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write(obj) {
|
|
||||||
this.#adapter.write(this.#stringify(obj))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { DataFile, DataFileSync } from "./DataFile.js";
|
|
||||||
|
|
||||||
export class JSONFile extends DataFile {
|
|
||||||
constructor(filename) {
|
|
||||||
super(filename, {
|
|
||||||
parse: JSON.parse,
|
|
||||||
stringify: (data) => JSON.stringify(data, null, 2),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JSONFileSync extends DataFileSync {
|
|
||||||
constructor(filename) {
|
|
||||||
super(filename, {
|
|
||||||
parse: JSON.parse,
|
|
||||||
stringify: (data) => JSON.stringify(data, null, 2),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
import { readFileSync, renameSync, writeFileSync } from "node:fs"
|
|
||||||
import { readFile } from "node:fs/promises"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
import { Writer } from "../../steno"
|
|
||||||
|
|
||||||
export class TextFile {
|
|
||||||
#filename
|
|
||||||
#writer
|
|
||||||
|
|
||||||
constructor(filename) {
|
|
||||||
this.#filename = filename
|
|
||||||
this.#writer = new Writer(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
|
||||||
let data
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = await readFile(this.#filename, "utf-8")
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === "ENOENT") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
write(str) {
|
|
||||||
return this.#writer.write(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TextFileSync {
|
|
||||||
#tempFilename
|
|
||||||
#filename
|
|
||||||
|
|
||||||
constructor(filename) {
|
|
||||||
this.#filename = filename
|
|
||||||
const f = filename.toString()
|
|
||||||
this.#tempFilename = path.join(path.dirname(f), `.${path.basename(f)}.tmp`)
|
|
||||||
}
|
|
||||||
|
|
||||||
read() {
|
|
||||||
let data
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = readFileSync(this.#filename, "utf-8")
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === "ENOENT") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
write(str) {
|
|
||||||
writeFileSync(this.#tempFilename, str)
|
|
||||||
renameSync(this.#tempFilename, this.#filename)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
function checkArgs(adapter, defaultData) {
|
|
||||||
if (adapter === undefined) throw new Error("lowdb: missing adapter")
|
|
||||||
if (defaultData === undefined) throw new Error("lowdb: missing default data")
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Low {
|
|
||||||
constructor(adapter, defaultData) {
|
|
||||||
checkArgs(adapter, defaultData)
|
|
||||||
this.adapter = adapter
|
|
||||||
this.data = defaultData
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
|
||||||
const data = await this.adapter.read()
|
|
||||||
if (data) this.data = data
|
|
||||||
}
|
|
||||||
|
|
||||||
async write() {
|
|
||||||
if (this.data) await this.adapter.write(this.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(fn) {
|
|
||||||
fn(this.data)
|
|
||||||
await this.write()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LowSync {
|
|
||||||
constructor(adapter, defaultData) {
|
|
||||||
checkArgs(adapter, defaultData)
|
|
||||||
this.adapter = adapter
|
|
||||||
this.data = defaultData
|
|
||||||
}
|
|
||||||
|
|
||||||
read() {
|
|
||||||
const data = this.adapter.read()
|
|
||||||
if (data) this.data = data
|
|
||||||
}
|
|
||||||
|
|
||||||
write() {
|
|
||||||
if (this.data) this.adapter.write(this.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
update(fn) {
|
|
||||||
fn(this.data)
|
|
||||||
this.write()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Memory, MemorySync } from "../adapters/Memory.js"
|
|
||||||
import { JSONFile, JSONFileSync } from "../adapters/node/JSONFile.js"
|
|
||||||
import { Low, LowSync } from "../core/Low.js"
|
|
||||||
|
|
||||||
export async function JSONFilePreset(filename, defaultData) {
|
|
||||||
const adapter = process.env.NODE_ENV === "test" ? new Memory() : new JSONFile(filename)
|
|
||||||
|
|
||||||
const db = new Low(adapter, defaultData)
|
|
||||||
|
|
||||||
await db.read()
|
|
||||||
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JSONFileSyncPreset(filename, defaultData) {
|
|
||||||
const adapter = process.env.NODE_ENV === "test" ? new MemorySync() : new JSONFileSync(filename)
|
|
||||||
|
|
||||||
const db = new LowSync(adapter, defaultData)
|
|
||||||
|
|
||||||
db.read()
|
|
||||||
|
|
||||||
return db
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import winston from "winston"
|
|
||||||
import WinstonTransport from "winston-transport"
|
|
||||||
import colors from "cli-color"
|
|
||||||
|
|
||||||
const servicesToColor = {
|
|
||||||
"CORE": {
|
|
||||||
color: "whiteBright",
|
|
||||||
background: "bgBlackBright",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const paintText = (level, service, ...args) => {
|
|
||||||
let { color, background } = servicesToColor[service ?? "CORE"] ?? servicesToColor["CORE"]
|
|
||||||
|
|
||||||
if (level === "error") {
|
|
||||||
color = "whiteBright"
|
|
||||||
background = "bgRedBright"
|
|
||||||
}
|
|
||||||
|
|
||||||
return colors[background][color](...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
const format = winston.format.printf(({ timestamp, service = "CORE", level, message, }) => {
|
|
||||||
return `${paintText(level, service, `(${level}) [${service}]`)} > ${message}`
|
|
||||||
})
|
|
||||||
|
|
||||||
class EventBusTransport extends WinstonTransport {
|
|
||||||
log(info, next) {
|
|
||||||
global._relic_eventBus.emit(`logger:new`, info)
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default winston.createLogger({
|
|
||||||
format: winston.format.combine(
|
|
||||||
winston.format.timestamp(),
|
|
||||||
format
|
|
||||||
),
|
|
||||||
transports: [
|
|
||||||
new winston.transports.Console(),
|
|
||||||
new EventBusTransport(),
|
|
||||||
//new winston.transports.File({ filename: "error.log", level: "error" }),
|
|
||||||
//new winston.transports.File({ filename: "combined.log" }),
|
|
||||||
],
|
|
||||||
})
|
|
@ -1,23 +0,0 @@
|
|||||||
import PublicInternalLibraries from "./libs"
|
|
||||||
|
|
||||||
const isAClass = (x) => x && typeof x === "function" && x.prototype && typeof x.prototype.constructor === "function"
|
|
||||||
|
|
||||||
export default async (dependencies, bindCtx) => {
|
|
||||||
const libraries = {}
|
|
||||||
|
|
||||||
for await (const lib of dependencies) {
|
|
||||||
if (PublicInternalLibraries[lib]) {
|
|
||||||
if (typeof PublicInternalLibraries[lib] === "function" && isAClass(PublicInternalLibraries[lib])) {
|
|
||||||
libraries[lib] = new PublicInternalLibraries[lib](bindCtx)
|
|
||||||
|
|
||||||
if (libraries[lib].initialize) {
|
|
||||||
await libraries[lib].initialize()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
libraries[lib] = PublicInternalLibraries[lib]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return libraries
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
import open from "open"
|
|
||||||
import axios from "axios"
|
|
||||||
import ManifestAuthDB from "../../../classes/ManifestAuthDB"
|
|
||||||
import UnauthorizeMethod from "../../../handlers/deauthorize"
|
|
||||||
|
|
||||||
export default class Auth {
|
|
||||||
constructor(ctx) {
|
|
||||||
this.manifest = ctx.manifest
|
|
||||||
}
|
|
||||||
|
|
||||||
async get() {
|
|
||||||
const storagedData = await ManifestAuthDB.get(this.manifest.id)
|
|
||||||
|
|
||||||
if (storagedData && this.manifest.authService) {
|
|
||||||
if (!this.manifest.authService.getter) {
|
|
||||||
return storagedData
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await axios({
|
|
||||||
method: "GET",
|
|
||||||
url: this.manifest.authService.getter,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": `Bearer ${storagedData}`
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
global._relic_eventBus.emit("auth:getter:error", err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
UnauthorizeMethod(this.manifest.id).then(() => {
|
|
||||||
this.request()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result instanceof Error) {
|
|
||||||
throw result
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(result.data)
|
|
||||||
|
|
||||||
return result.data
|
|
||||||
}
|
|
||||||
|
|
||||||
return storagedData
|
|
||||||
}
|
|
||||||
|
|
||||||
request() {
|
|
||||||
if (!this.manifest.authService || !this.manifest.authService.fetcher) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const authURL = this.manifest.authService.fetcher
|
|
||||||
|
|
||||||
open(authURL)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import extractFile from "../../../utils/extractFile"
|
|
||||||
import { execa } from "../../../libraries/execa"
|
|
||||||
import Vars from "../../../vars"
|
|
||||||
|
|
||||||
export default class Extract {
|
|
||||||
async extractFull(file, dest, { password } = {}) {
|
|
||||||
const args = [
|
|
||||||
"x",
|
|
||||||
"-y",
|
|
||||||
]
|
|
||||||
|
|
||||||
if (password) {
|
|
||||||
args.push(`-p"${password}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push(`-o"${dest}"`)
|
|
||||||
|
|
||||||
args.push(`"${file}"`)
|
|
||||||
|
|
||||||
const cmd = `${Vars.sevenzip_bin} ${args.join(" ")}`
|
|
||||||
|
|
||||||
console.log(cmd)
|
|
||||||
|
|
||||||
await execa(cmd, {
|
|
||||||
shell: true,
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async autoExtract(file, dest) {
|
|
||||||
return await extractFile(file, dest)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
// Protect from reading or write operations outside of the package directory
|
|
||||||
export default class SecureFileSystem {
|
|
||||||
constructor(ctx) {
|
|
||||||
this.jailPath = ctx.manifest.install_path
|
|
||||||
}
|
|
||||||
|
|
||||||
checkOutsideJail(target) {
|
|
||||||
// if (!path.resolve(target).startsWith(this.jailPath)) {
|
|
||||||
// throw new Error("Cannot access resource outside of package directory")
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
readFileSync(destination, options) {
|
|
||||||
this.checkOutsideJail(destination)
|
|
||||||
|
|
||||||
return fs.readFileSync(destination, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
copyFileSync(from, to) {
|
|
||||||
this.checkOutsideJail(from)
|
|
||||||
this.checkOutsideJail(to)
|
|
||||||
|
|
||||||
return fs.copyFileSync(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(destination, data, options) {
|
|
||||||
this.checkOutsideJail(destination)
|
|
||||||
|
|
||||||
return fs.writeFileSync(destination, data, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't need to check finalPath
|
|
||||||
existsSync(...args) {
|
|
||||||
return fs.existsSync(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
async rename(from, to) {
|
|
||||||
this.checkOutsideJail(from)
|
|
||||||
this.checkOutsideJail(to)
|
|
||||||
|
|
||||||
return await fs.promises.rename(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
async writeFile(path, data, options) {
|
|
||||||
this.checkOutsideJail(path)
|
|
||||||
return await fs.promises.writeFile(path, data, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async readDir(path) {
|
|
||||||
this.checkOutsideJail(path)
|
|
||||||
return await fs.promises.readdir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
async rm(path, options) {
|
|
||||||
this.checkOutsideJail(path)
|
|
||||||
return await fs.promises.rm(path, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async mkdir(path, options) {
|
|
||||||
this.checkOutsideJail(path)
|
|
||||||
return await fs.promises.mkdir(path, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async stat(path) {
|
|
||||||
this.checkOutsideJail(path)
|
|
||||||
return await fs.promises.stat(path)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import Open from "./open"
|
|
||||||
import Path from "./path"
|
|
||||||
import Fs from "./fs"
|
|
||||||
import Auth from "./auth"
|
|
||||||
import Extract from "./extract"
|
|
||||||
|
|
||||||
// Third party libraries
|
|
||||||
import Mcl from "./mcl"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fs: Fs,
|
|
||||||
path: Path,
|
|
||||||
open: Open,
|
|
||||||
auth: Auth,
|
|
||||||
extract: Extract,
|
|
||||||
mcl: Mcl,
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
import Logger from "../../../logger"
|
|
||||||
|
|
||||||
import open, { apps } from "open"
|
|
||||||
|
|
||||||
const Log = Logger.child({ service: "OPEN-LIB" })
|
|
||||||
|
|
||||||
export default {
|
|
||||||
spawn: async (...args) => {
|
|
||||||
Log.info("Spawning with args >", args)
|
|
||||||
|
|
||||||
return await open(...args)
|
|
||||||
},
|
|
||||||
apps: apps,
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
|
|
||||||
export default path
|
|
@ -1,60 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
import axios from "axios"
|
|
||||||
import checksum from "checksum"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
export async function readManifest(manifest) {
|
|
||||||
// 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
|
|
||||||
|
|
||||||
const target = manifest?.remote_url ?? manifest
|
|
||||||
|
|
||||||
if (!fs.existsSync(Vars.cache_path)) {
|
|
||||||
fs.mkdirSync(Vars.cache_path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlRegex.test(target)) {
|
|
||||||
const { data: code } = await axios.get(target)
|
|
||||||
|
|
||||||
const manifestChecksum = checksum(code, { algorithm: "md5" })
|
|
||||||
|
|
||||||
const cachedManifest = path.join(Vars.cache_path, `${manifestChecksum}.rmanifest`)
|
|
||||||
|
|
||||||
await fs.promises.writeFile(cachedManifest, code)
|
|
||||||
|
|
||||||
return {
|
|
||||||
remote_manifest: manifest,
|
|
||||||
local_manifest: cachedManifest,
|
|
||||||
is_catched: true,
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!fs.existsSync(target)) {
|
|
||||||
throw new Error(`Manifest not found: ${target}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.statSync(target).isFile()) {
|
|
||||||
throw new Error(`Manifest is not a file: ${target}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy to cache
|
|
||||||
const cachedManifest = path.join(Vars.cache_path, path.basename(target))
|
|
||||||
|
|
||||||
await fs.promises.copyFile(target, cachedManifest)
|
|
||||||
|
|
||||||
if (!fs.existsSync(cachedManifest)) {
|
|
||||||
throw new Error(`Manifest copy failed: ${target}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
remote_manifest: undefined,
|
|
||||||
local_manifest: target,
|
|
||||||
is_catched: false,
|
|
||||||
code: fs.readFileSync(target, "utf8"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default readManifest
|
|
@ -1,99 +0,0 @@
|
|||||||
import Logger from "../logger"
|
|
||||||
|
|
||||||
import os from "node:os"
|
|
||||||
import vm from "node:vm"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
import DB from "../db"
|
|
||||||
import ManifestConfigManager from "../classes/ManifestConfig"
|
|
||||||
|
|
||||||
import resolveOs from "../utils/resolveOs"
|
|
||||||
import FetchLibraries from "./libraries"
|
|
||||||
|
|
||||||
import Settings from "../classes/Settings"
|
|
||||||
|
|
||||||
import Vars from "../vars"
|
|
||||||
|
|
||||||
async function BuildManifest(baseClass, context, { soft = false } = {}) {
|
|
||||||
// try to find install_path on db
|
|
||||||
const pkg = await DB.getPackages(baseClass.id)
|
|
||||||
|
|
||||||
if (pkg) {
|
|
||||||
if (pkg.install_path) {
|
|
||||||
context.install_path = pkg.install_path
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const packagesPath = await Settings.get("packages_path") ?? Vars.packages_path
|
|
||||||
|
|
||||||
// inject install_path
|
|
||||||
context.install_path = path.resolve(packagesPath, baseClass.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseClass.install_path = context.install_path
|
|
||||||
|
|
||||||
if (soft === true) {
|
|
||||||
return baseClass
|
|
||||||
}
|
|
||||||
|
|
||||||
const configManager = new ManifestConfigManager(baseClass.id)
|
|
||||||
|
|
||||||
await configManager.initialize()
|
|
||||||
|
|
||||||
let dependencies = []
|
|
||||||
|
|
||||||
if (Array.isArray(baseClass.useLib)) {
|
|
||||||
dependencies = [
|
|
||||||
...dependencies,
|
|
||||||
...baseClass.useLib
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// modify context
|
|
||||||
context.Log = Logger.child({ service: `VM|${baseClass.id}` })
|
|
||||||
context.Lib = await FetchLibraries(dependencies, {
|
|
||||||
manifest: baseClass,
|
|
||||||
install_path: context.install_path,
|
|
||||||
})
|
|
||||||
context.Config = configManager
|
|
||||||
|
|
||||||
// Construct the instance
|
|
||||||
const instance = new baseClass()
|
|
||||||
|
|
||||||
instance.install_path = context.install_path
|
|
||||||
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectUseManifest(code) {
|
|
||||||
return code + "\n\nuse(Manifest);"
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async (code, { soft = false } = {}) => {
|
|
||||||
return await new Promise(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
code = injectUseManifest(code)
|
|
||||||
|
|
||||||
const context = {
|
|
||||||
Vars: Vars,
|
|
||||||
Log: Logger.child({ service: "MANIFEST_VM" }),
|
|
||||||
use: (baseClass) => {
|
|
||||||
return BuildManifest(
|
|
||||||
baseClass,
|
|
||||||
context,
|
|
||||||
{
|
|
||||||
soft: soft,
|
|
||||||
}
|
|
||||||
).then(resolve)
|
|
||||||
},
|
|
||||||
os_string: resolveOs(),
|
|
||||||
arch: os.arch(),
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.createContext(context)
|
|
||||||
|
|
||||||
await vm.runInContext(code, context)
|
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
import resolveRemoteBinPath from "./utils/resolveRemoteBinPath"
|
|
||||||
import Vars from "./vars"
|
|
||||||
import path from "node:path"
|
|
||||||
import axios from "axios"
|
|
||||||
|
|
||||||
const baseURL = "https://storage.ragestudio.net/rstudio/binaries"
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
id: "7z-bin",
|
|
||||||
finalBin: Vars.sevenzip_bin,
|
|
||||||
url: resolveRemoteBinPath(`${baseURL}/7z-full`, "7z.zip"),
|
|
||||||
destination: path.resolve(Vars.binaries_path, "7z.zip"),
|
|
||||||
extract: path.resolve(Vars.binaries_path, "7z-bin"),
|
|
||||||
rewriteExecutionPermission: true,
|
|
||||||
deleteBeforeExtract: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "git-bin",
|
|
||||||
finalBin: Vars.git_bin,
|
|
||||||
url: resolveRemoteBinPath(`${baseURL}/git`, "git-bundle-2.4.0.zip"),
|
|
||||||
destination: path.resolve(Vars.binaries_path, "git-bundle.zip"),
|
|
||||||
extract: path.resolve(Vars.binaries_path, "git-bin"),
|
|
||||||
requireOs: ["win32"],
|
|
||||||
rewriteExecutionPermission: true,
|
|
||||||
deleteBeforeExtract: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "aria2",
|
|
||||||
finalBin: Vars.aria2_bin,
|
|
||||||
url: async (os, arch) => {
|
|
||||||
return `https://storage.ragestudio.net/rstudio/binaries/aria2/${os}/${arch}/${os === "win32" ? "aria2c.exe" : "aria2c"}`
|
|
||||||
},
|
|
||||||
destination: Vars.aria2_bin,
|
|
||||||
rewriteExecutionPermission: Vars.aria2_bin,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "java22_jre_bin",
|
|
||||||
finalBin: Vars.java22_jre_bin,
|
|
||||||
url: async (os, arch) => {
|
|
||||||
const { data } = await axios({
|
|
||||||
method: "GET",
|
|
||||||
url: "https://api.azul.com/metadata/v1/zulu/packages",
|
|
||||||
params: {
|
|
||||||
arch: arch,
|
|
||||||
java_version: "22",
|
|
||||||
os: os === "win32" ? "windows" : os,
|
|
||||||
archive_type: "zip",
|
|
||||||
javafx_bundled: "false",
|
|
||||||
java_package_type: "jre",
|
|
||||||
page_size: "1",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return data[0].download_url
|
|
||||||
},
|
|
||||||
destination: path.resolve(Vars.binaries_path, "java22-jre.zip"),
|
|
||||||
extract: path.resolve(Vars.binaries_path, "java22_jre_bin"),
|
|
||||||
extractTargetFromName: true,
|
|
||||||
moveDirs: [
|
|
||||||
{
|
|
||||||
requireOs: ["macos"],
|
|
||||||
from: path.resolve(Vars.binaries_path, "java22_jre_bin", "zulu-22.jre", "Contents"),
|
|
||||||
to: path.resolve(Vars.binaries_path, "java22_jre_bin", "Contents"),
|
|
||||||
deleteParentBefore: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
rewriteExecutionPermission: path.resolve(Vars.binaries_path, "java22_jre_bin"),
|
|
||||||
deleteBeforeExtract: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "java17_jre_bin",
|
|
||||||
finalBin: Vars.java17_jre_bin,
|
|
||||||
url: async (os, arch) => {
|
|
||||||
const { data } = await axios({
|
|
||||||
method: "GET",
|
|
||||||
url: "https://api.azul.com/metadata/v1/zulu/packages",
|
|
||||||
params: {
|
|
||||||
arch: arch,
|
|
||||||
java_version: "17",
|
|
||||||
os: os === "win32" ? "windows" : os,
|
|
||||||
archive_type: "zip",
|
|
||||||
javafx_bundled: "false",
|
|
||||||
java_package_type: "jre",
|
|
||||||
page_size: "1",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return data[0].download_url
|
|
||||||
},
|
|
||||||
destination: path.resolve(Vars.binaries_path, "java17-jre.zip"),
|
|
||||||
extract: path.resolve(Vars.binaries_path, "java17_jre_bin"),
|
|
||||||
extractTargetFromName: true,
|
|
||||||
moveDirs: [
|
|
||||||
{
|
|
||||||
requireOs: ["macos"],
|
|
||||||
from: path.resolve(Vars.binaries_path, "java17_jre_bin", "zulu-17.jre", "Contents"),
|
|
||||||
to: path.resolve(Vars.binaries_path, "java17_jre_bin", "Contents"),
|
|
||||||
deleteParentBefore: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
rewriteExecutionPermission: path.resolve(Vars.binaries_path, "java17_jre_bin"),
|
|
||||||
deleteBeforeExtract: true,
|
|
||||||
},
|
|
||||||
]
|
|
@ -1,16 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
async function chmodRecursive(target, mode) {
|
|
||||||
if (fs.lstatSync(target).isDirectory()) {
|
|
||||||
const files = await fs.promises.readdir(target, { withFileTypes: true })
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
await chmodRecursive(path.join(target, file.name), mode)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await fs.promises.chmod(target, mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default chmodRecursive
|
|
@ -1,25 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
async function readDirRecurse(dir, maxDepth = 3, current = 0) {
|
|
||||||
if (current > maxDepth) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = await fs.promises.readdir(dir)
|
|
||||||
|
|
||||||
const promises = files.map(async (file) => {
|
|
||||||
const filePath = path.join(dir, file)
|
|
||||||
const stat = await fs.promises.stat(filePath)
|
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
return readDirRecurse(filePath, maxDepth, current + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filePath
|
|
||||||
})
|
|
||||||
|
|
||||||
return (await Promise.all(promises)).flat()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default readDirRecurse
|
|
@ -1,17 +0,0 @@
|
|||||||
import os from "node:os"
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
if (os.platform() === "win32") {
|
|
||||||
return "windows"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (os.platform() === "darwin") {
|
|
||||||
return "macos"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (os.platform() === "linux") {
|
|
||||||
return "linux"
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.platform()
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
export default (pre, post) => {
|
|
||||||
let url = null
|
|
||||||
|
|
||||||
if (process.platform === "darwin") {
|
|
||||||
url = `${pre}/darwin/${process.arch}/${post}`
|
|
||||||
}
|
|
||||||
else if (process.platform === "win32") {
|
|
||||||
url = `${pre}/win32/${process.arch}/${post}`
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
url = `${pre}/linux/${process.arch}/${post}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
import upath from "upath"
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return upath.normalizeSafe(path.resolve(
|
|
||||||
process.env.APPDATA ||
|
|
||||||
(process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.HOME + "/.local/share"),
|
|
||||||
))
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
import upath from "upath"
|
|
||||||
import resolveUserDataPath from "./utils/resolveUserDataPath"
|
|
||||||
|
|
||||||
const isWin = process.platform.includes("win32")
|
|
||||||
const isMac = process.platform.includes("darwin")
|
|
||||||
|
|
||||||
const runtimeName = "rs-relic"
|
|
||||||
|
|
||||||
const userdata_path = resolveUserDataPath()
|
|
||||||
const runtime_path = upath.normalizeSafe(path.join(userdata_path, runtimeName))
|
|
||||||
const cache_path = upath.normalizeSafe(path.join(runtime_path, "cache"))
|
|
||||||
const packages_path = upath.normalizeSafe(path.join(runtime_path, "packages"))
|
|
||||||
const binaries_path = upath.normalizeSafe(
|
|
||||||
path.resolve(runtime_path, "binaries"),
|
|
||||||
)
|
|
||||||
const db_path = upath.normalizeSafe(path.resolve(runtime_path, "db.json"))
|
|
||||||
|
|
||||||
const binaries = {
|
|
||||||
sevenzip_bin: upath.normalizeSafe(
|
|
||||||
path.resolve(binaries_path, "7z-bin", isWin ? "7za.exe" : "7z"),
|
|
||||||
),
|
|
||||||
git_bin: upath.normalizeSafe(
|
|
||||||
path.resolve(
|
|
||||||
binaries_path,
|
|
||||||
"git-bin",
|
|
||||||
"bin",
|
|
||||||
isWin ? "git.exe" : "git",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
aria2_bin: upath.normalizeSafe(
|
|
||||||
path.resolve(binaries_path, "aria2", isWin ? "aria2c.exe" : "aria2c"),
|
|
||||||
),
|
|
||||||
java22_jre_bin: upath.normalizeSafe(
|
|
||||||
path.resolve(
|
|
||||||
binaries_path,
|
|
||||||
"java22_jre_bin",
|
|
||||||
isMac
|
|
||||||
? "Contents/Home/bin/java"
|
|
||||||
: isWin
|
|
||||||
? "bin/java.exe"
|
|
||||||
: "bin/java",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
java17_jre_bin: upath.normalizeSafe(
|
|
||||||
path.resolve(
|
|
||||||
binaries_path,
|
|
||||||
"java17_jre_bin",
|
|
||||||
isMac
|
|
||||||
? "Contents/Home/bin/java"
|
|
||||||
: isWin
|
|
||||||
? "bin/java.exe"
|
|
||||||
: "bin/java",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
runtimeName,
|
|
||||||
db_path,
|
|
||||||
userdata_path,
|
|
||||||
runtime_path,
|
|
||||||
cache_path,
|
|
||||||
packages_path,
|
|
||||||
binaries_path,
|
|
||||||
...binaries,
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
appId: com.ragestudio.relic
|
|
||||||
productName: Relic
|
|
||||||
directories:
|
|
||||||
buildResources: build
|
|
||||||
files:
|
|
||||||
- '!**/.vscode/*'
|
|
||||||
- '!src/*'
|
|
||||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
|
||||||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
|
||||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
|
||||||
asarUnpack:
|
|
||||||
- resources/**
|
|
||||||
win:
|
|
||||||
executableName: Relic
|
|
||||||
icon: resources/icon.ico
|
|
||||||
nsis:
|
|
||||||
artifactName: ${productName}-${version}-setup.${ext}
|
|
||||||
shortcutName: ${productName}
|
|
||||||
uninstallDisplayName: ${productName}
|
|
||||||
createDesktopShortcut: always
|
|
||||||
mac:
|
|
||||||
icon: resources/icon.icns
|
|
||||||
notarize: false
|
|
||||||
dmg:
|
|
||||||
artifactName: ${productName}-${version}.${ext}
|
|
||||||
linux:
|
|
||||||
target:
|
|
||||||
- AppImage
|
|
||||||
- deb
|
|
||||||
maintainer: ragestudio.net
|
|
||||||
category: Utility
|
|
||||||
icon: resources/icon.png
|
|
||||||
appImage:
|
|
||||||
artifactName: ${productName}-${version}.${ext}
|
|
||||||
npmRebuild: false
|
|
||||||
publish:
|
|
||||||
provider: generic
|
|
||||||
url: https://storage.ragestudio.net/relic/release
|
|
@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "relic-gui",
|
|
||||||
"version": "0.20.3",
|
|
||||||
"description": "RageStudio Relic, yet another package manager.",
|
|
||||||
"homepage": "https://relic.ragestudio.net",
|
|
||||||
"main": "./out/main/index.js",
|
|
||||||
"author": "RageStudio",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"start": "electron-vite preview",
|
|
||||||
"dev": "npm run build:core && electron-vite dev",
|
|
||||||
"build": "npm run build:core && electron-vite build",
|
|
||||||
"postinstall": "electron-builder install-app-deps",
|
|
||||||
"pack:win": "electron-builder --win --config",
|
|
||||||
"pack:mac": "electron-builder --mac --config",
|
|
||||||
"pack:linux": "electron-builder --linux --config",
|
|
||||||
"build:win": "npm run build && npm run pack:win",
|
|
||||||
"build:mac": "npm run build && npm run pack:mac",
|
|
||||||
"build:linux": "npm run build && npm run pack:linux",
|
|
||||||
"build:core": "cd ../core && npm run build:swc"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@electron-toolkit/preload": "^2.0.0",
|
|
||||||
"@electron-toolkit/utils": "^2.0.0",
|
|
||||||
"@getstation/electron-google-oauth2": "^14.0.0",
|
|
||||||
"@imjs/electron-differential-updater": "^5.1.7",
|
|
||||||
"@loadable/component": "^5.16.3",
|
|
||||||
"@ragestudio/relic-core": "^0.20.3",
|
|
||||||
"antd": "^5.13.2",
|
|
||||||
"classnames": "^2.3.2",
|
|
||||||
"electron-differential-updater": "^4.3.2",
|
|
||||||
"electron-is-dev": "^2.0.0",
|
|
||||||
"electron-store": "^8.1.0",
|
|
||||||
"electron-updater": "^6.1.1",
|
|
||||||
"got": "11.8.3",
|
|
||||||
"human-format": "^1.2.0",
|
|
||||||
"protocol-registry": "^1.4.1",
|
|
||||||
"less": "^4.2.0",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"react-icons": "^4.11.0",
|
|
||||||
"react-motion": "0.5.2",
|
|
||||||
"react-router-dom": "6.6.2",
|
|
||||||
"react-spinners": "^0.13.8",
|
|
||||||
"react-spring": "^9.7.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@ragestudio/hermes": "^0.1.1",
|
|
||||||
"@vitejs/plugin-react": "^4.0.4",
|
|
||||||
"electron": "25.6.0",
|
|
||||||
"electron-builder": "24.6.3",
|
|
||||||
"electron-vite": "^2.1.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"vite": "^4.4.9"
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 14 KiB |
@ -1,222 +0,0 @@
|
|||||||
import sendToRender from "../utils/sendToRender"
|
|
||||||
import { ipcMain, dialog } from "electron"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
export default class CoreAdapter {
|
|
||||||
constructor(electronApp, RelicCore) {
|
|
||||||
this.app = electronApp
|
|
||||||
this.core = RelicCore
|
|
||||||
this.initialized = false
|
|
||||||
}
|
|
||||||
|
|
||||||
loggerWindow = null
|
|
||||||
|
|
||||||
ipcEvents = {
|
|
||||||
"pkg:list": async () => {
|
|
||||||
return await this.core.package.list()
|
|
||||||
},
|
|
||||||
"pkg:get": async (event, pkg_id) => {
|
|
||||||
return await this.core.db.getPackages(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:read": async (event, manifest_path, options = {}) => {
|
|
||||||
const manifest = await this.core.package.read(manifest_path, options)
|
|
||||||
|
|
||||||
return JSON.stringify({
|
|
||||||
...this.core.db.defaultPackageState({ ...manifest }),
|
|
||||||
...manifest,
|
|
||||||
name: manifest.pkg_name,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"pkg:install": async (event, manifest_path) => {
|
|
||||||
return await this.core.package.install(manifest_path)
|
|
||||||
},
|
|
||||||
"pkg:update": async (event, pkg_id, { execOnFinish = false } = {}) => {
|
|
||||||
await this.core.package.update(pkg_id)
|
|
||||||
|
|
||||||
if (execOnFinish) {
|
|
||||||
await this.core.package.execute(pkg_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
"pkg:apply": async (event, pkg_id, changes) => {
|
|
||||||
return await this.core.package.apply(pkg_id, changes)
|
|
||||||
},
|
|
||||||
"pkg:uninstall": async (event, pkg_id) => {
|
|
||||||
return await this.core.package.uninstall(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:reinstall": async (event, pkg_id) => {
|
|
||||||
return await this.core.package.reinstall(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:cancel_install": async (event, pkg_id) => {
|
|
||||||
return await this.core.package.cancelInstall(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:execute": async (event, pkg_id, { force = false } = {}) => {
|
|
||||||
// check for updates first
|
|
||||||
if (!force) {
|
|
||||||
const update = await this.core.package.checkUpdate(pkg_id)
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
return sendToRender("pkg:update_available", update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.core.package.execute(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:open": async (event, pkg_id) => {
|
|
||||||
return await this.core.openPath(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:last_operation_retry": async (event, pkg_id) => {
|
|
||||||
return await this.core.package.lastOperationRetry(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:cancel_current_operation": async (event, pkg_id) => {
|
|
||||||
return await this.core.package.cancelCurrentOperation(pkg_id)
|
|
||||||
},
|
|
||||||
"pkg:delete_auth": async (event, pkg_id) => {
|
|
||||||
return await this.core.package.deauthorize(pkg_id)
|
|
||||||
},
|
|
||||||
"core:open-path": async (event, pkg_id) => {
|
|
||||||
return await this.core.openPath(pkg_id)
|
|
||||||
},
|
|
||||||
"core:change-packages-path": async (event) => {
|
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog(undefined, {
|
|
||||||
properties: ["openDirectory"]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (canceled) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetPath = path.resolve(filePaths[0], "RelicPackages")
|
|
||||||
|
|
||||||
await global.Settings.set("packages_path", targetPath)
|
|
||||||
|
|
||||||
return targetPath
|
|
||||||
},
|
|
||||||
"core:set-default-packages-path": async (event) => {
|
|
||||||
const { packages_path } = globalThis.relic_core.vars
|
|
||||||
|
|
||||||
await global.Settings.set("packages_path", packages_path)
|
|
||||||
|
|
||||||
return packages_path
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
coreEvents = {
|
|
||||||
"pkg:new": (pkg) => {
|
|
||||||
sendToRender("pkg:new", pkg)
|
|
||||||
},
|
|
||||||
"pkg:remove": (pkg) => {
|
|
||||||
sendToRender("pkg:remove", pkg)
|
|
||||||
},
|
|
||||||
"pkg:update:state": (data = {}) => {
|
|
||||||
if (!data.id) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.use_id_only === true) {
|
|
||||||
return sendToRender(`pkg:update:state:${data.id}`, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sendToRender("pkg:update:state", data)
|
|
||||||
},
|
|
||||||
"pkg:new:done": (pkg) => {
|
|
||||||
sendToRender("pkg:new:done", pkg)
|
|
||||||
},
|
|
||||||
"app:setup": (data) => {
|
|
||||||
sendToRender("app:setup", data)
|
|
||||||
},
|
|
||||||
"auth:getter:error": (err) => {
|
|
||||||
let str = "Failed to authorize"
|
|
||||||
|
|
||||||
if (err.response.data.message) {
|
|
||||||
str = err.response.data.message
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.response.data.error) {
|
|
||||||
str = err.response.data.error
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str.message) {
|
|
||||||
str = str.message
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToRender(`new:notification`, {
|
|
||||||
type: "error",
|
|
||||||
message: "Failed to authorize",
|
|
||||||
description: str,
|
|
||||||
duration: 10
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"pkg:authorized": (pkg) => {
|
|
||||||
sendToRender(`new:notification`, {
|
|
||||||
type: "success",
|
|
||||||
message: "Package authorized",
|
|
||||||
description: `${pkg.name} has been authorized! You can start the package now.`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"pkg:deauthorized": (pkg) => {
|
|
||||||
sendToRender(`new:notification`, {
|
|
||||||
type: "success",
|
|
||||||
message: "Package deauthorized",
|
|
||||||
description: `${pkg.name} has been deauthorized`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"pkg:error": (data) => {
|
|
||||||
sendToRender(`new:notification`, {
|
|
||||||
type: "error",
|
|
||||||
message: `An error occurred`,
|
|
||||||
description: `Something failed to ${data.event} package ${data.id}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
sendToRender(`pkg:update:state`, data)
|
|
||||||
},
|
|
||||||
"logger:new": (data) => {
|
|
||||||
if (this.loggerWindow) {
|
|
||||||
this.loggerWindow.webContents.send("logger:new", data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachLogger = (window) => {
|
|
||||||
this.loggerWindow = window
|
|
||||||
|
|
||||||
window.webContents.send("logger:new", {
|
|
||||||
timestamp: new Date().getTime(),
|
|
||||||
message: "Core adapter attached...",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
detachLogger = () => {
|
|
||||||
this.loggerWindow = null
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize = async () => {
|
|
||||||
if (this.initialized) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, handler] of Object.entries(this.coreEvents)) {
|
|
||||||
global._relic_eventBus.on(key, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, handler] of Object.entries(this.ipcEvents)) {
|
|
||||||
ipcMain.handle(key, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.core.initialize()
|
|
||||||
await this.core.setup()
|
|
||||||
|
|
||||||
this.initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
deinitialize = () => {
|
|
||||||
for (const [key, handler] of Object.entries(this.coreEvents)) {
|
|
||||||
global._relic_eventBus.off(key, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, handler] of Object.entries(this.ipcEvents)) {
|
|
||||||
ipcMain.removeHandler(key, handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,301 +0,0 @@
|
|||||||
import path from "node:path"
|
|
||||||
|
|
||||||
import { app, shell, BrowserWindow, ipcMain } from "electron"
|
|
||||||
import { electronApp, optimizer, is } from "@electron-toolkit/utils"
|
|
||||||
import isDev from "electron-is-dev"
|
|
||||||
|
|
||||||
let RelicCore = null
|
|
||||||
let Settings = null
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
RelicCore = require("../../../core/dist").default
|
|
||||||
Settings = global.Settings = require("../../../core/dist/classes/Settings").default
|
|
||||||
} else {
|
|
||||||
RelicCore = require("@ragestudio/relic-core").default
|
|
||||||
Settings = global.Settings = require("@ragestudio/relic-core/dist/classes/Settings").default
|
|
||||||
}
|
|
||||||
|
|
||||||
import CoreAdapter from "./classes/CoreAdapter"
|
|
||||||
import sendToRender from "./utils/sendToRender"
|
|
||||||
import pkg from "../../package.json"
|
|
||||||
|
|
||||||
const { autoUpdater } = require("electron-differential-updater")
|
|
||||||
const ProtocolRegistry = require("protocol-registry")
|
|
||||||
|
|
||||||
const protocolRegistryNamespace = "relic"
|
|
||||||
|
|
||||||
class LogsViewer {
|
|
||||||
window = null
|
|
||||||
|
|
||||||
async createWindow() {
|
|
||||||
this.window = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
show: false,
|
|
||||||
resizable: true,
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
icon: "../../resources/icon.png",
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, "../preload/index.js"),
|
|
||||||
sandbox: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
|
||||||
this.window.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}/#logs`)
|
|
||||||
} else {
|
|
||||||
this.window.loadFile(path.join(__dirname, "../renderer/index.html"), {
|
|
||||||
hash: "#logs",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve) => this.window.once("ready-to-show", resolve))
|
|
||||||
|
|
||||||
this.window.show()
|
|
||||||
|
|
||||||
return this.window
|
|
||||||
}
|
|
||||||
|
|
||||||
closeWindow() {
|
|
||||||
if (this.window) {
|
|
||||||
this.window.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ElectronApp {
|
|
||||||
constructor() {
|
|
||||||
this.core = new RelicCore()
|
|
||||||
this.adapter = new CoreAdapter(this, this.core)
|
|
||||||
}
|
|
||||||
|
|
||||||
window = null
|
|
||||||
|
|
||||||
logsViewer = new LogsViewer()
|
|
||||||
|
|
||||||
handlers = {
|
|
||||||
"updater:check": () => {
|
|
||||||
autoUpdater.checkForUpdates()
|
|
||||||
},
|
|
||||||
"updater:apply": () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
autoUpdater.quitAndInstall()
|
|
||||||
}, 3000)
|
|
||||||
},
|
|
||||||
"settings:get": async (event, key) => {
|
|
||||||
return await Settings.get(key)
|
|
||||||
},
|
|
||||||
"settings:set": async (event, key, value) => {
|
|
||||||
return await Settings.set(key, value)
|
|
||||||
},
|
|
||||||
"settings:delete": async (event, key) => {
|
|
||||||
return await Settings.delete(key)
|
|
||||||
},
|
|
||||||
"settings:has": async (event, key) => {
|
|
||||||
return await Settings.has(key)
|
|
||||||
},
|
|
||||||
"app:open-logs": async (event) => {
|
|
||||||
const loggerWindow = await this.logsViewer.createWindow()
|
|
||||||
|
|
||||||
this.adapter.attachLogger(loggerWindow)
|
|
||||||
|
|
||||||
loggerWindow.on("closed", () => {
|
|
||||||
this.adapter.detachLogger()
|
|
||||||
})
|
|
||||||
|
|
||||||
loggerWindow.webContents.send("logger:new", {
|
|
||||||
timestamp: new Date().getTime(),
|
|
||||||
message: "Logger opened, starting watching logs",
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"app:init": async (event, data) => {
|
|
||||||
try {
|
|
||||||
await this.adapter.initialize()
|
|
||||||
|
|
||||||
return {
|
|
||||||
pkg: pkg,
|
|
||||||
authorizedServices: {}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
sendToRender("app:init:failed", {
|
|
||||||
message: "Initalization failed",
|
|
||||||
error: error,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createWindow() {
|
|
||||||
this.window = global.mainWindow = new BrowserWindow({
|
|
||||||
width: 450,
|
|
||||||
height: 670,
|
|
||||||
show: false,
|
|
||||||
resizable: false,
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
icon: "../../resources/icon.png",
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, "../preload/index.js"),
|
|
||||||
sandbox: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.window.on("ready-to-show", () => {
|
|
||||||
this.window.show()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.window.webContents.setWindowOpenHandler((details) => {
|
|
||||||
shell.openExternal(details.url)
|
|
||||||
|
|
||||||
return { action: "deny" }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
|
||||||
this.window.loadURL(process.env["ELECTRON_RENDERER_URL"])
|
|
||||||
} else {
|
|
||||||
this.window.loadFile(path.join(__dirname, "../renderer/index.html"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleURLProtocol(url) {
|
|
||||||
const urlStarter = `${protocolRegistryNamespace}://`
|
|
||||||
|
|
||||||
if (url.startsWith(urlStarter)) {
|
|
||||||
const urlValue = url.split(urlStarter)[1]
|
|
||||||
|
|
||||||
let explicitAction = urlValue.split("#")
|
|
||||||
|
|
||||||
// remove trailing slash for windows :(
|
|
||||||
if (explicitAction[0].endsWith("/")) {
|
|
||||||
explicitAction[0] = explicitAction[0].slice(0, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (explicitAction.length > 0) {
|
|
||||||
switch (explicitAction[0]) {
|
|
||||||
case "authorize": {
|
|
||||||
if (!explicitAction[2]) {
|
|
||||||
const [pkg_id, token] = explicitAction[1].split("%23")
|
|
||||||
return this.core.package.authorize(pkg_id, token)
|
|
||||||
} else {
|
|
||||||
return this.core.package.authorize(explicitAction[1], explicitAction[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return sendToRender("pkg:installation:invoked", explicitAction[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// by default if no action is specified, assume is a install action
|
|
||||||
return sendToRender("pkg:installation:invoked", urlValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnSecondInstance = async (event, commandLine, workingDirectory) => {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
|
||||||
if (this.window) {
|
|
||||||
if (this.window.isMinimized()) {
|
|
||||||
this.window.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.window.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Second instance >`, commandLine)
|
|
||||||
|
|
||||||
const url = commandLine.pop()
|
|
||||||
|
|
||||||
await this.handleURLProtocol(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
// Set app user model id for windows
|
|
||||||
electronApp.setAppUserModelId("com.electron")
|
|
||||||
|
|
||||||
const gotTheLock = await app.requestSingleInstanceLock()
|
|
||||||
|
|
||||||
if (!gotTheLock) {
|
|
||||||
return app.quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in this.handlers) {
|
|
||||||
ipcMain.handle(key, this.handlers[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on("second-instance", this.handleOnSecondInstance)
|
|
||||||
|
|
||||||
app.on("open-url", (event, url) => {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
this.handleURLProtocol(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on("browser-window-created", (_, window) => {
|
|
||||||
optimizer.watchWindowShortcuts(window)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on("activate", () => {
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
this.createWindow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on("window-all-closed", () => {
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await app.whenReady()
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
if (app.isDefaultProtocolClient(protocolRegistryNamespace)) {
|
|
||||||
app.removeAsDefaultProtocolClient(protocolRegistryNamespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtocolRegistry.register({
|
|
||||||
protocol: protocolRegistryNamespace,
|
|
||||||
command: `"${process.execPath}" "${path.resolve(process.argv[1])}" $_URL_`,
|
|
||||||
override: true,
|
|
||||||
script: true,
|
|
||||||
terminal: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (!app.isDefaultProtocolClient(protocolRegistryNamespace)) {
|
|
||||||
app.setAsDefaultProtocolClient(protocolRegistryNamespace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.createWindow()
|
|
||||||
|
|
||||||
if (!isDev) {
|
|
||||||
autoUpdater.on("update-available", (ev, info) => {
|
|
||||||
console.log(info)
|
|
||||||
sendToRender("app:checking_update_downloading", info)
|
|
||||||
})
|
|
||||||
|
|
||||||
autoUpdater.on("error", (ev, err) => {
|
|
||||||
console.error(err)
|
|
||||||
sendToRender("app:checking_update_error")
|
|
||||||
})
|
|
||||||
|
|
||||||
autoUpdater.on("update-downloaded", (ev, info) => {
|
|
||||||
console.log(info)
|
|
||||||
sendToRender("app:update_available", info)
|
|
||||||
})
|
|
||||||
|
|
||||||
sendToRender("app:checking_update")
|
|
||||||
|
|
||||||
await autoUpdater.checkForUpdates()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new ElectronApp().initialize()
|
|
@ -1,39 +0,0 @@
|
|||||||
import lodash from "lodash"
|
|
||||||
|
|
||||||
const forbidden = [
|
|
||||||
"libraries"
|
|
||||||
]
|
|
||||||
|
|
||||||
export default (event, data) => {
|
|
||||||
try {
|
|
||||||
function serializeIpc(data) {
|
|
||||||
if (!data) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
data = JSON.stringify(data)
|
|
||||||
|
|
||||||
data = JSON.parse(data)
|
|
||||||
|
|
||||||
const copy = lodash.cloneDeep(data)
|
|
||||||
|
|
||||||
if (!Array.isArray(copy)) {
|
|
||||||
Object.keys(copy).forEach((key) => {
|
|
||||||
if (forbidden.includes(key)) {
|
|
||||||
delete copy[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof copy[key] === "function") {
|
|
||||||
delete copy[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
global.mainWindow.webContents.send(event, serializeIpc(data))
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 330 KiB |
@ -1,15 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>Relic</title>
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,181 +0,0 @@
|
|||||||
import GlobalApp from "./GlobalApp.jsx"
|
|
||||||
|
|
||||||
import React from "react"
|
|
||||||
import * as antd from "antd"
|
|
||||||
|
|
||||||
import versions from "utils/getVersions"
|
|
||||||
import GlobalStateContext from "contexts/global"
|
|
||||||
|
|
||||||
import AppLayout from "layout"
|
|
||||||
import AppModalDialog from "layout/components/ModalDialog"
|
|
||||||
import AppDrawer from "layout/components/Drawer"
|
|
||||||
|
|
||||||
import { InternalRouter, PageRender } from "./router.jsx"
|
|
||||||
|
|
||||||
import CrashError from "components/Crash"
|
|
||||||
import LogsViewer from "./pages/logs"
|
|
||||||
|
|
||||||
// create a global app context
|
|
||||||
window.app = GlobalApp
|
|
||||||
|
|
||||||
class App extends React.Component {
|
|
||||||
state = {
|
|
||||||
pkg: null,
|
|
||||||
|
|
||||||
crash: null,
|
|
||||||
initializing: true,
|
|
||||||
|
|
||||||
appSetup: {
|
|
||||||
error: false,
|
|
||||||
installed: false,
|
|
||||||
message: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
appUpdate: {
|
|
||||||
changelog: null,
|
|
||||||
available: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
authorizedServices: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcEvents = {
|
|
||||||
"new:notification": (event, data) => {
|
|
||||||
app.notification[data.type || "info"]({
|
|
||||||
message: data.message,
|
|
||||||
description: data.description,
|
|
||||||
loading: data.loading,
|
|
||||||
duration: data.duration,
|
|
||||||
icon: data.icon,
|
|
||||||
placement: "bottomLeft"
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"new:message": (event, data) => {
|
|
||||||
antd.message[data.type || "info"](data.message)
|
|
||||||
},
|
|
||||||
"app:setup": (event, data) => {
|
|
||||||
this.setState({
|
|
||||||
appSetup: data,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"app:update_available": (event, data) => {
|
|
||||||
if (this.state.initializing) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
appUpdate: {
|
|
||||||
available: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
app.appUpdateAvailable(data)
|
|
||||||
},
|
|
||||||
"pkg:install:ask": (event, data) => {
|
|
||||||
if (this.state.initializing) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
app.pkgInstallWizard(data)
|
|
||||||
},
|
|
||||||
"pkg:update_available": (event, data) => {
|
|
||||||
if (this.state.initializing) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
app.pkgUpdateAvailable(data)
|
|
||||||
},
|
|
||||||
"pkg:installation:invoked": (event, data) => {
|
|
||||||
if (this.state.initializing) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
app.invokeInstall(data)
|
|
||||||
},
|
|
||||||
"app:init:failed": (event, data) => {
|
|
||||||
this.setState({
|
|
||||||
crash: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount = async () => {
|
|
||||||
console.log(`React version > ${versions["react"]}`)
|
|
||||||
console.log(`DOMRouter version > ${versions["react-router-dom"]}`)
|
|
||||||
|
|
||||||
if (window.location.hash === "#logs") {
|
|
||||||
return await this.setState({
|
|
||||||
initializing: false,
|
|
||||||
no_layout: true,
|
|
||||||
log_viewer_mode: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window.app.style.appendClassname("initializing")
|
|
||||||
|
|
||||||
for (const event in this.ipcEvents) {
|
|
||||||
ipc.exclusiveListen(event, this.ipcEvents[event])
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainInitialization = await ipc.exec("app:init")
|
|
||||||
|
|
||||||
console.log(`app:init() | Result >`, mainInitialization)
|
|
||||||
|
|
||||||
if (mainInitialization.error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.setState({
|
|
||||||
initializing: false,
|
|
||||||
pkg: mainInitialization.pkg,
|
|
||||||
})
|
|
||||||
|
|
||||||
app.location.push("/")
|
|
||||||
|
|
||||||
window.app.style.removeClassname("initializing")
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <antd.ConfigProvider
|
|
||||||
theme={{
|
|
||||||
token: {
|
|
||||||
colorPrimary: getRootCssVar("--primary-color"),
|
|
||||||
colorBgContainer: getRootCssVar("--background-color-primary"),
|
|
||||||
colorPrimaryBg: getRootCssVar("--background-color-primary"),
|
|
||||||
},
|
|
||||||
algorithm: antd.theme.darkAlgorithm
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
this.state.log_viewer_mode && <LogsViewer />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!this.state.log_viewer_mode && <>
|
|
||||||
<InternalRouter>
|
|
||||||
<GlobalStateContext.Provider value={this.state}>
|
|
||||||
{
|
|
||||||
!this.state.crash && <>
|
|
||||||
<AppDrawer />
|
|
||||||
<AppModalDialog />
|
|
||||||
|
|
||||||
<AppLayout>
|
|
||||||
<PageRender />
|
|
||||||
</AppLayout>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
this.state.crash && <CrashError
|
|
||||||
crash={this.state.crash}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</GlobalStateContext.Provider>
|
|
||||||
</InternalRouter>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</antd.ConfigProvider>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
|
@ -1,86 +0,0 @@
|
|||||||
import { notification, message, Modal } from "antd"
|
|
||||||
|
|
||||||
import ManifestInfo from "components/ManifestInfo"
|
|
||||||
import PackageUpdateAvailable from "components/PackageUpdateAvailable"
|
|
||||||
import InstallConfigAsk from "components/InstallConfigAsk"
|
|
||||||
|
|
||||||
import getRootCssVar from "utils/getRootCssVar"
|
|
||||||
|
|
||||||
globalThis.getRootCssVar = getRootCssVar
|
|
||||||
globalThis.notification = notification
|
|
||||||
globalThis.message = message
|
|
||||||
|
|
||||||
class GlobalStyleController {
|
|
||||||
static root = document.getElementById("root")
|
|
||||||
|
|
||||||
static appendClassname = (classname) => {
|
|
||||||
console.log(`appending classname >`, classname)
|
|
||||||
GlobalStyleController.root.classList.add(classname)
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeClassname = (classname) => {
|
|
||||||
console.log(`removing classname >`, classname)
|
|
||||||
GlobalStyleController.root.classList.remove(classname)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getRootCssVar = getRootCssVar
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class GlobalCTXApp {
|
|
||||||
static style = GlobalStyleController
|
|
||||||
|
|
||||||
static invokeInstall = (manifest) => {
|
|
||||||
console.log(`installation invoked >`, manifest)
|
|
||||||
|
|
||||||
app.drawer.open(ManifestInfo, {
|
|
||||||
title: "New installation",
|
|
||||||
props: {
|
|
||||||
manifest: manifest,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static pkgUpdateAvailable = (update_data) => {
|
|
||||||
app.drawer.open(PackageUpdateAvailable, {
|
|
||||||
title: "Update Available",
|
|
||||||
props: {
|
|
||||||
update: update_data,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static pkgInstallWizard = (manifest) => {
|
|
||||||
app.drawer.open(InstallConfigAsk, {
|
|
||||||
title: "Configure installation",
|
|
||||||
props: {
|
|
||||||
manifest: manifest,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static appUpdateAvailable = (update_data) => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: "Update Available",
|
|
||||||
content: <>
|
|
||||||
<p>
|
|
||||||
A new version of the application is available.
|
|
||||||
</p>
|
|
||||||
</>,
|
|
||||||
okText: "Update",
|
|
||||||
cancelText: "Later",
|
|
||||||
onOk: () => {
|
|
||||||
app.applyUpdate()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static applyUpdate = () => {
|
|
||||||
message.loading("Updating, please wait...")
|
|
||||||
|
|
||||||
ipc.exec("updater:apply")
|
|
||||||
}
|
|
||||||
|
|
||||||
static checkUpdates = () => {
|
|
||||||
ipc.exec("updater:check")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
const Crash = (props) => {
|
|
||||||
const { crash } = props
|
|
||||||
|
|
||||||
return <div className="app-crash">
|
|
||||||
<div className="crash-icon">
|
|
||||||
<img
|
|
||||||
src="/assets/bruh_fox.jpg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>Crash</h1>
|
|
||||||
<p>The application has encontered a critical error that cannot handle it, so must be terminated.</p>
|
|
||||||
|
|
||||||
<div className="crash-details">
|
|
||||||
<p>Detailed error:</p>
|
|
||||||
|
|
||||||
<code>
|
|
||||||
{JSON.stringify(crash, null, 2)}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Crash
|
|
@ -1,58 +0,0 @@
|
|||||||
.app-crash {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crash-icon {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
|
|
||||||
object-fit: contain;
|
|
||||||
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.crash-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
gap: 7px;
|
|
||||||
|
|
||||||
code {
|
|
||||||
background-color: var(--background-color-secondary);
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
font-family: "DM Mono", monospace;
|
|
||||||
|
|
||||||
font-size: 0.8rem;
|
|
||||||
|
|
||||||
word-break: break-all;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import * as antd from "antd"
|
|
||||||
import { BarLoader } from "react-spinners"
|
|
||||||
import GlobalStateContext from "contexts/global"
|
|
||||||
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
const Splash = (props) => {
|
|
||||||
const globalState = React.useContext(GlobalStateContext)
|
|
||||||
|
|
||||||
return <div className="splash">
|
|
||||||
{
|
|
||||||
!!globalState.appSetup.message && <div className="app-setup_header">
|
|
||||||
<h1>
|
|
||||||
Setting up...
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
Please wait while the application is being set up.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
globalState.appSetup.message && <>
|
|
||||||
<div className="app-setup_message-wrapper">
|
|
||||||
<div className="app-setup_message">
|
|
||||||
<span>
|
|
||||||
{globalState.appSetup.message}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<BarLoader
|
|
||||||
className="app_loader"
|
|
||||||
color={getRootCssVar("--primary-color")}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!globalState.appSetup.message && <antd.Skeleton
|
|
||||||
active
|
|
||||||
round
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Splash
|
|
@ -1,46 +0,0 @@
|
|||||||
.splash {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
.app-setup_header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
padding-top: 30px;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.7rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-setup_message-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.app-setup_message {
|
|
||||||
padding: 15px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
|
|
||||||
background-color: var(--background-color-secondary);
|
|
||||||
|
|
||||||
font-family: "DM Mono", monospace;
|
|
||||||
|
|
||||||
word-break: break-all;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import { Drawer } from "antd"
|
|
||||||
|
|
||||||
const AppDrawer = () => {
|
|
||||||
const [render, setRender] = React.useState(null)
|
|
||||||
|
|
||||||
const c_interface = {
|
|
||||||
open: (component, params) => {
|
|
||||||
setRender({
|
|
||||||
component: component,
|
|
||||||
params: params ?? {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
close: () => {
|
|
||||||
setRender(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
window.app.drawer = c_interface
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.app.drawer = null
|
|
||||||
delete window.app.drawer
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <Drawer
|
|
||||||
open={render !== null}
|
|
||||||
onClose={() => c_interface.close()}
|
|
||||||
destroyOnClose
|
|
||||||
placement="bottom"
|
|
||||||
title={render && render.params && render.params.title}
|
|
||||||
height={render && render.params && render.params.height}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
render && render.component && React.createElement(render.component, {
|
|
||||||
...render.params.props ?? {},
|
|
||||||
close: () => c_interface.close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Drawer>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppDrawer
|
|
@ -1,68 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import * as antd from "antd"
|
|
||||||
|
|
||||||
import { MdAdd } from "react-icons/md"
|
|
||||||
|
|
||||||
import { Context as InstallationsContext, WithContext } from "contexts/packages"
|
|
||||||
|
|
||||||
import PackageItem from "components/PackageItem"
|
|
||||||
import NewInstallation from "components/NewInstallation"
|
|
||||||
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
class Packages extends React.Component {
|
|
||||||
static contextType = InstallationsContext
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { packages, loading } = this.context
|
|
||||||
|
|
||||||
const empty = packages.length == 0
|
|
||||||
|
|
||||||
return <div className="packages">
|
|
||||||
<div className="packages-header">
|
|
||||||
<antd.Button
|
|
||||||
type="primary"
|
|
||||||
icon={<MdAdd />}
|
|
||||||
onClick={() => app.drawer.open(NewInstallation, {
|
|
||||||
title: "Install new package",
|
|
||||||
height: "200px",
|
|
||||||
})}
|
|
||||||
className="add-btn"
|
|
||||||
>
|
|
||||||
Add new
|
|
||||||
</antd.Button>
|
|
||||||
|
|
||||||
<antd.Input.Search
|
|
||||||
variant="filled"
|
|
||||||
placeholder="Search"
|
|
||||||
onSearch={() => { }}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={empty ? "packages-list empty" : "packages-list"}>
|
|
||||||
{
|
|
||||||
loading && <antd.Skeleton active round />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!loading && empty && <antd.Empty description="No packages installed" />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!loading && packages.map((manifest) => {
|
|
||||||
return <PackageItem key={manifest.id} manifest={manifest} />
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PackagesPage = (props) => {
|
|
||||||
return <WithContext>
|
|
||||||
<Packages {...props} />
|
|
||||||
</WithContext>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PackagesPage
|
|
@ -1,71 +0,0 @@
|
|||||||
.packages {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.packages-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.ant-btn-default {
|
|
||||||
background-color: var(--background-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-btn {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 12px;
|
|
||||||
gap: 0px;
|
|
||||||
|
|
||||||
span:not(.ant-btn-icon) {
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
transition: all 150ms ease;
|
|
||||||
|
|
||||||
max-width: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
gap: 7px;
|
|
||||||
|
|
||||||
span:not(.ant-btn-icon) {
|
|
||||||
opacity: 1;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn-icon {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.packages-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
background-color: var(--background-color-secondary);
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
&.empty {
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
const Timestamp = ({ timestamp }) => {
|
|
||||||
if (isNaN(timestamp)) {
|
|
||||||
return <span className="timestamp">{timestamp}</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span
|
|
||||||
className="timestamp"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
new Date(timestamp).toLocaleString().split(", ").join("|")
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
const LogEntry = ({ log }) => {
|
|
||||||
return <div className="log-entry">
|
|
||||||
<span className="line_indicator">
|
|
||||||
{">"}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{log.timestamp && <Timestamp timestamp={log.timestamp} />}
|
|
||||||
|
|
||||||
{!log.timestamp && <span className="timestamp">- no timestamp -</span>}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{log.message ?? "No message"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const LogsViewer = () => {
|
|
||||||
const listRef = React.useRef()
|
|
||||||
const [timeline, setTimeline] = React.useState([])
|
|
||||||
|
|
||||||
const events = {
|
|
||||||
"logger:new": (event, log) => {
|
|
||||||
setTimeline((timeline) => [...timeline, log])
|
|
||||||
|
|
||||||
listRef.current.scrollTop = listRef.current.scrollHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
for (const event in events) {
|
|
||||||
ipc.exclusiveListen(event, events[event])
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div
|
|
||||||
className="app-logs"
|
|
||||||
ref={listRef}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
timeline.length === 0 && <p>No logs</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
timeline.map((log) => <LogEntry key={log.id} log={log} />)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LogsViewer
|
|
@ -1,47 +0,0 @@
|
|||||||
.app-logs {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
font-family: "DM Mono", monospace;
|
|
||||||
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
.log-entry {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
gap: 7px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
line-height: 0.8rem;
|
|
||||||
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
opacity: 0.9;
|
|
||||||
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(odd) {
|
|
||||||
background-color: var(--background-color-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,157 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import { Button } from "antd"
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
id: "services",
|
|
||||||
name: "Services",
|
|
||||||
icon: "MdAccountTree",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: "drive_auth",
|
|
||||||
name: "Google Drive",
|
|
||||||
description: "Authorize your Google Drive account to be used for bundles installation.",
|
|
||||||
icon: "SiGoogledrive",
|
|
||||||
type: "button",
|
|
||||||
storaged: false,
|
|
||||||
watchIpc: ["drive:authorized", "drive:unauthorized"],
|
|
||||||
defaultValue: async () => {
|
|
||||||
return await api.settings.get("drive_auth")
|
|
||||||
},
|
|
||||||
render: (props) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
disabled
|
|
||||||
type={props.value ? "primary" : "default"}
|
|
||||||
onClick={() => {
|
|
||||||
if (!props.value) {
|
|
||||||
message.info("Authorizing...")
|
|
||||||
|
|
||||||
return ipc.exec("drive:authorize")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipc.exec("drive:unauthorize")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.value ? "Unauthorize" : "Authorize"}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "updates",
|
|
||||||
name: "Updates",
|
|
||||||
icon: "MdUpdate",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: "check_update",
|
|
||||||
name: "Check for updates",
|
|
||||||
description: "Check for updates to the app.",
|
|
||||||
icon: "MdUpdate",
|
|
||||||
type: "button",
|
|
||||||
props: {
|
|
||||||
children: "Check",
|
|
||||||
onClick: () => {
|
|
||||||
message.info("Checking for updates...")
|
|
||||||
app.checkUpdates()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
storaged: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "pkg_auto_update_on_execute",
|
|
||||||
name: "Packages auto update",
|
|
||||||
description: "If a update is available, automatically update the app when it is executed.",
|
|
||||||
icon: "MdUpdate",
|
|
||||||
type: "switch",
|
|
||||||
storaged: true,
|
|
||||||
defaultValue: false,
|
|
||||||
props: {
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "other",
|
|
||||||
name: "Other",
|
|
||||||
icon: "MdSettings",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: "change_packages_path",
|
|
||||||
name: "Change packages path",
|
|
||||||
description: "Change the folder where all packages will be installed.",
|
|
||||||
icon: "MdFolder",
|
|
||||||
type: "button",
|
|
||||||
defaultValue: async () => {
|
|
||||||
return await ipc.exec("settings:get", "packages_path")
|
|
||||||
},
|
|
||||||
render: (props) => {
|
|
||||||
return <>
|
|
||||||
<Button
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
onClick={async () => {
|
|
||||||
const path = await ipc.exec("core:change-packages-path")
|
|
||||||
|
|
||||||
if (path) {
|
|
||||||
props.handleChange(path)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Change
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
onClick={async () => {
|
|
||||||
const path = await ipc.exec("core:set-default-packages-path")
|
|
||||||
|
|
||||||
if (path) {
|
|
||||||
props.handleChange(path)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
},
|
|
||||||
footer: (props) => {
|
|
||||||
return <span style={{ fontSize: "0.6rem" }}>{props.value}</span>
|
|
||||||
},
|
|
||||||
storaged: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "open_settings_path",
|
|
||||||
name: "Open settings path",
|
|
||||||
description: "Open the folder where all packages are stored.",
|
|
||||||
icon: "MdFolder",
|
|
||||||
type: "button",
|
|
||||||
props: {
|
|
||||||
children: "Open",
|
|
||||||
onClick: () => {
|
|
||||||
ipc.exec("core:open-path")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
storaged: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "open_dev_logs",
|
|
||||||
name: "Open internal logs",
|
|
||||||
description: "Open the internal logs of the app.",
|
|
||||||
icon: "MdTerminal",
|
|
||||||
type: "button",
|
|
||||||
props: {
|
|
||||||
children: "Open",
|
|
||||||
onClick: () => {
|
|
||||||
ipc.exec("app:open-logs")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
storaged: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
0
packages/gui/resources/icon.ico → resources/icon.ico
Executable file → Normal file
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
BIN
resources/icon.png
Normal file
After Width: | Height: | Size: 28 KiB |
0
packages/gui/resources/icon.svg → resources/icon.svg
Executable file → Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,35 +0,0 @@
|
|||||||
const path = require("path")
|
|
||||||
const child_process = require("child_process")
|
|
||||||
|
|
||||||
const packagesPath = path.resolve(__dirname, "..", "packages")
|
|
||||||
|
|
||||||
const linkRoot = path.resolve(packagesPath, "core")
|
|
||||||
|
|
||||||
const linkPackages = [
|
|
||||||
path.resolve(packagesPath, "cli"),
|
|
||||||
path.resolve(packagesPath, "gui"),
|
|
||||||
]
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
console.log(`Linking @core to other packages...`)
|
|
||||||
|
|
||||||
const rootPkg = require(path.resolve(linkRoot, "package.json"))
|
|
||||||
|
|
||||||
await child_process.execSync("yarn link", {
|
|
||||||
cwd: linkRoot,
|
|
||||||
stdio: "inherit",
|
|
||||||
stdout: "inherit",
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const linkPackage of linkPackages) {
|
|
||||||
await child_process.execSync(`yarn link "${rootPkg.name}"`, {
|
|
||||||
cwd: linkPackage,
|
|
||||||
stdio: "inherit",
|
|
||||||
stdout: "inherit",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Done!`)
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
139
src/main/commands/apply.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
|
||||||
|
import sendToRender from "../utils/sendToRender"
|
||||||
|
import initManifest from "../utils/initManifest"
|
||||||
|
import parseStringVars from "../utils/parseStringVars"
|
||||||
|
import processGenericSteps from "../generic_steps"
|
||||||
|
|
||||||
|
import {
|
||||||
|
updateInstalledPackage,
|
||||||
|
getInstalledPackages,
|
||||||
|
} from "../local_db"
|
||||||
|
|
||||||
|
export default async function apply(pkg_id, changes) {
|
||||||
|
let pkg = await getInstalledPackages(pkg_id)
|
||||||
|
|
||||||
|
if (!pkg) {
|
||||||
|
sendToRender("runtime:error", "Package not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg = await initManifest(pkg)
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] apply() | Applying changes... >`, changes)
|
||||||
|
|
||||||
|
if (Array.isArray(changes.patches)) {
|
||||||
|
if (!Array.isArray(pkg.applied_patches)) {
|
||||||
|
pkg.applied_patches = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const disablePatches = pkg.patches.filter((p) => {
|
||||||
|
return !changes.patches[p.id]
|
||||||
|
})
|
||||||
|
|
||||||
|
const installPatches = pkg.patches.filter((p) => {
|
||||||
|
return changes.patches[p.id]
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (let patch of disablePatches) {
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "loading",
|
||||||
|
statusText: `Removing patch [${patch.id}]...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] apply() | Removing patch [${patch.id}]...`)
|
||||||
|
|
||||||
|
// remove patch additions
|
||||||
|
for await (let addition of patch.additions) {
|
||||||
|
// resolve patch file
|
||||||
|
addition.file = await parseStringVars(addition.file, pkg)
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] apply() | Removing addition [${addition.file}]...`)
|
||||||
|
|
||||||
|
if (!fs.existsSync(addition.file)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove addition
|
||||||
|
await fs.promises.unlink(addition.file, { force: true, recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove file patch overrides with original file
|
||||||
|
// remove from applied patches
|
||||||
|
pkg.applied_patches = pkg.applied_patches.filter((p) => {
|
||||||
|
return p !== patch.id
|
||||||
|
})
|
||||||
|
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "done",
|
||||||
|
statusText: `Patch [${patch.id}] removed!`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (let patch of installPatches) {
|
||||||
|
if (pkg.applied_patches.includes(patch.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "loading",
|
||||||
|
statusText: `Applying patch [${patch.id}]...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] apply() | Applying patch [${patch.id}]...`)
|
||||||
|
|
||||||
|
for await (let addition of patch.additions) {
|
||||||
|
console.log(addition)
|
||||||
|
|
||||||
|
// resolve patch file
|
||||||
|
addition.file = await parseStringVars(addition.file, pkg)
|
||||||
|
|
||||||
|
if (fs.existsSync(addition.file)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await processGenericSteps(pkg, addition.steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to applied patches
|
||||||
|
pkg.applied_patches.push(patch.id)
|
||||||
|
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "done",
|
||||||
|
statusText: `Patch [${patch.id}] applied!`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.configs) {
|
||||||
|
if (!pkg.storaged_configs) {
|
||||||
|
pkg.storaged_configs = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(changes.configs).length !== 0) {
|
||||||
|
Object.entries(changes.configs).forEach(([key, value]) => {
|
||||||
|
pkg.storaged_configs[key] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateInstalledPackage(pkg)
|
||||||
|
|
||||||
|
sendToRender(`new:notification`, {
|
||||||
|
type: "success",
|
||||||
|
message: "Changes applied!",
|
||||||
|
})
|
||||||
|
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
...pkg,
|
||||||
|
statusText: "Changes applied!",
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] apply() | Changes applied`)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
116
src/main/commands/execute.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
getInstalledPackages,
|
||||||
|
} from "../local_db"
|
||||||
|
|
||||||
|
import readManifest from "../utils/readManifest"
|
||||||
|
import initManifest from "../utils/initManifest"
|
||||||
|
import parseStringVars from "../utils/parseStringVars"
|
||||||
|
import sendToRender from "../utils/sendToRender"
|
||||||
|
|
||||||
|
import UpdateCMD from "./update"
|
||||||
|
|
||||||
|
export default async function execute(pkg_id, { force = false } = {}) {
|
||||||
|
let pkg = await getInstalledPackages(pkg_id)
|
||||||
|
|
||||||
|
if (!pkg) {
|
||||||
|
sendToRender("runtime:error", "Package not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "loading",
|
||||||
|
statusText: `Executing...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] execute() | Executing...`)
|
||||||
|
|
||||||
|
if (pkg.remote_url) {
|
||||||
|
pkg = {
|
||||||
|
...pkg,
|
||||||
|
...await readManifest(pkg, { just_read: true }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg = await initManifest(pkg)
|
||||||
|
|
||||||
|
if (pkg.check_updates_after_execute === true) {
|
||||||
|
if (pkg._original_manifest) {
|
||||||
|
if ((pkg._original_manifest.version !== pkg.version) && !force) {
|
||||||
|
console.log(`[${pkg_id}] execute() | Update available (${pkg._original_manifest.version} -> ${pkg.version}). Aborting...`,)
|
||||||
|
|
||||||
|
if (global.SettingsStore.get("pkg_auto_update_on_execute") === true) {
|
||||||
|
await UpdateCMD(pkg_id)
|
||||||
|
} else {
|
||||||
|
sendToRender("pkg:update_available", {
|
||||||
|
manifest: pkg._original_manifest,
|
||||||
|
current_version: pkg._original_manifest.version,
|
||||||
|
new_version: pkg.version,
|
||||||
|
})
|
||||||
|
|
||||||
|
sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installed",
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof pkg.after_execute === "function") {
|
||||||
|
await pkg.after_execute(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof pkg.execute === "string") {
|
||||||
|
pkg.execute = parseStringVars(pkg.execute, pkg)
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] execute() | Executing binary from path >`, pkg.execute)
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const process = child_process.execFile(pkg.execute, [], {
|
||||||
|
shell: true,
|
||||||
|
cwd: pkg.install_path,
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("exit", resolve)
|
||||||
|
process.on("error", reject)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (typeof pkg.execute !== "function") {
|
||||||
|
sendToRender("installation:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "error",
|
||||||
|
statusText: "No execute function found",
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
await pkg.execute(pkg)
|
||||||
|
} catch (error) {
|
||||||
|
sendToRender("new:notification", {
|
||||||
|
type: "error",
|
||||||
|
message: "Failed to launch",
|
||||||
|
description: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installed",
|
||||||
|
statusText: `Failed to launch`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installed",
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] execute() | Successfully executed`)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
126
src/main/commands/install.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
|
||||||
|
import readManifest from "../utils/readManifest"
|
||||||
|
import initManifest from "../utils/initManifest"
|
||||||
|
import sendToRender from "../utils/sendToRender"
|
||||||
|
|
||||||
|
import defaultManifest from "../defaults/pkg_manifest"
|
||||||
|
import processGenericSteps from "../generic_steps"
|
||||||
|
|
||||||
|
import applyChanges from "./apply"
|
||||||
|
|
||||||
|
import {
|
||||||
|
updateInstalledPackage,
|
||||||
|
} from "../local_db"
|
||||||
|
|
||||||
|
export default async function install(manifest) {
|
||||||
|
manifest = await readManifest(manifest).catch((error) => {
|
||||||
|
sendToRender("runtime:error", "Cannot fetch this manifest")
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkg = {
|
||||||
|
...defaultManifest,
|
||||||
|
...manifest,
|
||||||
|
status: "installing",
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkg_id = pkg.id
|
||||||
|
|
||||||
|
sendToRender("pkg:new", pkg)
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] install() | Starting to install...`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
pkg = await initManifest(pkg)
|
||||||
|
|
||||||
|
if (fs.existsSync(pkg.install_path)) {
|
||||||
|
await fs.rmSync(pkg.install_path, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.mkdirSync(pkg.install_path, { recursive: true })
|
||||||
|
|
||||||
|
// append to db
|
||||||
|
await updateInstalledPackage(pkg)
|
||||||
|
|
||||||
|
if (typeof pkg.before_install === "function") {
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installing",
|
||||||
|
statusText: `Performing before_install hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] install() | Performing before_install hook...`)
|
||||||
|
|
||||||
|
// execute before_install
|
||||||
|
await pkg.before_install(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installing",
|
||||||
|
statusText: `Performing install steps...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Execute generic install steps
|
||||||
|
await processGenericSteps(pkg, pkg.install_steps)
|
||||||
|
|
||||||
|
if (typeof pkg.after_install === "function") {
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installing",
|
||||||
|
statusText: `Performing after_install hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] install() | Performing after_install hook...`)
|
||||||
|
|
||||||
|
// execute after_install
|
||||||
|
await pkg.after_install(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.status = "installed"
|
||||||
|
pkg.installed_at = new Date()
|
||||||
|
|
||||||
|
// update to db
|
||||||
|
await updateInstalledPackage(pkg)
|
||||||
|
|
||||||
|
if (pkg.patches) {
|
||||||
|
// process default patches
|
||||||
|
const defaultPatches = pkg.patches.filter((patch) => patch.default)
|
||||||
|
|
||||||
|
await applyChanges(pkg.id, {
|
||||||
|
patches: Object.fromEntries(defaultPatches.map((patch) => [patch.id, true])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (Array.isArray(pkg.install_ask_configs)) {
|
||||||
|
// sendToRender("pkg:install:ask", pkg)
|
||||||
|
// }
|
||||||
|
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "installed",
|
||||||
|
})
|
||||||
|
|
||||||
|
sendToRender(`new:message`, {
|
||||||
|
message: `Successfully installed ${pkg.name}!`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] install() | Successfully installed ${pkg.name}!`)
|
||||||
|
} catch (error) {
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "error",
|
||||||
|
statusText: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
fs.rmdirSync(pkg.install_path, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
53
src/main/commands/uninstall.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
getInstalledPackages,
|
||||||
|
deleteInstalledPackage,
|
||||||
|
} from "../local_db"
|
||||||
|
|
||||||
|
import sendToRender from "../utils/sendToRender"
|
||||||
|
import readManifest from "../utils/readManifest"
|
||||||
|
import initManifest from "../utils/initManifest"
|
||||||
|
|
||||||
|
import { rimraf } from "rimraf"
|
||||||
|
|
||||||
|
export default async function uninstall(pkg_id) {
|
||||||
|
let pkg = await getInstalledPackages(pkg_id)
|
||||||
|
|
||||||
|
if (!pkg) {
|
||||||
|
sendToRender("runtime:error", "Package not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "uninstalling",
|
||||||
|
statusText: `Uninstalling...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`[${pkg_id}] uninstall() | Uninstalling...`)
|
||||||
|
|
||||||
|
if (pkg.remote_url) {
|
||||||
|
pkg = await readManifest(pkg.remote_url, { just_read: true })
|
||||||
|
|
||||||
|
if (typeof pkg.uninstall === "function") {
|
||||||
|
console.log(`Performing uninstall hook...`)
|
||||||
|
|
||||||
|
await pkg.uninstall(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg = await initManifest(pkg)
|
||||||
|
|
||||||
|
await deleteInstalledPackage(pkg_id)
|
||||||
|
|
||||||
|
await rimraf(pkg.install_path)
|
||||||
|
|
||||||
|
sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "uninstalling",
|
||||||
|
statusText: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
sendToRender("pkg:remove", {
|
||||||
|
id: pkg_id
|
||||||
|
})
|
||||||
|
}
|
108
src/main/commands/update.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import {
|
||||||
|
updateInstalledPackage,
|
||||||
|
getInstalledPackages,
|
||||||
|
} from "../local_db"
|
||||||
|
|
||||||
|
import readManifest from "../utils/readManifest"
|
||||||
|
import initManifest from "../utils/initManifest"
|
||||||
|
import sendToRender from "../utils/sendToRender"
|
||||||
|
|
||||||
|
import processGenericSteps from "../generic_steps"
|
||||||
|
|
||||||
|
export default async function update(pkg_id) {
|
||||||
|
// find package manifest
|
||||||
|
let pkg = await getInstalledPackages(pkg_id)
|
||||||
|
|
||||||
|
if (!pkg) {
|
||||||
|
sendToRender("runtime:error", "Package not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// output to logs
|
||||||
|
console.log(`[${pkg_id}] update() | Updating to latest version...`)
|
||||||
|
|
||||||
|
// update render
|
||||||
|
sendToRender("pkg:update:status", {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "loading",
|
||||||
|
statusText: `Updating to latest version...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// fulfill if remote available
|
||||||
|
if (pkg.remote_url) {
|
||||||
|
pkg = {
|
||||||
|
...pkg,
|
||||||
|
...await readManifest(pkg.remote_url, { just_read: true }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize package manifest
|
||||||
|
pkg = await initManifest(pkg)
|
||||||
|
|
||||||
|
// check if package manifest has a update function
|
||||||
|
if (typeof pkg.update === "function") {
|
||||||
|
// update render
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "loading",
|
||||||
|
statusText: `Performing update hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// output to logs
|
||||||
|
console.log(`[${pkg_id}] update() | Performing update hook`)
|
||||||
|
|
||||||
|
// execute update function
|
||||||
|
await pkg.update(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process generic steps
|
||||||
|
await processGenericSteps(pkg, pkg.update_steps)
|
||||||
|
|
||||||
|
// check if package manifest has an after_update function
|
||||||
|
if (typeof pkg.after_update === "function") {
|
||||||
|
// update render
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
id: pkg_id,
|
||||||
|
status: "loading",
|
||||||
|
statusText: `Performing after_update hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// output to logs
|
||||||
|
console.log(`[${pkg_id}] update() | Performing after_update hook`)
|
||||||
|
|
||||||
|
// execute after_update function
|
||||||
|
await pkg.after_update(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update package vars
|
||||||
|
pkg.status = "installed"
|
||||||
|
pkg.last_update = new Date()
|
||||||
|
|
||||||
|
// update package manifest on db
|
||||||
|
await updateInstalledPackage(pkg)
|
||||||
|
|
||||||
|
// update render
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
...pkg,
|
||||||
|
status: "installed",
|
||||||
|
})
|
||||||
|
|
||||||
|
sendToRender(`new:notification`, {
|
||||||
|
message: `(${pkg.name}) successfully updated!`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// output to logs
|
||||||
|
console.log(`[${pkg_id}] update() | Successfully updated!`)
|
||||||
|
} catch (error) {
|
||||||
|
// update render
|
||||||
|
sendToRender(`pkg:update:status`, {
|
||||||
|
...pkg,
|
||||||
|
status: "error",
|
||||||
|
statusText: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// output to logs
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
6
src/main/defaults/local_db.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import pkg from "../../../package.json"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
created_at_version: pkg.version,
|
||||||
|
packages: [],
|
||||||
|
}
|
19
src/main/defaults/pkg_manifest.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export default {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
icon: null,
|
||||||
|
version: null,
|
||||||
|
install_path: null,
|
||||||
|
remote_url: null,
|
||||||
|
last_update: null,
|
||||||
|
|
||||||
|
status: "pending",
|
||||||
|
statusText: "Pending...",
|
||||||
|
|
||||||
|
patches: [],
|
||||||
|
applied_patches: [],
|
||||||
|
|
||||||
|
configs: {},
|
||||||
|
storaged_configs: {}
|
||||||
|
}
|