Much dev later

This commit is contained in:
Jonatan Nilsson 2020-09-07 18:15:11 +00:00
parent 0b50d50f3b
commit 0c9870717d
6 changed files with 167 additions and 2694 deletions

View file

@ -1,7 +1,7 @@
import fs from 'fs' import fs from 'fs'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { request } from './client.mjs' import { request } from './client.mjs'
import { getPathFromRoot, runCommand } from './util.mjs' import { getPathFromRoot, getUrlFromRoot, runCommand } from './util.mjs'
const fsp = fs.promises const fsp = fs.promises
@ -16,10 +16,12 @@ export default class Core extends EventEmitter{
this._manageRunning = false this._manageRunning = false
this._appUpdating = { this._appUpdating = {
status: false, status: false,
starting: false,
logs: '', logs: '',
} }
this._manageUpdating = { this._manageUpdating = {
status: false, status: false,
starting: false,
logs: '', logs: '',
} }
} }
@ -39,7 +41,7 @@ export default class Core extends EventEmitter{
async getLatestVersion(active, name) { async getLatestVersion(active, name) {
// Example: 'https://api.github.com/repos/thething/sc-helloworld/releases' // Example: 'https://api.github.com/repos/thething/sc-helloworld/releases'
this.logActive(name, active, `Fetching release info from: https://api.github.com/repos/${this._config[name + 'Repository']}/releases\n`) this.logActive(name, active, `[Core] Fetching release info from: https://api.github.com/repos/${this._config[name + 'Repository']}/releases\n`)
let result = await request(`https://api.github.com/repos/${this._config[name + 'Repository']}/releases`) let result = await request(`https://api.github.com/repos/${this._config[name + 'Repository']}/releases`)
@ -56,7 +58,7 @@ export default class Core extends EventEmitter{
let item = items[x] let item = items[x]
for (let i = 0; i < item.assets.length; i++) { for (let i = 0; i < item.assets.length; i++) {
if (item.assets[i].name.endsWith('-sc.zip')) { if (item.assets[i].name.endsWith('-sc.zip')) {
this.logActive(name, active, `Found version ${item.name} with file ${item.assets[i].name}\n`) this.logActive(name, active, `[Core] Found version ${item.name} with file ${item.assets[i].name}\n`)
await this._db.set(`core.${name}LatestVersion`, item.name) await this._db.set(`core.${name}LatestVersion`, item.name)
.write() .write()
@ -105,48 +107,39 @@ export default class Core extends EventEmitter{
} }
async installVersion(name, active, version) { async installVersion(name, active, version) {
if (fs.existsSync(getPathFromRoot('./app/' + version.name))) { if (fs.existsSync(getPathFromRoot(`./${name}/` + version.name))) {
await runCommand('rmdir', ['/S', '/Q', `"${getPathFromRoot('./app/' + version.name)}"`]) await runCommand('rmdir', ['/S', '/Q', `"${getPathFromRoot(`./${name}/` + version.name)}"`])
} }
try { try {
await fsp.mkdir(getPathFromRoot('./app/' + version.name)) await fsp.mkdir(getPathFromRoot(`./${name}/` + version.name))
} catch(err) { } catch(err) {
if (err.code !== 'EEXIST') { if (err.code !== 'EEXIST') {
throw err throw err
} }
} }
// await fsp.mkdir(getPathFromRoot('./app/' + version.name + '/node_modules')) // await fsp.mkdir(getPathFromRoot(`./${name}/` + version.name + '/node_modules'))
this.logActive(name, active, `Downloading ${version.name} (${version.url}) to ${version.name + '/' + version.name + '.zip'}\n`) this.logActive(name, active, `[Core] Downloading ${version.name} (${version.url}) to ${version.name + '/' + version.name + '.zip'}\n`)
let filePath = getPathFromRoot('./app/' + version.name + '/' + version.name + '.zip') let filePath = getPathFromRoot(`./${name}/` + version.name + '/' + version.name + '.zip')
await request(version.url, filePath) await request(version.url, filePath)
this.logActive(name, active, `Downloading finished, starting extraction\n`) this.logActive(name, active, `[Core] Downloading finished, starting extraction\n`)
await runCommand( await runCommand(
'"C:\\Program Files\\7-Zip\\7z.exe"', '"C:\\Program Files\\7-Zip\\7z.exe"',
['x', `"${filePath}"`], ['x', `"${filePath}"`],
getPathFromRoot('./app/' + version.name + '/'), getPathFromRoot(`./${name}/` + version.name + '/'),
this.logActive.bind(this, name, active) this.logActive.bind(this, name, active)
) )
if (!fs.existsSync(getPathFromRoot('./app/' + version.name + '/index.mjs'))) { if (!fs.existsSync(getPathFromRoot(`./${name}/` + version.name + '/index.mjs'))) {
this.logActive(name, active, `\nERROR: Missing index.mjs in the folder, exiting\n`) this.logActive(name, active, `\n[Core] ERROR: Missing index.mjs in the folder, exiting\n`)
throw new Error(`Missing index.mjs in ${getPathFromRoot('./app/' + version.name + '/index.mjs')}`) throw new Error(`Missing index.mjs in ${getPathFromRoot(`./${name}/` + version.name + '/index.mjs')}`)
} }
this.logActive(name, active, `\nStarting npm install\n`) this.logActive(name, active, `\n[Core] Starting npm install\n`)
await runCommand( await runCommand(
'npm.cmd', 'npm.cmd',
['install', '--production', '--no-optional', '--no-package-lock', '--no-audit'], ['install', '--production', '--no-optional', '--no-package-lock', '--no-audit'],
getPathFromRoot('./app/' + version.name + '/'), getPathFromRoot(`./${name}/` + version.name + '/'),
this.logActive.bind(this, name, active)
)
this.logActive(name, active, `\nInstalled:\n`)
await runCommand(
'npm.cmd',
['list'],
getPathFromRoot('./app/' + version.name + '/'),
this.logActive.bind(this, name, active) this.logActive.bind(this, name, active)
) )
@ -154,10 +147,103 @@ export default class Core extends EventEmitter{
.write() .write()
this.emit('dbupdated', {}) this.emit('dbupdated', {})
this.logActive(name, active, `\nSuccessfully installed ${version.name}\n`) this.logActive(name, active, `\n[Core] Successfully installed ${version.name}\n`)
}
getActive(name) {
if (name === 'app') {
return this._appUpdating
} else if (name === 'manage') {
return this._manageUpdating
} else {
throw new Error('Invalid name: ' + name)
}
} }
async startProgram(name) { async startProgram(name) {
let active = this.getActive(name)
if ((name === 'app' && this._appRunning)
|| (name === 'manage' && this._manageRunning)
|| active.starting) {
this._log.event.warn('Attempting to start ' + name + ' which is already running')
this._log.warn('Attempting to start ' + name + ' which is already running')
this.logActive(name, active, `[${name}] Attempting to start it but it is already running\n`, true)
return
}
active.starting = true
let core = this._db.get('core').value()
let version = core[name + 'LatestInstalled']
if (await this.tryStartProgram(name, active, version)) return
version = core[name + 'LastActive']
if (await this.tryStartProgram(name, active,version)) return
this._log.error('Unable to start ' + name)
this._log.event.error('Unable to start ' + name)
active.starting = false
}
async tryStartProgram(name, active, version) {
if (!version) return false
this.logActive(name, active, `[${name}] Attempting to start ${version}\n`)
let indexPath = getUrlFromRoot(`./${name}/` + version + '/index.mjs')
let module
try {
this.logActive(name, active, `[${name}] Loading ${indexPath}\n`)
module = await import(indexPath)
} catch (err) {
this.logActive(name, active, `[${name}] Error importing module\n`, true)
this.logActive(name, active, `[${name}] ${err.stack}\n`, true)
this._log.error(err, `Failed to load ${indexPath}`)
return false
}
let checkTimeout = null
try {
await new Promise((res, rej) => {
try {
let checkTimeout = setTimeout(function() {
rej(new Error('Program took longer than 60 seconds to resolve promise'))
}, 60 * 1000)
this.logActive(name, active, `[${name}] Starting module\n`)
let out = module.start(this._config, this._db, this._log, this)
if (out.then) {
return out.then(res, rej)
} else {
res()
}
} catch (err) {
rej(err)
}
})
} catch (err) {
clearTimeout(checkTimeout)
this.logActive(name, active, `[${name}] Error starting\n`, true)
this.logActive(name, active, `[${name}] ${err.stack}\n`, true)
this._log.error(err, `Failed to start ${name}`)
return false
}
clearTimeout(checkTimeout)
this.logActive(name, active, `[${name}] Successfully started version ${version}\n`)
await this._db.set(`core.${name}Active`, version)
.write()
let port = name === 'app' ? this._config.port : this._config.managePort
this.logActive(name, active, `[${name}] Checking if listening to port ${port}\n`)
if (name === 'app') {
this._appRunning = true
} else {
this._manageRunning = true
}
this.logActive(name, active, `[${name}] Module is running successfully\n`)
return true
} }
async updateProgram(name) { async updateProgram(name) {
@ -172,35 +258,40 @@ export default class Core extends EventEmitter{
return return
} }
let active = null let active = this.getActive(name)
if (name === 'app') {
active = this._appUpdating
} else {
active = this._manageUpdating
}
active.status = true active.status = true
active.logs = '' active.logs = ''
this.emit('statusupdated', {}) this.emit('statusupdated', {})
this.logActive(name, active, 'Checking for updates...\n') this.logActive(name, active, `[Core] Time: ${new Date().toISOString().replace('T', ' ').split('.')[0]}\n`)
this.logActive(name, active, '[Core] Checking for updates...\n')
let version = null let version = null
let installed = false
let found = false
try { try {
version = await this.getLatestVersion(active, name) version = await this.getLatestVersion(active, name)
let core = this._db.get('core').value() let core = this._db.get('core').value()
if (!core[name + 'Current'] || (core[name + 'Current'] !== version.name && core[name + 'CurrentVersion'] !== version)) { let fromDb = this._db.get(`core_${name}History`).getById(version.name).value()
console.log(fromDb)
if (!fromDb || !fromDb.installed) {
let oldVersion = core[name + 'Current'] || '<none>' let oldVersion = core[name + 'Current'] || '<none>'
this.logActive(name, active, `Updating from ${oldVersion} to ${version.name}\n`) this.logActive(name, active, `[Core] Updating from ${oldVersion} to ${version.name}\n`)
await this.installVersion(name, active, version) await this.installVersion(name, active, version)
this.logActive(name, active, `[Core] Finished: ${new Date().toISOString().replace('T', ' ').split('.')[0]}\n`)
installed = new Date()
} else {
found = true
this.logActive(name, active, `[Core] Version ${version.name} already installed\n\n[Core] Logs from previous install:\n----------------------------------\n\n${fromDb.logs}\n----------------------------------\n[Core] Old logs finished`)
} }
} catch(err) { } catch(err) {
this.logActive(name, active, '\n', true) this.logActive(name, active, '\n', true)
this.logActive(name, active, `Exception occured while updating ${name}\n`, true) this.logActive(name, active, `[Error] Exception occured while updating ${name}\n`, true)
this.logActive(name, active, err.stack, true) this.logActive(name, active, err.stack, true)
this._log.error(err, 'Error while updating ' + name) this._log.error(err, 'Error while updating ' + name)
} }
active.status = false active.status = false
if (version) { if (version && !found) {
await this._db.get(`core_${name}History`).upsert({ await this._db.get(`core_${name}History`).upsert({
id: version.name, id: version.name,
name: version.name, name: version.name,
@ -208,6 +299,8 @@ export default class Core extends EventEmitter{
url: version.url, url: version.url,
description: version.description, description: version.description,
logs: active.logs, logs: active.logs,
stable: 0,
installed: installed,
}).write() }).write()
} }
this.emit('statusupdated', {}) this.emit('statusupdated', {})

View file

@ -1,14 +1,19 @@
import path from 'path' import path from 'path'
import { spawn } from 'child_process' import { spawn } from 'child_process'
import { fileURLToPath } from 'url' import { fileURLToPath, pathToFileURL } from 'url'
export function getPathFromRoot(add) { export function getPathFromRoot(add) {
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
return path.join(__dirname,'../', add) return path.join(__dirname,'../', add)
} }
export function getUrlFromRoot(add) {
return path.join(import.meta.url,'../../', add)
}
export function runCommand(command, options = [], folder = null, stream = function() {}) { export function runCommand(command, options = [], folder = null, stream = function() {}) {
return new Promise(function(res, rej) { return new Promise(function(res, rej) {
stream(`[Command] ${folder ? folder : ''}${command} ${options.join(' ')}\n`)
let processor = spawn(command, options, { let processor = spawn(command, options, {
shell: true, shell: true,
cwd: folder, cwd: folder,
@ -18,11 +23,9 @@ export function runCommand(command, options = [], folder = null, stream = functi
}, 250) }, 250)
processor.stdout.on('data', function(data) { processor.stdout.on('data', function(data) {
stream(data.toString()) stream(data.toString())
processor.stdin.write('n\n')
}) })
processor.stderr.on('data', function(data) { processor.stderr.on('data', function(data) {
stream(data.toString()) stream(data.toString())
processor.stdin.write('n\n')
}) })
processor.on('error', function(err) { processor.on('error', function(err) {
clearInterval(timeOuter) clearInterval(timeOuter)
@ -30,6 +33,9 @@ export function runCommand(command, options = [], folder = null, stream = functi
}) })
processor.on('exit', function (code) { processor.on('exit', function (code) {
clearInterval(timeOuter) clearInterval(timeOuter)
if (code !== 0) {
return rej(new Error('Program returned error code: ' + code))
}
res(code) res(code)
}) })
}) })

2
db.mjs
View file

@ -151,7 +151,7 @@ export default function GetDB(log) {
}, },
core_appHistory: [], core_appHistory: [],
core_manageHistory: [], core_manageHistory: [],
version: 1, core_version: 1,
}) })
.write() .write()
.then( .then(

View file

@ -61,6 +61,22 @@ export async function update(ctx, data, cb) {
} }
} }
/*
* Event: 'core.start'
*
* Start specific software
*/
export async function start(ctx, data, cb) {
if (data.name === 'app') {
await ctx.core.startProgram('app')
} else if (data.name === 'manage') {
await ctx.core.startProgram('manage')
} else {
ctx.log.warn('Invalid start command for app ' + data.name)
ctx.log.event.warn('Invalid start command for app ' + data.name)
}
}
/* /*
* Event: 'core.listencore' * Event: 'core.listencore'
* *

View file

@ -59,6 +59,7 @@ const Updater = Module({
m.redraw() m.redraw()
}) })
socket.emit('core.listencore', {}) socket.emit('core.listencore', {})
}, },
@ -96,6 +97,12 @@ const Updater = Module({
}) })
}, },
startSoftware: function() {
socket.emit('core.start', {
name: this.activeApp,
})
},
view: function() { view: function() {
return m('div#update', [ return m('div#update', [
m('div.actions', [ m('div.actions', [
@ -147,6 +154,10 @@ const Updater = Module({
m('button', { m('button', {
onclick: () => this.startUpdate(), onclick: () => this.startUpdate(),
}, 'Update & Install'), }, 'Update & Install'),
m('button', {
hidden: this.status[this.activeApp] || !(this.db[this.activeApp + 'LastActive'] || this.db[this.activeApp + 'LatestInstalled']),
onclick: () => this.startSoftware(),
}, 'Start'),
m('button', { m('button', {
hidden: !this.db[this.activeApp + 'LastActive'] hidden: !this.db[this.activeApp + 'LastActive']
|| this.db[this.activeApp + 'LastActive'] === this.db[this.activeApp + 'Active'] || this.db[this.activeApp + 'LastActive'] === this.db[this.activeApp + 'Active']

File diff suppressed because one or more lines are too long