hydrooj-addons-manager 0.0.1 → 0.0.2-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +106 -92
- package/package.json +11 -3
- package/templates/manage_addons.html +6 -5
package/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { HydroResponse } from '@hydrooj/framework';
|
|
2
1
|
import {
|
|
3
2
|
Context, Handler, PRIV, Schema,
|
|
4
3
|
Service, superagent, SystemModel,
|
|
@@ -7,31 +6,44 @@ import {
|
|
|
7
6
|
Model,
|
|
8
7
|
requireSudo
|
|
9
8
|
} from 'hydrooj';
|
|
10
|
-
import { exec } from 'child_process';
|
|
11
|
-
import * as fs from 'fs';
|
|
9
|
+
import { exec, spawn } from 'child_process';
|
|
10
|
+
import * as fs from 'fs/promises';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import semver from 'semver';
|
|
13
|
+
import validatePackageName from 'validate-npm-package-name';
|
|
14
|
+
type CommandResult = {success: boolean, message?: string};
|
|
15
|
+
type PackageInfo = {name: string, version: string};
|
|
16
|
+
type PackageAction = 'add' | 'delete' | 'update';
|
|
17
|
+
type PackageOperation = PackageInfo & {action: PackageAction};
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
getLockedPackages: () => Promise<String[]>
|
|
19
|
+
type AddonsManagerModel = {
|
|
20
|
+
manageAddon: (action: PackageAction, name: string, version?: string) => Promise<CommandResult>,
|
|
21
|
+
localUpdate: (name: string) => Promise<CommandResult>,
|
|
22
|
+
getActivedPackages: () => Promise<string[]>,
|
|
23
|
+
getLockedPackages: () => Promise<string[]>
|
|
19
24
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return /^(?:(?:@(?:[a-z0-9-*~][a-z0-9-*. _~]*)?\/[a-z0-9-._~])|[a-z0-9-~])[a-z0-9-._~]*$/.test(name);
|
|
25
|
+
function checkNpmPackageValidity(name : string): boolean {
|
|
26
|
+
return validatePackageName(name).validForNewPackages;
|
|
23
27
|
}
|
|
24
|
-
function checkNpmVersionValidity(name : string):
|
|
25
|
-
return name === '' ||
|
|
28
|
+
function checkNpmVersionValidity(name : string): boolean {
|
|
29
|
+
return name === '' || semver.valid(name) !== null;
|
|
30
|
+
}
|
|
31
|
+
function checkPackageIsLocal(name : string): boolean {
|
|
32
|
+
return name[0] === '/' && path.isAbsolute(name);
|
|
26
33
|
}
|
|
27
34
|
class AddonsManagerHandler extends Handler {
|
|
35
|
+
private static model: AddonsManagerModel;
|
|
36
|
+
static setModel(m: AddonsManagerModel) { this.model = m; }
|
|
28
37
|
@requireSudo
|
|
29
38
|
async get()
|
|
30
39
|
{
|
|
31
40
|
this.response.template = 'manage_addons.html';
|
|
32
|
-
const packages = await
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
const packages = await AddonsManagerHandler.model.getActivedPackages();
|
|
42
|
+
let lockedPackages = await AddonsManagerHandler.model.getLockedPackages();
|
|
43
|
+
for(const pkg of packages){
|
|
44
|
+
if(checkPackageIsLocal(pkg)) lockedPackages.push(pkg);
|
|
45
|
+
}
|
|
46
|
+
this.response.body = this.request.body ||{
|
|
35
47
|
packages: packages,
|
|
36
48
|
lockedPackages: lockedPackages,
|
|
37
49
|
result: null
|
|
@@ -41,23 +53,27 @@ class AddonsManagerHandler extends Handler {
|
|
|
41
53
|
async post()
|
|
42
54
|
{
|
|
43
55
|
const body = this.request.body;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
result = await addonsManagerModel.add(name, version);
|
|
49
|
-
}else if(body['delete_package']){
|
|
50
|
-
const name = body['delete_package'];
|
|
51
|
-
result = await addonsManagerModel.remove(name);
|
|
52
|
-
}else if(body['update_package']){
|
|
53
|
-
const name = body['update_package'];
|
|
54
|
-
result = await addonsManagerModel.update(name);
|
|
55
|
-
} else{
|
|
56
|
-
throw new UserFacingError('Invalid request');
|
|
56
|
+
const packages = await AddonsManagerHandler.model.getActivedPackages();
|
|
57
|
+
let lockedPackages = await AddonsManagerHandler.model.getLockedPackages();
|
|
58
|
+
for(const pkg of packages){
|
|
59
|
+
if(checkPackageIsLocal(pkg)) lockedPackages.push(pkg);
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
|
|
62
|
+
let pkg: PackageOperation =
|
|
63
|
+
{
|
|
64
|
+
name: body['package_name'],
|
|
65
|
+
version: body['package_version'] || '',
|
|
66
|
+
action: body['action']
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let result: CommandResult = {success: false, message: 'Unknown error'};
|
|
70
|
+
if(!(pkg.name in packages) && pkg.action === 'add')result = {success: false, message: 'Package is not installed'};
|
|
71
|
+
if(checkPackageIsLocal(pkg.name))
|
|
72
|
+
{
|
|
73
|
+
if(pkg.action !== 'update') result = {success: false, message: 'Local packages can only be updated'};
|
|
74
|
+
else result = await AddonsManagerHandler.model.localUpdate(pkg.name);
|
|
75
|
+
}else result = await AddonsManagerHandler.model.manageAddon(pkg.action, pkg.name, pkg.version);
|
|
76
|
+
|
|
61
77
|
this.back({
|
|
62
78
|
result: result,
|
|
63
79
|
packages: packages,
|
|
@@ -66,26 +82,21 @@ class AddonsManagerHandler extends Handler {
|
|
|
66
82
|
}
|
|
67
83
|
}
|
|
68
84
|
|
|
69
|
-
|
|
85
|
+
async function sendCommand(command: string, cwd: string): Promise<CommandResult> {
|
|
70
86
|
return new Promise((resolve) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
resolve({ success: true });
|
|
85
|
-
});
|
|
86
|
-
} catch (error) {
|
|
87
|
-
resolve({ success: false, message: (error as Error).message });
|
|
88
|
-
}
|
|
87
|
+
const child = spawn(command, { cwd });
|
|
88
|
+
let stdout = '';
|
|
89
|
+
let stderr = '';
|
|
90
|
+
child.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
91
|
+
child.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
92
|
+
child.on('error', (error) => resolve({ success: false, message: error.message }));
|
|
93
|
+
child.on('close', (code) => {
|
|
94
|
+
if (code !== 0) {
|
|
95
|
+
resolve({ success: false, message: stderr || stdout || `Exit code ${code}` });
|
|
96
|
+
} else {
|
|
97
|
+
resolve({ success: true, message: stdout || stderr });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
89
100
|
});
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -100,54 +111,57 @@ export default class AddonsManagerService extends Service {
|
|
|
100
111
|
global.Hydro.ui.inject('ControlPanel', 'manage_addons');
|
|
101
112
|
init();
|
|
102
113
|
async function init() {
|
|
114
|
+
// Model functions
|
|
115
|
+
async function manageAddon(action: PackageAction, name: string, version: string = ''): Promise<CommandResult> {
|
|
116
|
+
if (!checkNpmPackageValidity(name)) return {success: false, message: 'Invalid package name'};
|
|
117
|
+
if (version !== '' && !checkNpmVersionValidity(version)) return {success: false, message: 'Invalid version'};
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
let result: CommandResult = {success: false, message: 'Unknown error'};
|
|
120
|
+
switch (action) {
|
|
121
|
+
case 'delete':
|
|
122
|
+
await sendCommand("yarn global remove " + name, config.pathToHydro);
|
|
123
|
+
result = await sendCommand("hydrooj addon remove " + name, config.pathToHydro);
|
|
124
|
+
break;
|
|
125
|
+
case 'update':
|
|
126
|
+
result = await sendCommand("yarn global upgrade " + name + (version ? '@' + version : ''), config.pathToHydro);
|
|
127
|
+
break;
|
|
128
|
+
case 'add':
|
|
129
|
+
result = await sendCommand("yarn global add " + name + (version ? '@' + version : ''), config.pathToHydro);
|
|
130
|
+
result = await sendCommand("hydrooj addon add " + name, config.pathToHydro);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
114
133
|
return result;
|
|
115
134
|
}
|
|
116
|
-
async function
|
|
117
|
-
if(!
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const result = await sendCommand("hydrooj addon add "+name+(version ? '@' + version : ''), config.pathToHydro);
|
|
135
|
+
async function localUpdate(name: string): Promise<CommandResult> {
|
|
136
|
+
if (!checkPackageIsLocal(name)) return {success: false, message: 'Not a local package'};
|
|
137
|
+
let result: CommandResult = {success: false, message: 'Unknown error'};
|
|
138
|
+
result = await sendCommand("git pull origin main", name);
|
|
121
139
|
return result;
|
|
122
140
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
else {
|
|
131
|
-
packages = JSON.parse(data.toString());
|
|
132
|
-
resolve(packages);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
});
|
|
141
|
+
async function getActivedPackages(): Promise<string[]> {
|
|
142
|
+
try {
|
|
143
|
+
const data = await fs.readFile(config.pathToHydro+'addon.json', 'utf-8');
|
|
144
|
+
return JSON.parse(data);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
149
|
|
|
138
|
-
async function getLockedPackages(): Promise<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
resolve(packages);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
150
|
+
async function getLockedPackages(): Promise<string[]> {
|
|
151
|
+
try {
|
|
152
|
+
const data = await fs.readFile(config.pathToHydro+'addon-locked.json', 'utf-8');
|
|
153
|
+
return JSON.parse(data);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
149
157
|
}
|
|
150
|
-
addonsManagerModel = {
|
|
158
|
+
const addonsManagerModel: AddonsManagerModel = {
|
|
159
|
+
manageAddon: manageAddon,
|
|
160
|
+
localUpdate: localUpdate,
|
|
161
|
+
getActivedPackages: () => getActivedPackages(),
|
|
162
|
+
getLockedPackages: () => getLockedPackages()
|
|
163
|
+
};
|
|
164
|
+
AddonsManagerHandler.setModel(addonsManagerModel);
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
167
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hydrooj-addons-manager",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-dev",
|
|
4
4
|
"description": "addons manager for hydrooj",
|
|
5
5
|
"main": "index.ts",
|
|
6
|
-
"author": "https://github.com/Bryan0324"
|
|
7
|
-
|
|
6
|
+
"author": "https://github.com/Bryan0324",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@types/semver": "^7.7.1",
|
|
9
|
+
"@types/validate-npm-package-name": "^4.0.2"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/semver": "^7.7.1",
|
|
13
|
+
"@types/validate-npm-package-name": "^4.0.2"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -109,10 +109,11 @@
|
|
|
109
109
|
<form method="post" class="activate-package-form">
|
|
110
110
|
<div class="activate-package-item">
|
|
111
111
|
<a>{{ package }}</a>
|
|
112
|
+
<input type="text" name="package_name" value="{{ package }}" hidden >
|
|
112
113
|
{% if package not in lockedPackages %}
|
|
113
|
-
<button type="submit" name="
|
|
114
|
+
<button type="submit" name="action" value="delete" class="btn btn--danger">{{ _('delete') }}</button>
|
|
114
115
|
{% endif %}
|
|
115
|
-
<button type="submit" name="
|
|
116
|
+
<button type="submit" name="action" value="update" class="btn btn--secondary">{{ _('update') }}</button>
|
|
116
117
|
</div>
|
|
117
118
|
</form>
|
|
118
119
|
</li>
|
|
@@ -121,9 +122,9 @@
|
|
|
121
122
|
<h2 class="section__subtitle" data-heading>{{ _('add new package') }}</h2>
|
|
122
123
|
<form method="post" class="activate-package-form">
|
|
123
124
|
<div class="activate-package-item">
|
|
124
|
-
<input type="text" name="
|
|
125
|
-
<input type="text" name="
|
|
126
|
-
<button type="submit" name="
|
|
125
|
+
<input type="text" name="package_name" placeholder="{{ _('package name') }}" required>
|
|
126
|
+
<input type="text" name="package_version" placeholder="{{ _('version name *unnecessary') }}">
|
|
127
|
+
<button type="submit" name="action" value="add" class="btn btn--primary">{{ _('activate') }}</button>
|
|
127
128
|
</div>
|
|
128
129
|
</form>
|
|
129
130
|
{% if result %}
|