@axium/server 0.44.0 → 0.44.2
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/dist/{cli.d.ts → cli/common.d.ts} +1 -1
- package/dist/{cli.js → cli/common.js} +2 -2
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/config.js +69 -0
- package/dist/{db/cli.js → cli/db.js} +10 -32
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +303 -0
- package/dist/cli/plugins.d.ts +1 -0
- package/dist/cli/plugins.js +148 -0
- package/dist/cli/user.d.ts +1 -0
- package/dist/cli/user.js +102 -0
- package/dist/io.d.ts +1 -1
- package/dist/io.js +9 -4
- package/dist/linking.d.ts +1 -2
- package/dist/main.d.ts +2 -1
- package/dist/main.js +6 -584
- package/package.json +4 -3
- package/patches/patch.js +12 -0
- package/routes/admin/+page.svelte +2 -2
- package/routes/admin/plugins/+page.svelte +2 -2
- /package/dist/{db/cli.d.ts → cli/db.d.ts} +0 -0
|
@@ -7,7 +7,7 @@ export declare function lookupUser(lookup: string): Promise<UserInternal>;
|
|
|
7
7
|
* Only returns whether the array was updated and diff text for what actually changed.
|
|
8
8
|
*/
|
|
9
9
|
export declare function diffUpdate(original: string[], add?: string[], remove?: string[]): [updated: boolean, newValue: string[], diffText: string];
|
|
10
|
-
export declare const
|
|
10
|
+
export declare const sharedOptions: {
|
|
11
11
|
check: Option<"--check", undefined, false, undefined, false, undefined>;
|
|
12
12
|
force: Option<"-f, --force", undefined, false, undefined, false, undefined>;
|
|
13
13
|
global: Option<"-g, --global", undefined, false, undefined, false, undefined>;
|
|
@@ -53,7 +53,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
import * as io from 'ioium/node';
|
|
54
54
|
import { styleText } from 'node:util';
|
|
55
55
|
import * as z from 'zod';
|
|
56
|
-
import * as db from '
|
|
56
|
+
import * as db from '../db/index.js';
|
|
57
57
|
import { Option } from 'commander';
|
|
58
58
|
import { createInterface } from 'node:readline/promises';
|
|
59
59
|
export function userText(user, bold = false) {
|
|
@@ -99,7 +99,7 @@ export function diffUpdate(original, add, remove) {
|
|
|
99
99
|
return [!!diffs.length, original, diffs.join(', ')];
|
|
100
100
|
}
|
|
101
101
|
// Options shared by multiple (sub)commands
|
|
102
|
-
export const
|
|
102
|
+
export const sharedOptions = {
|
|
103
103
|
check: new Option('--check', 'check the database schema after initialization').default(false),
|
|
104
104
|
force: new Option('-f, --force', 'force the operation').default(false),
|
|
105
105
|
global: new Option('-g, --global', 'apply the operation globally').default(false),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { program } from 'commander';
|
|
2
|
+
import * as io from 'ioium/node';
|
|
3
|
+
import { getByString, isJSON, setByString } from 'utilium';
|
|
4
|
+
import * as z from 'zod';
|
|
5
|
+
import config, { ConfigFile } from '../config.js';
|
|
6
|
+
import { sharedOptions as opts } from './common.js';
|
|
7
|
+
const axiumConfig = program
|
|
8
|
+
.command('config')
|
|
9
|
+
.description('Manage the configuration')
|
|
10
|
+
.addOption(opts.global)
|
|
11
|
+
.option('-j, --json', 'values are JSON encoded', false)
|
|
12
|
+
.option('-r, --redact', 'Do not output sensitive values', false);
|
|
13
|
+
function configReplacer(opt) {
|
|
14
|
+
return (key, value) => {
|
|
15
|
+
return opt.redact && ['password', 'secret'].includes(key) ? '[redacted]' : value;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
axiumConfig
|
|
19
|
+
.command('dump')
|
|
20
|
+
.description('Output the entire current configuration')
|
|
21
|
+
.action(function axium_config_dump() {
|
|
22
|
+
const opt = this.optsWithGlobals();
|
|
23
|
+
const value = config.plain();
|
|
24
|
+
console.log(opt.json ? JSON.stringify(value, configReplacer(opt), 4) : value);
|
|
25
|
+
});
|
|
26
|
+
axiumConfig
|
|
27
|
+
.command('get')
|
|
28
|
+
.description('Get a config value')
|
|
29
|
+
.argument('<key>', 'the key to get')
|
|
30
|
+
.action(function axium_config_get(key) {
|
|
31
|
+
const opt = this.optsWithGlobals();
|
|
32
|
+
const value = getByString(config.plain(), key);
|
|
33
|
+
console.log(opt.json ? JSON.stringify(value, configReplacer(opt), 4) : value);
|
|
34
|
+
});
|
|
35
|
+
axiumConfig
|
|
36
|
+
.command('set')
|
|
37
|
+
.description('Set a config value. Note setting objects is not supported.')
|
|
38
|
+
.argument('<key>', 'the key to set')
|
|
39
|
+
.argument('<value>', 'the value')
|
|
40
|
+
.action(function axium_config_set(key, value) {
|
|
41
|
+
const opt = this.optsWithGlobals();
|
|
42
|
+
if (opt.json && !isJSON(value))
|
|
43
|
+
io.exit('Invalid JSON');
|
|
44
|
+
const obj = {};
|
|
45
|
+
setByString(obj, key, opt.json ? JSON.parse(value) : value);
|
|
46
|
+
config.save(obj, opt.global);
|
|
47
|
+
});
|
|
48
|
+
axiumConfig
|
|
49
|
+
.command('list')
|
|
50
|
+
.alias('ls')
|
|
51
|
+
.alias('files')
|
|
52
|
+
.description('List loaded config files')
|
|
53
|
+
.action(() => {
|
|
54
|
+
for (const path of config.files.keys())
|
|
55
|
+
console.log(path);
|
|
56
|
+
});
|
|
57
|
+
axiumConfig
|
|
58
|
+
.command('schema')
|
|
59
|
+
.description('Get the JSON schema for the configuration file')
|
|
60
|
+
.action(() => {
|
|
61
|
+
const opt = axiumConfig.optsWithGlobals();
|
|
62
|
+
try {
|
|
63
|
+
const schema = z.toJSONSchema(ConfigFile, { io: 'input' });
|
|
64
|
+
console.log(opt.json ? JSON.stringify(schema, configReplacer(opt), 4) : schema);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
io.exit(e);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -56,8 +56,8 @@ import { createWriteStream } from 'node:fs';
|
|
|
56
56
|
import { styleText } from 'node:util';
|
|
57
57
|
import { capitalize } from 'utilium';
|
|
58
58
|
import * as z from 'zod';
|
|
59
|
-
import {
|
|
60
|
-
import * as db from '
|
|
59
|
+
import { sharedOptions as opts, rlConfirm } from './common.js';
|
|
60
|
+
import * as db from '../db/index.js';
|
|
61
61
|
const axiumDB = program.command('db').alias('database').description('Manage the database').addOption(opts.timeout);
|
|
62
62
|
export async function dbInitTables() {
|
|
63
63
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
@@ -204,14 +204,8 @@ axiumDB
|
|
|
204
204
|
const tables = Object.fromEntries(tableMetadata.map(t => [t.schema == 'public' ? t.name : `${t.schema}.${t.name}`, t]));
|
|
205
205
|
io.done();
|
|
206
206
|
io.start('Resolving database schemas');
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
schema = db.schema.getFull();
|
|
210
|
-
io.done();
|
|
211
|
-
}
|
|
212
|
-
catch (e) {
|
|
213
|
-
io.exit(e);
|
|
214
|
-
}
|
|
207
|
+
const schema = db.schema.getFull();
|
|
208
|
+
io.done();
|
|
215
209
|
for (const [name, table] of Object.entries(schema.tables)) {
|
|
216
210
|
await db.checkTableTypes(name, table, opt, tableMetadata);
|
|
217
211
|
delete tables[name];
|
|
@@ -251,13 +245,8 @@ axiumDB
|
|
|
251
245
|
.description('Get the JSON schema for the database configuration file')
|
|
252
246
|
.option('-j, --json', 'values are JSON encoded')
|
|
253
247
|
.action(opt => {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.log(opt.json ? JSON.stringify(schema, null, 4) : schema);
|
|
257
|
-
}
|
|
258
|
-
catch (e) {
|
|
259
|
-
io.exit(e);
|
|
260
|
-
}
|
|
248
|
+
const schema = z.toJSONSchema(db.schema.SchemaFile, { io: 'input' });
|
|
249
|
+
console.log(opt.json ? JSON.stringify(schema, null, 4) : schema);
|
|
261
250
|
});
|
|
262
251
|
axiumDB
|
|
263
252
|
.command('upgrade')
|
|
@@ -307,22 +296,11 @@ axiumDB
|
|
|
307
296
|
}
|
|
308
297
|
await rlConfirm();
|
|
309
298
|
io.start('Computing delta');
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
delta = db.delta.collapse(deltas);
|
|
313
|
-
io.done();
|
|
314
|
-
}
|
|
315
|
-
catch (e) {
|
|
316
|
-
io.exit(e);
|
|
317
|
-
}
|
|
299
|
+
const delta = db.delta.collapse(deltas);
|
|
300
|
+
io.done();
|
|
318
301
|
io.start('Validating delta');
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
io.done();
|
|
322
|
-
}
|
|
323
|
-
catch (e) {
|
|
324
|
-
io.exit(e);
|
|
325
|
-
}
|
|
302
|
+
db.delta.validate(delta);
|
|
303
|
+
io.done();
|
|
326
304
|
console.log('Applying delta.');
|
|
327
305
|
await db.delta.apply(delta, opt.abort);
|
|
328
306
|
info.upgrades.push({ timestamp: new Date(), from, to });
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
export * from './common.js';
|
|
2
|
+
import { apps } from '@axium/core';
|
|
3
|
+
import { AuditFilter, severityNames } from '@axium/core/audit';
|
|
4
|
+
import { formatBytes, formatMs } from '@axium/core/format';
|
|
5
|
+
import { outputDaemonStatus } from '@axium/core/node';
|
|
6
|
+
import { plugins } from '@axium/core/plugins';
|
|
7
|
+
import { Argument, Option, program } from 'commander';
|
|
8
|
+
import * as io from 'ioium/node';
|
|
9
|
+
import { allLogLevels } from 'logzen';
|
|
10
|
+
import { createWriteStream, readFileSync } from 'node:fs';
|
|
11
|
+
import { access, watch } from 'node:fs/promises';
|
|
12
|
+
import { join, resolve } from 'node:path/posix';
|
|
13
|
+
import { styleText } from 'node:util';
|
|
14
|
+
import { searchForWorkspaceRoot } from 'vite';
|
|
15
|
+
import * as z from 'zod';
|
|
16
|
+
import $pkg from '../../package.json' with { type: 'json' };
|
|
17
|
+
import { getEvents, styleSeverity } from '../audit.js';
|
|
18
|
+
import { build } from '../build.js';
|
|
19
|
+
import config from '../config.js';
|
|
20
|
+
import * as db from '../db/index.js';
|
|
21
|
+
import { _portActions, _portMethods, dirs, logger, restrictedPorts } from '../io.js';
|
|
22
|
+
import { linkRoutes, listRouteLinks, unlinkRoutes, writePluginHooks } from '../linking.js';
|
|
23
|
+
import { serve } from '../serve.js';
|
|
24
|
+
import { sharedOptions as opts } from './common.js';
|
|
25
|
+
import { dbInitTables } from './db.js';
|
|
26
|
+
// other subcommands
|
|
27
|
+
import './config.js';
|
|
28
|
+
import './db.js';
|
|
29
|
+
import './plugins.js';
|
|
30
|
+
import './user.js';
|
|
31
|
+
const noAutoDB = ['init', 'serve', 'check'];
|
|
32
|
+
program
|
|
33
|
+
.version($pkg.version)
|
|
34
|
+
.name('axium')
|
|
35
|
+
.description('Axium server CLI')
|
|
36
|
+
.configureHelp({ showGlobalOptions: true })
|
|
37
|
+
.option('--safe', 'do not execute code from plugins', false)
|
|
38
|
+
.option('--debug', 'override debug mode')
|
|
39
|
+
.option('--no-debug', 'override debug mode')
|
|
40
|
+
.option('-c, --config <path>', 'path to the config file')
|
|
41
|
+
.hook('preAction', (_, action) => {
|
|
42
|
+
const opt = action.optsWithGlobals();
|
|
43
|
+
opt.force && io.warn('--force: Protections disabled.');
|
|
44
|
+
if (typeof opt.debug == 'boolean') {
|
|
45
|
+
config.set({ debug: opt.debug });
|
|
46
|
+
io._setDebugOutput(opt.debug);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
db.connect();
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
if (!noAutoDB.includes(action.name()))
|
|
53
|
+
throw e;
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.hook('postAction', async (_, action) => {
|
|
57
|
+
if (!noAutoDB.includes(action.name()))
|
|
58
|
+
await db.database.destroy();
|
|
59
|
+
})
|
|
60
|
+
.on('option:debug', () => config.set({ debug: true }));
|
|
61
|
+
const axiumApps = program.command('apps').description('Manage Axium apps').addOption(opts.global);
|
|
62
|
+
axiumApps
|
|
63
|
+
.command('list')
|
|
64
|
+
.alias('ls')
|
|
65
|
+
.description('List apps added by plugins')
|
|
66
|
+
.option('-l, --long', 'use the long listing format')
|
|
67
|
+
.option('-b, --builtin', 'include built-in apps')
|
|
68
|
+
.action(opt => {
|
|
69
|
+
if (!apps.size) {
|
|
70
|
+
console.log('No apps.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!opt.long) {
|
|
74
|
+
console.log(Array.from(apps.values().map(app => app.name)).join(', '));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log(styleText('whiteBright', apps.size + ' app(s) loaded:'));
|
|
78
|
+
for (const app of apps.values()) {
|
|
79
|
+
console.log(app.name, styleText('dim', `(${app.id})`));
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
program
|
|
83
|
+
.command('status')
|
|
84
|
+
.alias('stats')
|
|
85
|
+
.description('Get information about the server')
|
|
86
|
+
.action(async () => {
|
|
87
|
+
console.log('Axium Server v' + $pkg.version);
|
|
88
|
+
console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
89
|
+
const configFiles = config.files.keys().toArray();
|
|
90
|
+
console.log(styleText('whiteBright', 'Loaded config files:'), styleText(['dim', 'bold'], `(${configFiles.length})`), configFiles.join(', '));
|
|
91
|
+
outputDaemonStatus('axium');
|
|
92
|
+
process.stdout.write(styleText('whiteBright', 'Database: '));
|
|
93
|
+
try {
|
|
94
|
+
console.log(await db.statText());
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
console.log(styleText('red', 'Unavailable'));
|
|
98
|
+
}
|
|
99
|
+
console.log(styleText('whiteBright', 'Loaded plugins:'), styleText(['dim', 'bold'], `(${plugins.size || 'none'})`), Array.from(plugins.keys()).join(', '));
|
|
100
|
+
for (const plugin of plugins.values()) {
|
|
101
|
+
if (!plugin._hooks?.statusText)
|
|
102
|
+
continue;
|
|
103
|
+
const text = await plugin._hooks?.statusText();
|
|
104
|
+
console.log(styleText('bold', plugin.name), plugin.version + ':', text.includes('\n') ? '\n' + text : text);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
program
|
|
108
|
+
.command('ports')
|
|
109
|
+
.description('Enable or disable use of restricted ports (e.g. 443)')
|
|
110
|
+
.addArgument(new Argument('<action>', 'The action to take').choices(_portActions))
|
|
111
|
+
.addOption(new Option('-m, --method <method>', 'the method to use').choices(_portMethods).default('node-cap'))
|
|
112
|
+
.option('-N, --node <path>', 'the path to the node binary')
|
|
113
|
+
.action(async (action, opt) => {
|
|
114
|
+
await restrictedPorts({ ...opt, action });
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command('init')
|
|
118
|
+
.description('Install Axium server')
|
|
119
|
+
.addOption(opts.force)
|
|
120
|
+
.addOption(opts.check)
|
|
121
|
+
.option('-s, --skip', 'Skip already initialized steps', false)
|
|
122
|
+
.action(async (opt) => {
|
|
123
|
+
await db.init(opt);
|
|
124
|
+
await dbInitTables();
|
|
125
|
+
await restrictedPorts({ method: 'node-cap', action: 'enable' });
|
|
126
|
+
});
|
|
127
|
+
program
|
|
128
|
+
.command('serve')
|
|
129
|
+
.description('Start the Axium server')
|
|
130
|
+
.option('-p, --port <port>', 'the port to listen on', Number.parseInt, config.web.port)
|
|
131
|
+
.option('--ssl <prefix>', 'the prefix for the cert.pem and key.pem SSL files')
|
|
132
|
+
.option('-b, --build <path>', 'the path to the handler build')
|
|
133
|
+
.action(async (opt) => {
|
|
134
|
+
if (opt.port < 1 || opt.port > 65535)
|
|
135
|
+
io.exit('Invalid port');
|
|
136
|
+
const server = await serve({
|
|
137
|
+
secure: opt.ssl ? true : config.web.secure,
|
|
138
|
+
ssl_cert: opt.ssl ? join(opt.ssl, 'cert.pem') : config.web.ssl_cert,
|
|
139
|
+
ssl_key: opt.ssl ? join(opt.ssl, 'key.pem') : config.web.ssl_key,
|
|
140
|
+
build: opt.build ? resolve(opt.build) : config.web.build,
|
|
141
|
+
});
|
|
142
|
+
logger.attach(createWriteStream(join(dirs.at(-1), 'server.log')), { output: allLogLevels });
|
|
143
|
+
db.connect();
|
|
144
|
+
await db.clean({});
|
|
145
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
146
|
+
process.on('beforeExit', () => db.database.destroy());
|
|
147
|
+
server.listen(opt.port, () => {
|
|
148
|
+
console.log('Server is listening on port ' + opt.port);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
program
|
|
152
|
+
.command('link')
|
|
153
|
+
.description('Link routes provided by plugins and the server')
|
|
154
|
+
.addOption(new Option('-l, --list', 'list route links').conflicts('delete'))
|
|
155
|
+
.option('-d, --delete', 'delete route links')
|
|
156
|
+
.argument('[name...]', 'List of plugin names to operate on. If not specified, operates on all plugins and built-in routes.')
|
|
157
|
+
.action(async function axium_link(names) {
|
|
158
|
+
const opt = this.optsWithGlobals();
|
|
159
|
+
const linkOpts = { only: names };
|
|
160
|
+
if (opt.list) {
|
|
161
|
+
let idTextLength = 0, fromLength = 0, toLength = 0;
|
|
162
|
+
const links = [];
|
|
163
|
+
for (const link of listRouteLinks(linkOpts)) {
|
|
164
|
+
const idText = link.id.startsWith('#') ? `(${link.id.slice(1)})` : link.id;
|
|
165
|
+
idTextLength = Math.max(idTextLength, idText.length);
|
|
166
|
+
fromLength = Math.max(fromLength, link.from.length);
|
|
167
|
+
toLength = Math.max(toLength, link.to.length);
|
|
168
|
+
links.push(Object.assign(link, { idText }));
|
|
169
|
+
}
|
|
170
|
+
idTextLength++;
|
|
171
|
+
for (const link of links) {
|
|
172
|
+
const fromColor = await access(link.from)
|
|
173
|
+
.then(() => 'cyanBright')
|
|
174
|
+
.catch(() => 'redBright');
|
|
175
|
+
console.log((link.idText + ':').padEnd(idTextLength), styleText(fromColor, link.from.padEnd(fromLength)), '->', link.to.padEnd(toLength).replace(/.*\/node_modules\//, styleText('dim', '$&')));
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (opt.delete) {
|
|
180
|
+
unlinkRoutes(linkOpts);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
io.start('Linking routes');
|
|
184
|
+
linkRoutes(linkOpts);
|
|
185
|
+
io.done();
|
|
186
|
+
io.start('Writing web client hooks for plugins');
|
|
187
|
+
writePluginHooks();
|
|
188
|
+
io.done();
|
|
189
|
+
});
|
|
190
|
+
program
|
|
191
|
+
.command('audit')
|
|
192
|
+
.description('View audit logs')
|
|
193
|
+
.option('-x, --extra', 'Include the extra object when listing events')
|
|
194
|
+
.option('-t, --include-tags', 'Include tags when listing events')
|
|
195
|
+
.addOption(new Option('-s, --summary', 'Summarize audit log entries instead of displaying individual ones').conflicts(['extra', 'includeTags']))
|
|
196
|
+
.optionsGroup('Filters:')
|
|
197
|
+
.option('--since <date>', 'Filter for events since a date')
|
|
198
|
+
.option('--until <date>', 'Filter for events until a date')
|
|
199
|
+
.option('--user <uuid|null>', 'Filter for events triggered by a user')
|
|
200
|
+
.addOption(new Option('--severity <level>', 'Filter for events at or above a severity level').choices(severityNames))
|
|
201
|
+
.option('--source <source>', 'Filter by source')
|
|
202
|
+
.option('--tag <tag...>', 'Filter by tag(s)')
|
|
203
|
+
.option('--event <event>', 'Filter by event name')
|
|
204
|
+
.action(async (opt) => {
|
|
205
|
+
const filter = await AuditFilter.parseAsync(opt).catch(e => io.exit('Invalid filter: ' + z.prettifyError(e)));
|
|
206
|
+
const events = await getEvents(filter).execute();
|
|
207
|
+
if (opt.summary) {
|
|
208
|
+
const groups = Object.groupBy(events, e => e.severity);
|
|
209
|
+
const maxGroupLength = Math.max(...Object.values(groups).map(g => g.length.toString().length), 0);
|
|
210
|
+
for (const [severity, group] of Object.entries(groups)) {
|
|
211
|
+
if (!group?.length)
|
|
212
|
+
continue;
|
|
213
|
+
console.log(styleText('white', group.length.toString().padStart(maxGroupLength)), styleSeverity(severity, true), 'events. Latest occurred', group.at(-1).timestamp.toLocaleString());
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
let maxSource = 0, maxName = 0, maxTags = 0, maxExtra = 0;
|
|
218
|
+
for (const event of events) {
|
|
219
|
+
maxSource = Math.max(maxSource, event.source.length);
|
|
220
|
+
maxName = Math.max(maxName, event.name.length);
|
|
221
|
+
event._tags = !event.tags.length
|
|
222
|
+
? ''
|
|
223
|
+
: opt.includeTags
|
|
224
|
+
? '# ' + event.tags.join(', ')
|
|
225
|
+
: `(${event.tags.length} tag${event.tags.length == 1 ? '' : 's'})`;
|
|
226
|
+
maxTags = Math.max(maxTags, event._tags.length);
|
|
227
|
+
const extraKeys = Object.keys(event.extra);
|
|
228
|
+
event._extra = !extraKeys.length ? '' : opt.extra ? JSON.stringify(event.extra) : '+' + extraKeys.length;
|
|
229
|
+
maxExtra = Math.max(maxExtra, event._extra.length);
|
|
230
|
+
}
|
|
231
|
+
for (const event of events) {
|
|
232
|
+
console.log(styleSeverity(event.severity, true), styleText('dim', io.prettyDate(event.timestamp)), event.source.padEnd(maxSource), styleText('whiteBright', event.name.padEnd(maxName)), styleText('gray', event._tags.padEnd(maxTags)), 'by', event.userId ? event.userId : styleText(['dim', 'italic'], 'unknown'.padEnd(36)), styleText('blue', event._extra));
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
program
|
|
236
|
+
.command('build')
|
|
237
|
+
.description('Create the Vite build for the server')
|
|
238
|
+
.option('--show-garbage-output', 'Show all output from the build process')
|
|
239
|
+
.option('-s, --diagnostics', 'Show build time and bundle size')
|
|
240
|
+
.option('-m, --no-minify', 'Whether to use minification')
|
|
241
|
+
.action(async (options) => {
|
|
242
|
+
io.start('Building');
|
|
243
|
+
const { time, size } = await build(options);
|
|
244
|
+
io.done();
|
|
245
|
+
if (options.diagnostics) {
|
|
246
|
+
console.log('Took', styleText('blueBright', formatMs(time)), 'with a bundle size of', styleText('blueBright', formatBytes(size)));
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
program
|
|
250
|
+
.command('develop')
|
|
251
|
+
.alias('dev')
|
|
252
|
+
.description('Develop with axium')
|
|
253
|
+
.argument('[dir]', 'The project directory', searchForWorkspaceRoot(process.cwd()))
|
|
254
|
+
.option('-g, --git', 'Use .gitignore to ignore files (can improve performance)')
|
|
255
|
+
.action(async (dir, opts) => {
|
|
256
|
+
let buildId = 0, server;
|
|
257
|
+
logger.attach(createWriteStream(join(dirs.at(-1), 'server.log')), { output: allLogLevels });
|
|
258
|
+
db.connect();
|
|
259
|
+
await db.clean({});
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
261
|
+
process.on('beforeExit', () => db.database.destroy());
|
|
262
|
+
async function rebuild() {
|
|
263
|
+
server?.close();
|
|
264
|
+
process.stdout.clearLine(0);
|
|
265
|
+
process.stdout.cursorTo(0);
|
|
266
|
+
io.start('Building');
|
|
267
|
+
const { time } = await build({ minify: false });
|
|
268
|
+
buildId++;
|
|
269
|
+
process.stdout.clearLine(0);
|
|
270
|
+
process.stdout.cursorTo(0);
|
|
271
|
+
server = await serve(config.web);
|
|
272
|
+
server.listen(config.web.port);
|
|
273
|
+
process.stdout.write(`Build #${buildId} finished in ${formatMs(time)}`);
|
|
274
|
+
}
|
|
275
|
+
const ignore = ['node_modules', '.git'];
|
|
276
|
+
try {
|
|
277
|
+
if (!opts.git)
|
|
278
|
+
throw null;
|
|
279
|
+
const gitignore = readFileSync(join(dir, '.gitignore'), 'utf8');
|
|
280
|
+
for (const rawLine of gitignore.split('\n')) {
|
|
281
|
+
const line = rawLine.trim();
|
|
282
|
+
if (!line || line[0] == '#')
|
|
283
|
+
continue;
|
|
284
|
+
ignore.push(line);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// It's fine if we don't have a .gitignore
|
|
289
|
+
}
|
|
290
|
+
io.debug('Watching', dir);
|
|
291
|
+
await rebuild();
|
|
292
|
+
try {
|
|
293
|
+
for await (const _event of watch(dir, { recursive: true, ignore })) {
|
|
294
|
+
// @todo see if we can be more efficient based on event data
|
|
295
|
+
await rebuild();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
if (err.name === 'AbortError')
|
|
300
|
+
return;
|
|
301
|
+
throw err;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { pluginText } from '@axium/core/node';
|
|
54
|
+
import { _findPlugin, plugins } from '@axium/core/plugins';
|
|
55
|
+
import { program } from 'commander';
|
|
56
|
+
import * as io from 'ioium/node';
|
|
57
|
+
import { styleText } from 'node:util';
|
|
58
|
+
import { configFiles, saveConfigTo } from '../config.js';
|
|
59
|
+
import * as db from '../db/index.js';
|
|
60
|
+
import { sharedOptions as opts, rlConfirm } from './common.js';
|
|
61
|
+
const axiumPlugin = program.command('plugins').alias('plugin').description('Manage plugins').addOption(opts.global);
|
|
62
|
+
axiumPlugin
|
|
63
|
+
.command('list')
|
|
64
|
+
.alias('ls')
|
|
65
|
+
.description('List loaded plugins')
|
|
66
|
+
.option('-l, --long', 'use the long listing format')
|
|
67
|
+
.option('--no-versions', 'do not show plugin versions')
|
|
68
|
+
.action(opt => {
|
|
69
|
+
if (!plugins.size) {
|
|
70
|
+
console.log('No plugins loaded.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!opt.long) {
|
|
74
|
+
console.log(Array.from(plugins.keys()).join(', '));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log(styleText('whiteBright', plugins.size + ' plugin(s) loaded:'));
|
|
78
|
+
for (const plugin of plugins.values()) {
|
|
79
|
+
console.log(plugin.name, opt.versions ? plugin.version : '');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
axiumPlugin
|
|
83
|
+
.command('info')
|
|
84
|
+
.description('Get information about a plugin')
|
|
85
|
+
.argument('<plugin>', 'the plugin to get information about')
|
|
86
|
+
.action((search) => {
|
|
87
|
+
const plugin = _findPlugin(search);
|
|
88
|
+
for (const line of pluginText(plugin))
|
|
89
|
+
console.log(line);
|
|
90
|
+
});
|
|
91
|
+
axiumPlugin
|
|
92
|
+
.command('remove')
|
|
93
|
+
.alias('rm')
|
|
94
|
+
.description('Remove a plugin')
|
|
95
|
+
.argument('<plugin>', 'the plugin to remove')
|
|
96
|
+
.action(async (search, opt) => {
|
|
97
|
+
const plugin = _findPlugin(search);
|
|
98
|
+
await plugin._hooks?.remove?.(opt);
|
|
99
|
+
for (const [path, data] of configFiles) {
|
|
100
|
+
if (!data.plugins)
|
|
101
|
+
continue;
|
|
102
|
+
data.plugins = data.plugins.filter(p => p !== plugin.specifier);
|
|
103
|
+
saveConfigTo(path, data);
|
|
104
|
+
}
|
|
105
|
+
plugins.delete(plugin.name);
|
|
106
|
+
});
|
|
107
|
+
axiumPlugin
|
|
108
|
+
.command('init')
|
|
109
|
+
.alias('setup')
|
|
110
|
+
.alias('install')
|
|
111
|
+
.description('Initialize a plugin. This could include adding tables to the database or linking routes.')
|
|
112
|
+
.addOption(opts.timeout)
|
|
113
|
+
.addOption(opts.check)
|
|
114
|
+
.argument('<plugin>', 'the plugin to initialize')
|
|
115
|
+
.action(async (search) => {
|
|
116
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
117
|
+
try {
|
|
118
|
+
const plugin = _findPlugin(search);
|
|
119
|
+
if (!plugin)
|
|
120
|
+
io.exit(`Can't find a plugin matching "${search}"`);
|
|
121
|
+
const _ = __addDisposableResource(env_1, db.connect(), true);
|
|
122
|
+
const info = db.getUpgradeInfo();
|
|
123
|
+
const exclude = Object.keys(info.current);
|
|
124
|
+
if (exclude.includes(plugin.name))
|
|
125
|
+
io.exit('Plugin is already initialized (database)');
|
|
126
|
+
const schema = db.schema.getFull({ exclude });
|
|
127
|
+
const delta = db.delta.compute({ tables: {}, indexes: {} }, schema);
|
|
128
|
+
if (db.delta.isEmpty(delta)) {
|
|
129
|
+
io.info('Plugin does not define any database schema.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const text of db.delta.display(delta))
|
|
133
|
+
console.log(text);
|
|
134
|
+
await rlConfirm();
|
|
135
|
+
await db.delta.apply(delta);
|
|
136
|
+
Object.assign(info.current, schema.versions);
|
|
137
|
+
db.setUpgradeInfo(info);
|
|
138
|
+
}
|
|
139
|
+
catch (e_1) {
|
|
140
|
+
env_1.error = e_1;
|
|
141
|
+
env_1.hasError = true;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
const result_1 = __disposeResources(env_1);
|
|
145
|
+
if (result_1)
|
|
146
|
+
await result_1;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|