@axium/server 0.9.0 → 0.11.0
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/assets/icons/brands.svg +1493 -0
- package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
- package/dist/api/index.js +5 -0
- package/dist/api/metadata.d.ts +1 -0
- package/dist/api/metadata.js +28 -0
- package/dist/api/passkeys.d.ts +1 -0
- package/dist/api/passkeys.js +50 -0
- package/dist/api/register.d.ts +1 -0
- package/dist/api/register.js +70 -0
- package/dist/api/session.d.ts +1 -0
- package/dist/api/session.js +31 -0
- package/dist/api/users.d.ts +1 -0
- package/dist/api/users.js +244 -0
- package/dist/apps.d.ts +0 -5
- package/dist/apps.js +2 -9
- package/dist/auth.d.ts +14 -30
- package/dist/auth.js +12 -18
- package/dist/cli.js +289 -32
- package/dist/config.d.ts +21 -8
- package/dist/config.js +46 -17
- package/dist/database.d.ts +12 -12
- package/dist/database.js +83 -84
- package/dist/io.d.ts +19 -20
- package/dist/io.js +85 -56
- package/dist/linking.d.ts +10 -0
- package/dist/linking.js +76 -0
- package/dist/plugins.d.ts +28 -12
- package/dist/plugins.js +29 -25
- package/dist/requests.d.ts +14 -0
- package/dist/requests.js +67 -0
- package/dist/routes.d.ts +12 -13
- package/dist/routes.js +21 -22
- package/dist/serve.d.ts +7 -0
- package/dist/serve.js +11 -0
- package/dist/state.d.ts +4 -0
- package/dist/state.js +22 -0
- package/dist/sveltekit.d.ts +8 -0
- package/dist/sveltekit.js +94 -0
- package/package.json +17 -8
- package/{web/routes → routes}/account/+page.svelte +6 -5
- package/svelte.config.js +37 -0
- package/web/hooks.server.ts +8 -3
- package/web/lib/Dialog.svelte +0 -1
- package/web/lib/FormDialog.svelte +0 -1
- package/web/lib/Upload.svelte +58 -0
- package/web/lib/icons/Icon.svelte +2 -7
- package/web/lib/icons/index.ts +6 -3
- package/web/lib/icons/mime.json +2 -1
- package/web/template.html +18 -0
- package/web/tsconfig.json +2 -2
- package/web/api/metadata.ts +0 -35
- package/web/api/passkeys.ts +0 -56
- package/web/api/readme.md +0 -1
- package/web/api/register.ts +0 -83
- package/web/api/schemas.ts +0 -22
- package/web/api/session.ts +0 -33
- package/web/api/users.ts +0 -351
- package/web/api/utils.ts +0 -66
- package/web/app.html +0 -14
- package/web/auth.ts +0 -8
- package/web/index.server.ts +0 -1
- package/web/index.ts +0 -1
- package/web/lib/auth.ts +0 -12
- package/web/lib/index.ts +0 -5
- package/web/routes/+layout.svelte +0 -6
- package/web/routes/[...path]/+page.server.ts +0 -13
- package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
- package/web/routes/api/[...path]/+server.ts +0 -49
- package/web/utils.ts +0 -26
- /package/{web/lib → assets}/icons/light.svg +0 -0
- /package/{web/lib → assets}/icons/regular.svg +0 -0
- /package/{web/lib → assets}/icons/solid.svg +0 -0
- /package/{web/lib → assets}/styles.css +0 -0
- /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
- /package/{web/routes → routes}/login/+page.svelte +0 -0
- /package/{web/routes → routes}/logout/+page.svelte +0 -0
- /package/{web/routes → routes}/register/+page.svelte +0 -0
package/dist/cli.js
CHANGED
|
@@ -51,15 +51,35 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
var e = new Error(message);
|
|
52
52
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
53
|
});
|
|
54
|
+
import { formatDateRange } from '@axium/core/format';
|
|
54
55
|
import { Argument, Option, program } from 'commander';
|
|
56
|
+
import { access } from 'node:fs/promises';
|
|
57
|
+
import { join } from 'node:path/posix';
|
|
58
|
+
import { createInterface } from 'node:readline/promises';
|
|
55
59
|
import { styleText } from 'node:util';
|
|
56
60
|
import { getByString, isJSON, setByString } from 'utilium';
|
|
61
|
+
import z from 'zod/v4';
|
|
57
62
|
import $pkg from '../package.json' with { type: 'json' };
|
|
58
|
-
import config from './config.js';
|
|
59
|
-
import * as db from './database.js';
|
|
60
|
-
import { _portActions, _portMethods, exit, handleError, output, restrictedPorts } from './io.js';
|
|
61
|
-
import { loadDefaultPlugins, plugins, pluginText, resolvePlugin } from './plugins.js';
|
|
62
63
|
import { apps } from './apps.js';
|
|
64
|
+
import config, { configFiles, saveConfigTo } from './config.js';
|
|
65
|
+
import * as db from './database.js';
|
|
66
|
+
import { _portActions, _portMethods, exit, handleError, output, restrictedPorts, setCommandTimeout, warn } from './io.js';
|
|
67
|
+
import { linkRoutes, listRouteLinks, unlinkRoutes } from './linking.js';
|
|
68
|
+
import { getSpecifier, plugins, pluginText, resolvePlugin } from './plugins.js';
|
|
69
|
+
import { serve } from './serve.js';
|
|
70
|
+
function readline() {
|
|
71
|
+
const rl = createInterface({
|
|
72
|
+
input: process.stdin,
|
|
73
|
+
output: process.stdout,
|
|
74
|
+
});
|
|
75
|
+
return Object.assign(rl, {
|
|
76
|
+
[Symbol.dispose]: rl.close.bind(rl),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function userText(user, bold = false) {
|
|
80
|
+
const text = `${user.name} <${user.email}> (${user.id})`;
|
|
81
|
+
return bold ? styleText('bold', text) : text;
|
|
82
|
+
}
|
|
63
83
|
program
|
|
64
84
|
.version($pkg.version)
|
|
65
85
|
.name('axium')
|
|
@@ -72,15 +92,10 @@ program.on('option:debug', () => config.set({ debug: true }));
|
|
|
72
92
|
program.on('option:config', () => void config.load(program.opts().config));
|
|
73
93
|
program.hook('preAction', async function (_, action) {
|
|
74
94
|
await config.loadDefaults();
|
|
75
|
-
await loadDefaultPlugins();
|
|
76
95
|
const opt = action.optsWithGlobals();
|
|
77
96
|
opt.force && output.warn('--force: Protections disabled.');
|
|
78
97
|
if (opt.debug === false)
|
|
79
98
|
config.set({ debug: false });
|
|
80
|
-
/* if (!config.auth.secret) {
|
|
81
|
-
config.save({ auth: { secret: process.env.AUTH_SECRET || randomBytes(32).toString('base64') } }, true);
|
|
82
|
-
output.debug('Auto-generated a new auth secret');
|
|
83
|
-
} */
|
|
84
99
|
});
|
|
85
100
|
// Options shared by multiple (sub)commands
|
|
86
101
|
const opts = {
|
|
@@ -92,13 +107,14 @@ const opts = {
|
|
|
92
107
|
}),
|
|
93
108
|
force: new Option('-f, --force', 'force the operation').default(false),
|
|
94
109
|
global: new Option('-g, --global', 'apply the operation globally').default(false),
|
|
110
|
+
timeout: new Option('-t, --timeout <ms>', 'how long to wait for commands to complete.').default('1000').argParser(value => {
|
|
111
|
+
const timeout = parseInt(value);
|
|
112
|
+
if (!Number.isSafeInteger(timeout) || timeout < 0)
|
|
113
|
+
warn('Invalid timeout value, using default.');
|
|
114
|
+
setCommandTimeout(timeout);
|
|
115
|
+
}),
|
|
95
116
|
};
|
|
96
|
-
const axiumDB = program
|
|
97
|
-
.command('db')
|
|
98
|
-
.alias('database')
|
|
99
|
-
.description('Manage the database')
|
|
100
|
-
.option('-t, --timeout <ms>', 'how long to wait for commands to complete.', '1000')
|
|
101
|
-
.addOption(opts.host);
|
|
117
|
+
const axiumDB = program.command('db').alias('database').description('Manage the database').addOption(opts.timeout).addOption(opts.host);
|
|
102
118
|
axiumDB
|
|
103
119
|
.command('init')
|
|
104
120
|
.description('Initialize the database')
|
|
@@ -255,11 +271,7 @@ axiumConfig
|
|
|
255
271
|
for (const path of config.files.keys())
|
|
256
272
|
console.log(path);
|
|
257
273
|
});
|
|
258
|
-
const axiumPlugin = program
|
|
259
|
-
.command('plugins')
|
|
260
|
-
.description('Manage plugins')
|
|
261
|
-
.addOption(opts.global)
|
|
262
|
-
.option('--safe', 'do not perform actions that would execute code from plugins.');
|
|
274
|
+
const axiumPlugin = program.command('plugins').description('Manage plugins').addOption(opts.global);
|
|
263
275
|
axiumPlugin
|
|
264
276
|
.command('list')
|
|
265
277
|
.alias('ls')
|
|
@@ -279,7 +291,7 @@ axiumPlugin
|
|
|
279
291
|
}
|
|
280
292
|
console.log(styleText('whiteBright', plugins.size + ' plugin(s) loaded:'));
|
|
281
293
|
for (const plugin of plugins) {
|
|
282
|
-
console.log(plugin.name,
|
|
294
|
+
console.log(plugin.name, opt.versions ? plugin.version : '');
|
|
283
295
|
}
|
|
284
296
|
});
|
|
285
297
|
axiumPlugin
|
|
@@ -292,6 +304,64 @@ axiumPlugin
|
|
|
292
304
|
exit(`Can't find a plugin matching "${search}"`);
|
|
293
305
|
console.log(pluginText(plugin));
|
|
294
306
|
});
|
|
307
|
+
axiumPlugin
|
|
308
|
+
.command('remove')
|
|
309
|
+
.alias('rm')
|
|
310
|
+
.description('Remove a plugin')
|
|
311
|
+
.argument('<plugin>', 'the plugin to remove')
|
|
312
|
+
.action(async (search, opt) => {
|
|
313
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
314
|
+
try {
|
|
315
|
+
const plugin = resolvePlugin(search);
|
|
316
|
+
if (!plugin)
|
|
317
|
+
exit(`Can't find a plugin matching "${search}"`);
|
|
318
|
+
const specifier = getSpecifier(plugin);
|
|
319
|
+
const _ = __addDisposableResource(env_4, db.connect(), true);
|
|
320
|
+
await plugin.hooks.remove?.(opt, db.database);
|
|
321
|
+
for (const [path, data] of configFiles) {
|
|
322
|
+
if (!data.plugins)
|
|
323
|
+
continue;
|
|
324
|
+
data.plugins = data.plugins.filter(p => p !== specifier);
|
|
325
|
+
saveConfigTo(path, data);
|
|
326
|
+
}
|
|
327
|
+
plugins.delete(plugin);
|
|
328
|
+
}
|
|
329
|
+
catch (e_4) {
|
|
330
|
+
env_4.error = e_4;
|
|
331
|
+
env_4.hasError = true;
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
const result_4 = __disposeResources(env_4);
|
|
335
|
+
if (result_4)
|
|
336
|
+
await result_4;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
axiumPlugin
|
|
340
|
+
.command('init')
|
|
341
|
+
.alias('setup')
|
|
342
|
+
.alias('install')
|
|
343
|
+
.description('Initialize a plugin. This could include adding tables to the database or linking routes.')
|
|
344
|
+
.addOption(opts.timeout)
|
|
345
|
+
.argument('<plugin>', 'the plugin to initialize')
|
|
346
|
+
.action(async (search, opt) => {
|
|
347
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
348
|
+
try {
|
|
349
|
+
const plugin = resolvePlugin(search);
|
|
350
|
+
if (!plugin)
|
|
351
|
+
exit(`Can't find a plugin matching "${search}"`);
|
|
352
|
+
const _ = __addDisposableResource(env_5, db.connect(), true);
|
|
353
|
+
await plugin.hooks.db_init?.({ force: false, ...opt, skip: true }, db.database);
|
|
354
|
+
}
|
|
355
|
+
catch (e_5) {
|
|
356
|
+
env_5.error = e_5;
|
|
357
|
+
env_5.hasError = true;
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
const result_5 = __disposeResources(env_5);
|
|
361
|
+
if (result_5)
|
|
362
|
+
await result_5;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
295
365
|
const axiumApps = program.command('apps').description('Manage Axium apps').addOption(opts.global);
|
|
296
366
|
axiumApps
|
|
297
367
|
.command('list')
|
|
@@ -313,28 +383,186 @@ axiumApps
|
|
|
313
383
|
console.log(app.name, styleText('dim', `(${app.id})`));
|
|
314
384
|
}
|
|
315
385
|
});
|
|
386
|
+
const lookup = new Argument('<user>', 'the UUID or email of the user to operate on').argParser(async (lookup) => {
|
|
387
|
+
const value = await (lookup.includes('@') ? z.email() : z.uuid())
|
|
388
|
+
.parseAsync(lookup.toLowerCase())
|
|
389
|
+
.catch(() => exit('Invalid user ID or email.'));
|
|
390
|
+
db.connect();
|
|
391
|
+
const result = await db.database
|
|
392
|
+
.selectFrom('users')
|
|
393
|
+
.where(value.includes('@') ? 'email' : 'id', '=', value)
|
|
394
|
+
.selectAll()
|
|
395
|
+
.executeTakeFirst();
|
|
396
|
+
if (!result)
|
|
397
|
+
exit('No user with matching ID or email.');
|
|
398
|
+
return result;
|
|
399
|
+
});
|
|
400
|
+
/**
|
|
401
|
+
* Updates an array of strings by adding or removing items.
|
|
402
|
+
* Only returns whether the array was updated and diff text for what actually changed.
|
|
403
|
+
*/
|
|
404
|
+
function diffUpdate(original, add, remove) {
|
|
405
|
+
const diffs = [];
|
|
406
|
+
// update the values
|
|
407
|
+
if (add) {
|
|
408
|
+
for (const role of add) {
|
|
409
|
+
if (original.includes(role))
|
|
410
|
+
continue;
|
|
411
|
+
original.push(role);
|
|
412
|
+
diffs.push(styleText('green', '+' + role));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (remove)
|
|
416
|
+
original = original.filter(item => {
|
|
417
|
+
const allow = !remove.includes(item);
|
|
418
|
+
if (!allow)
|
|
419
|
+
diffs.push(styleText('red', '-' + item));
|
|
420
|
+
return allow;
|
|
421
|
+
});
|
|
422
|
+
return [!!diffs.length, original, diffs.join(', ')];
|
|
423
|
+
}
|
|
424
|
+
program
|
|
425
|
+
.command('user')
|
|
426
|
+
.description('Get or change information about a user')
|
|
427
|
+
.addArgument(lookup)
|
|
428
|
+
.option('-S, --sessions', 'show user sessions')
|
|
429
|
+
.option('-P, --passkeys', 'show user passkeys')
|
|
430
|
+
.option('--add-role <role...>', 'add roles to the user')
|
|
431
|
+
.option('--remove-role <role...>', 'remove roles from the user')
|
|
432
|
+
.option('--tag <tag...>', 'Add tags to the user')
|
|
433
|
+
.option('--untag <tag...>', 'Remove tags from the user')
|
|
434
|
+
.option('--delete', 'Delete the user')
|
|
435
|
+
.action(async (_user, opt) => {
|
|
436
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
437
|
+
try {
|
|
438
|
+
let user = await _user;
|
|
439
|
+
const _ = __addDisposableResource(env_6, db.connect(), true);
|
|
440
|
+
const [updatedRoles, roles, rolesDiff] = diffUpdate(user.roles, opt.addRole, opt.removeRole);
|
|
441
|
+
const [updatedTags, tags, tagsDiff] = diffUpdate(user.tags, opt.tag, opt.untag);
|
|
442
|
+
if (updatedRoles || updatedTags) {
|
|
443
|
+
user = await db.database
|
|
444
|
+
.updateTable('users')
|
|
445
|
+
.set({ roles, tags })
|
|
446
|
+
.returningAll()
|
|
447
|
+
.executeTakeFirstOrThrow()
|
|
448
|
+
.then(u => {
|
|
449
|
+
if (updatedRoles && rolesDiff)
|
|
450
|
+
console.log(`> Updated roles: ${rolesDiff}`);
|
|
451
|
+
if (updatedTags && tagsDiff)
|
|
452
|
+
console.log(`> Updated tags: ${tagsDiff}`);
|
|
453
|
+
return u;
|
|
454
|
+
})
|
|
455
|
+
.catch(e => exit('Failed to update user roles: ' + e.message));
|
|
456
|
+
}
|
|
457
|
+
if (opt.delete) {
|
|
458
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
459
|
+
try {
|
|
460
|
+
const rl = __addDisposableResource(env_7, readline(), false);
|
|
461
|
+
const confirmed = await rl
|
|
462
|
+
.question(`Are you sure you want to delete ${userText(user, true)}? (y/N) `)
|
|
463
|
+
.then(v => z.stringbool().parseAsync(v))
|
|
464
|
+
.catch(() => false);
|
|
465
|
+
if (!confirmed)
|
|
466
|
+
console.log(styleText('dim', '> Delete aborted.'));
|
|
467
|
+
else
|
|
468
|
+
await db.database
|
|
469
|
+
.deleteFrom('users')
|
|
470
|
+
.where('id', '=', user.id)
|
|
471
|
+
.executeTakeFirstOrThrow()
|
|
472
|
+
.then(() => console.log(styleText(['red', 'bold'], '> Deleted')))
|
|
473
|
+
.catch(e => exit('Failed to delete user: ' + e.message));
|
|
474
|
+
}
|
|
475
|
+
catch (e_6) {
|
|
476
|
+
env_7.error = e_6;
|
|
477
|
+
env_7.hasError = true;
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
__disposeResources(env_7);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
console.log([
|
|
484
|
+
user.isAdmin && styleText('redBright', 'Administrator'),
|
|
485
|
+
'UUID: ' + user.id,
|
|
486
|
+
'Name: ' + user.name,
|
|
487
|
+
`Email: ${user.email}, ${user.emailVerified ? 'verified on ' + formatDateRange(user.emailVerified) : styleText(config.auth.email_verification ? 'yellow' : 'dim', 'not verified')}`,
|
|
488
|
+
'Registered ' + formatDateRange(user.registeredAt),
|
|
489
|
+
`Roles: ${user.roles.length ? user.roles.join(', ') : styleText('dim', '(none)')}`,
|
|
490
|
+
`Tags: ${user.tags.length ? user.tags.join(', ') : styleText('dim', '(none)')}`,
|
|
491
|
+
]
|
|
492
|
+
.filter(Boolean)
|
|
493
|
+
.join('\n'));
|
|
494
|
+
if (opt.sessions) {
|
|
495
|
+
const sessions = await db.database.selectFrom('sessions').where('userId', '=', user.id).selectAll().execute();
|
|
496
|
+
console.log(styleText('bold', 'Sessions:'));
|
|
497
|
+
if (!sessions.length)
|
|
498
|
+
console.log(styleText('dim', '(none)'));
|
|
499
|
+
else
|
|
500
|
+
for (const session of sessions) {
|
|
501
|
+
console.log(`\t${session.id}\tcreated ${formatDateRange(session.created).padEnd(40)}\texpires ${formatDateRange(session.expires).padEnd(40)}\t${session.elevated ? styleText('yellow', '(elevated)') : ''}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (opt.passkeys) {
|
|
505
|
+
const passkeys = await db.database.selectFrom('passkeys').where('userId', '=', user.id).selectAll().execute();
|
|
506
|
+
console.log(styleText('bold', 'Passkeys:'));
|
|
507
|
+
for (const passkey of passkeys) {
|
|
508
|
+
console.log(`\t${passkey.id}: created ${formatDateRange(passkey.createdAt).padEnd(40)} used ${passkey.counter} times. ${passkey.deviceType}, ${passkey.backedUp ? '' : 'not '}backed up; transports are [${passkey.transports.join(', ')}], ${passkey.name ? 'named ' + JSON.stringify(passkey.name) : 'unnamed'}.`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch (e_7) {
|
|
513
|
+
env_6.error = e_7;
|
|
514
|
+
env_6.hasError = true;
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
const result_6 = __disposeResources(env_6);
|
|
518
|
+
if (result_6)
|
|
519
|
+
await result_6;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
program
|
|
523
|
+
.command('toggle-admin')
|
|
524
|
+
.description('Toggle whether a user is an administrator')
|
|
525
|
+
.addArgument(lookup)
|
|
526
|
+
.action(async (_user) => {
|
|
527
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
528
|
+
try {
|
|
529
|
+
const user = await _user;
|
|
530
|
+
const _ = __addDisposableResource(env_8, db.connect(), true);
|
|
531
|
+
const isAdmin = !user.isAdmin;
|
|
532
|
+
await db.database.updateTable('users').set({ isAdmin }).where('id', '=', user.id).executeTakeFirstOrThrow();
|
|
533
|
+
console.log(`${userText(user)} is ${isAdmin ? 'now' : 'no longer'} an administrator. (${styleText(['whiteBright', 'bold'], isAdmin.toString())})`);
|
|
534
|
+
}
|
|
535
|
+
catch (e_8) {
|
|
536
|
+
env_8.error = e_8;
|
|
537
|
+
env_8.hasError = true;
|
|
538
|
+
}
|
|
539
|
+
finally {
|
|
540
|
+
const result_7 = __disposeResources(env_8);
|
|
541
|
+
if (result_7)
|
|
542
|
+
await result_7;
|
|
543
|
+
}
|
|
544
|
+
});
|
|
316
545
|
program
|
|
317
546
|
.command('status')
|
|
318
547
|
.alias('stats')
|
|
319
548
|
.description('Get information about the server')
|
|
320
549
|
.addOption(opts.host)
|
|
321
550
|
.action(async () => {
|
|
322
|
-
const
|
|
551
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
323
552
|
try {
|
|
324
553
|
console.log('Axium Server v' + program.version());
|
|
325
554
|
console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
326
555
|
console.log(styleText('whiteBright', 'Loaded config files:'), config.files.keys().toArray().join(', '));
|
|
327
556
|
process.stdout.write(styleText('whiteBright', 'Database: '));
|
|
328
|
-
const _ = __addDisposableResource(
|
|
557
|
+
const _ = __addDisposableResource(env_9, db.connect(), true);
|
|
329
558
|
try {
|
|
330
559
|
console.log(await db.statusText());
|
|
331
560
|
}
|
|
332
561
|
catch {
|
|
333
562
|
output.error('Unavailable');
|
|
334
563
|
}
|
|
335
|
-
console.log(styleText('whiteBright', 'Credentials authentication:'), config.auth.credentials ? styleText('yellow', 'enabled') : 'disabled');
|
|
336
564
|
console.log(styleText('whiteBright', 'Loaded plugins:'), Array.from(plugins)
|
|
337
|
-
.map(plugin => plugin.
|
|
565
|
+
.map(plugin => plugin.name)
|
|
338
566
|
.join(', ') || styleText('dim', '(none)'));
|
|
339
567
|
for (const plugin of plugins) {
|
|
340
568
|
if (!plugin.statusText)
|
|
@@ -343,14 +571,14 @@ program
|
|
|
343
571
|
console.log(await plugin.statusText());
|
|
344
572
|
}
|
|
345
573
|
}
|
|
346
|
-
catch (
|
|
347
|
-
|
|
348
|
-
|
|
574
|
+
catch (e_9) {
|
|
575
|
+
env_9.error = e_9;
|
|
576
|
+
env_9.hasError = true;
|
|
349
577
|
}
|
|
350
578
|
finally {
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
await
|
|
579
|
+
const result_8 = __disposeResources(env_9);
|
|
580
|
+
if (result_8)
|
|
581
|
+
await result_8;
|
|
354
582
|
}
|
|
355
583
|
});
|
|
356
584
|
program
|
|
@@ -368,8 +596,37 @@ program
|
|
|
368
596
|
.addOption(opts.force)
|
|
369
597
|
.addOption(opts.host)
|
|
370
598
|
.action(async (opt) => {
|
|
371
|
-
/* config.save({ auth: { secret: randomBytes(32).toString('base64') } }, true); */
|
|
372
599
|
await db.init({ ...opt, skip: opt.dbSkip }).catch(handleError);
|
|
373
600
|
await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(handleError);
|
|
374
601
|
});
|
|
602
|
+
program
|
|
603
|
+
.command('serve')
|
|
604
|
+
.description('Start the Axium server')
|
|
605
|
+
.option('-p, --port <port>', 'the port to listen on')
|
|
606
|
+
.option('--ssl <prefix>', 'the prefix for the cert.pem and key.pem SSL files')
|
|
607
|
+
.action(async (opt) => {
|
|
608
|
+
const server = await serve({
|
|
609
|
+
secure: opt.ssl ? true : config.web.secure,
|
|
610
|
+
ssl_cert: opt.ssl ? join(opt.ssl, 'cert.pem') : config.web.ssl_cert,
|
|
611
|
+
ssl_key: opt.ssl ? join(opt.ssl, 'key.pem') : config.web.ssl_key,
|
|
612
|
+
});
|
|
613
|
+
const port = !Number.isNaN(Number.parseInt(opt.port ?? '')) ? Number.parseInt(opt.port) : config.web.port;
|
|
614
|
+
server.listen(port, () => {
|
|
615
|
+
console.log('Server is listening on port ' + port);
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
program.command('link').description('Link svelte page routes').action(linkRoutes);
|
|
619
|
+
program.command('unlink').description('Unlink svelte page routes').action(unlinkRoutes);
|
|
620
|
+
program
|
|
621
|
+
.command('list-links')
|
|
622
|
+
.description('List linked routes')
|
|
623
|
+
.action(async () => {
|
|
624
|
+
for (const link of listRouteLinks()) {
|
|
625
|
+
const idText = link.id.startsWith('#') ? `(${link.id.slice(1)})` : link.id;
|
|
626
|
+
const toColor = await access(link.to)
|
|
627
|
+
.then(() => 'white')
|
|
628
|
+
.catch(() => 'redBright');
|
|
629
|
+
console.log(`${idText}:\t ${styleText('cyanBright', link.from)}\t->\t${styleText(toColor, link.to)}`);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
375
632
|
program.parse();
|
package/dist/config.d.ts
CHANGED
|
@@ -10,8 +10,6 @@ export interface Config extends Record<string, unknown> {
|
|
|
10
10
|
disabled: string[];
|
|
11
11
|
};
|
|
12
12
|
auth: {
|
|
13
|
-
credentials: boolean;
|
|
14
|
-
debug: boolean;
|
|
15
13
|
origin: string;
|
|
16
14
|
/** In minutes */
|
|
17
15
|
passkey_probation: number;
|
|
@@ -36,20 +34,28 @@ export interface Config extends Record<string, unknown> {
|
|
|
36
34
|
console: boolean;
|
|
37
35
|
};
|
|
38
36
|
web: {
|
|
37
|
+
assets: string;
|
|
38
|
+
disable_cache: boolean;
|
|
39
|
+
port: number;
|
|
39
40
|
prefix: string;
|
|
41
|
+
routes: string;
|
|
42
|
+
secure: boolean;
|
|
43
|
+
ssl_key: string;
|
|
44
|
+
ssl_cert: string;
|
|
45
|
+
template: string;
|
|
40
46
|
};
|
|
41
47
|
}
|
|
42
|
-
export declare const configFiles: Map<string,
|
|
48
|
+
export declare const configFiles: Map<string, File>;
|
|
43
49
|
export declare function plainConfig(): Omit<Config, keyof typeof configShortcuts>;
|
|
44
50
|
declare const configShortcuts: {
|
|
45
|
-
findPath: typeof
|
|
51
|
+
findPath: typeof findConfigPaths;
|
|
46
52
|
load: typeof loadConfig;
|
|
47
53
|
loadDefaults: typeof loadDefaultConfigs;
|
|
48
54
|
plain: typeof plainConfig;
|
|
49
55
|
save: typeof saveConfig;
|
|
50
56
|
saveTo: typeof saveConfigTo;
|
|
51
57
|
set: typeof setConfig;
|
|
52
|
-
files: Map<string,
|
|
58
|
+
files: Map<string, File>;
|
|
53
59
|
};
|
|
54
60
|
export declare const config: Config & typeof configShortcuts;
|
|
55
61
|
export default config;
|
|
@@ -62,8 +68,6 @@ export declare const File: z.ZodObject<{
|
|
|
62
68
|
disabled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
63
69
|
}, z.core.$strip>>;
|
|
64
70
|
auth: z.ZodOptional<z.ZodObject<{
|
|
65
|
-
credentials: z.ZodOptional<z.ZodBoolean>;
|
|
66
|
-
debug: z.ZodOptional<z.ZodBoolean>;
|
|
67
71
|
origin: z.ZodOptional<z.ZodString>;
|
|
68
72
|
passkey_probation: z.ZodOptional<z.ZodNumber>;
|
|
69
73
|
rp_id: z.ZodOptional<z.ZodString>;
|
|
@@ -91,13 +95,22 @@ export declare const File: z.ZodObject<{
|
|
|
91
95
|
console: z.ZodOptional<z.ZodBoolean>;
|
|
92
96
|
}, z.core.$strip>>;
|
|
93
97
|
web: z.ZodOptional<z.ZodObject<{
|
|
98
|
+
assets: z.ZodOptional<z.ZodString>;
|
|
99
|
+
disable_cache: z.ZodOptional<z.ZodBoolean>;
|
|
100
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
94
101
|
prefix: z.ZodOptional<z.ZodString>;
|
|
102
|
+
routes: z.ZodOptional<z.ZodString>;
|
|
103
|
+
secure: z.ZodOptional<z.ZodBoolean>;
|
|
104
|
+
ssl_key: z.ZodOptional<z.ZodString>;
|
|
105
|
+
ssl_cert: z.ZodOptional<z.ZodString>;
|
|
106
|
+
template: z.ZodOptional<z.ZodString>;
|
|
95
107
|
}, z.core.$strip>>;
|
|
96
108
|
include: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
97
109
|
plugins: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
98
110
|
}, z.core.$loose>;
|
|
99
111
|
export interface File extends PartialRecursive<Config>, z.infer<typeof File> {
|
|
100
112
|
}
|
|
113
|
+
export declare function addConfigDefaults(other: PartialRecursive<Config>, _target?: Record<string, any>): void;
|
|
101
114
|
/**
|
|
102
115
|
* Update the current config
|
|
103
116
|
*/
|
|
@@ -132,4 +145,4 @@ export declare function saveConfigTo(path: string, changed: PartialRecursive<Con
|
|
|
132
145
|
/**
|
|
133
146
|
* Find the path to the config file
|
|
134
147
|
*/
|
|
135
|
-
export declare function
|
|
148
|
+
export declare function findConfigPaths(): string[];
|
package/dist/config.js
CHANGED
|
@@ -3,14 +3,15 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path/posix';
|
|
4
4
|
import { deepAssign, omit } from 'utilium';
|
|
5
5
|
import * as z from 'zod/v4';
|
|
6
|
-
import {
|
|
6
|
+
import { _setDebugOutput, dirs, logger, output } from './io.js';
|
|
7
7
|
import { loadPlugin } from './plugins.js';
|
|
8
|
-
|
|
8
|
+
import { _unique } from './state.js';
|
|
9
|
+
export const configFiles = _unique('configFiles', new Map());
|
|
9
10
|
export function plainConfig() {
|
|
10
11
|
return omit(config, Object.keys(configShortcuts));
|
|
11
12
|
}
|
|
12
13
|
const configShortcuts = {
|
|
13
|
-
findPath:
|
|
14
|
+
findPath: findConfigPaths,
|
|
14
15
|
load: loadConfig,
|
|
15
16
|
loadDefaults: loadDefaultConfigs,
|
|
16
17
|
plain: plainConfig,
|
|
@@ -19,18 +20,16 @@ const configShortcuts = {
|
|
|
19
20
|
set: setConfig,
|
|
20
21
|
files: configFiles,
|
|
21
22
|
};
|
|
22
|
-
export const config = {
|
|
23
|
+
export const config = _unique('config', {
|
|
23
24
|
...configShortcuts,
|
|
24
25
|
api: {
|
|
25
26
|
disable_metadata: false,
|
|
26
|
-
cookie_auth:
|
|
27
|
+
cookie_auth: true,
|
|
27
28
|
},
|
|
28
29
|
apps: {
|
|
29
30
|
disabled: [],
|
|
30
31
|
},
|
|
31
32
|
auth: {
|
|
32
|
-
credentials: false,
|
|
33
|
-
debug: false,
|
|
34
33
|
origin: 'https://test.localhost',
|
|
35
34
|
passkey_probation: 60,
|
|
36
35
|
rp_id: 'test.localhost',
|
|
@@ -52,9 +51,17 @@ export const config = {
|
|
|
52
51
|
level: 'info',
|
|
53
52
|
},
|
|
54
53
|
web: {
|
|
54
|
+
assets: '',
|
|
55
|
+
disable_cache: false,
|
|
56
|
+
port: 443,
|
|
55
57
|
prefix: '',
|
|
58
|
+
routes: 'routes',
|
|
59
|
+
secure: true,
|
|
60
|
+
ssl_key: resolve(dirs[0], 'ssl_key.pem'),
|
|
61
|
+
ssl_cert: resolve(dirs[0], 'ssl_cert.pem'),
|
|
62
|
+
template: join(import.meta.dirname, '../web/template.html'),
|
|
56
63
|
},
|
|
57
|
-
};
|
|
64
|
+
});
|
|
58
65
|
export default config;
|
|
59
66
|
// config from file
|
|
60
67
|
export const File = z
|
|
@@ -72,8 +79,6 @@ export const File = z
|
|
|
72
79
|
.partial(),
|
|
73
80
|
auth: z
|
|
74
81
|
.object({
|
|
75
|
-
credentials: z.boolean(),
|
|
76
|
-
debug: z.boolean(),
|
|
77
82
|
origin: z.string(),
|
|
78
83
|
/** In minutes */
|
|
79
84
|
passkey_probation: z.number(),
|
|
@@ -104,13 +109,32 @@ export const File = z
|
|
|
104
109
|
.partial(),
|
|
105
110
|
web: z
|
|
106
111
|
.object({
|
|
112
|
+
assets: z.string(),
|
|
113
|
+
disable_cache: z.boolean(),
|
|
114
|
+
port: z.number().min(1).max(65535),
|
|
107
115
|
prefix: z.string(),
|
|
116
|
+
routes: z.string(),
|
|
117
|
+
secure: z.boolean(),
|
|
118
|
+
ssl_key: z.string(),
|
|
119
|
+
ssl_cert: z.string(),
|
|
120
|
+
template: z.string(),
|
|
108
121
|
})
|
|
109
122
|
.partial(),
|
|
110
123
|
include: z.array(z.string()).optional(),
|
|
111
124
|
plugins: z.array(z.string()).optional(),
|
|
112
125
|
})
|
|
113
126
|
.partial();
|
|
127
|
+
export function addConfigDefaults(other, _target = config) {
|
|
128
|
+
for (const [key, value] of Object.entries(other)) {
|
|
129
|
+
if (!(key in _target) || _target[key] === null || _target[key] === undefined || Number.isNaN(_target[key])) {
|
|
130
|
+
_target[key] = value;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (typeof value == 'object' && value != null && typeof _target[key] == 'object') {
|
|
134
|
+
addConfigDefaults(value, _target[key]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
114
138
|
/**
|
|
115
139
|
* Update the current config
|
|
116
140
|
*/
|
|
@@ -119,11 +143,14 @@ export function setConfig(other) {
|
|
|
119
143
|
logger.detach(output);
|
|
120
144
|
if (config.log.console)
|
|
121
145
|
logger.attach(output, { output: config.log.level });
|
|
146
|
+
_setDebugOutput(config.debug);
|
|
122
147
|
}
|
|
123
148
|
/**
|
|
124
149
|
* Load the config from the provided path
|
|
125
150
|
*/
|
|
126
151
|
export async function loadConfig(path, options = {}) {
|
|
152
|
+
if (configFiles.has(path))
|
|
153
|
+
return;
|
|
127
154
|
let json;
|
|
128
155
|
try {
|
|
129
156
|
json = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -131,19 +158,20 @@ export async function loadConfig(path, options = {}) {
|
|
|
131
158
|
catch (e) {
|
|
132
159
|
if (!options.optional)
|
|
133
160
|
throw e;
|
|
134
|
-
|
|
161
|
+
output.debug(`Skipping config at ${path} (${e.message})`);
|
|
135
162
|
return;
|
|
136
163
|
}
|
|
137
164
|
const file = options.strict ? File.parse(json) : json;
|
|
138
165
|
configFiles.set(path, file);
|
|
139
166
|
setConfig(file);
|
|
167
|
+
output.debug('Loaded config: ' + path);
|
|
140
168
|
for (const include of file.include ?? [])
|
|
141
169
|
await loadConfig(join(dirname(path), include), { optional: true });
|
|
142
170
|
for (const plugin of file.plugins ?? [])
|
|
143
171
|
await loadPlugin(plugin.startsWith('.') ? resolve(dirname(path), plugin) : plugin);
|
|
144
172
|
}
|
|
145
173
|
export async function loadDefaultConfigs() {
|
|
146
|
-
for (const path of
|
|
174
|
+
for (const path of findConfigPaths()) {
|
|
147
175
|
if (!existsSync(path))
|
|
148
176
|
writeFileSync(path, '{}');
|
|
149
177
|
await loadConfig(path, { optional: true });
|
|
@@ -153,7 +181,7 @@ export async function loadDefaultConfigs() {
|
|
|
153
181
|
* Update the current config and write the updated config to the appropriate file
|
|
154
182
|
*/
|
|
155
183
|
export function saveConfig(changed, global = false) {
|
|
156
|
-
saveConfigTo(
|
|
184
|
+
saveConfigTo(findConfigPaths().at(global ? 0 : -1), changed);
|
|
157
185
|
}
|
|
158
186
|
/**
|
|
159
187
|
* Update the current config and write the updated config to the provided path
|
|
@@ -162,16 +190,17 @@ export function saveConfigTo(path, changed) {
|
|
|
162
190
|
setConfig(changed);
|
|
163
191
|
const config = configFiles.get(path) ?? {};
|
|
164
192
|
Object.assign(config, { ...changed, db: { ...config.db, ...changed.db } });
|
|
165
|
-
|
|
193
|
+
output.debug(`Wrote config to ${path}`);
|
|
166
194
|
writeFileSync(path, JSON.stringify(config));
|
|
167
195
|
}
|
|
168
196
|
/**
|
|
169
197
|
* Find the path to the config file
|
|
170
198
|
*/
|
|
171
|
-
export function
|
|
199
|
+
export function findConfigPaths() {
|
|
200
|
+
const paths = dirs.map(dir => join(dir, 'config.json'));
|
|
172
201
|
if (process.env.AXIUM_CONFIG)
|
|
173
|
-
|
|
174
|
-
return
|
|
202
|
+
paths.push(process.env.AXIUM_CONFIG);
|
|
203
|
+
return paths;
|
|
175
204
|
}
|
|
176
205
|
if (process.env.AXIUM_CONFIG)
|
|
177
206
|
await loadConfig(process.env.AXIUM_CONFIG);
|