@axium/server 0.30.0 → 0.31.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/dist/api/admin.js +2 -8
- package/dist/api/users.js +2 -2
- package/dist/database.d.ts +1 -1
- package/dist/database.js +23 -18
- package/dist/linking.js +3 -3
- package/dist/main.js +70 -73
- package/dist/requests.d.ts +2 -1
- package/dist/requests.js +10 -0
- package/package.json +2 -2
package/dist/api/admin.js
CHANGED
|
@@ -8,7 +8,7 @@ import { audit, events, getEvents } from '../audit.js';
|
|
|
8
8
|
import { requireSession } from '../auth.js';
|
|
9
9
|
import { config } from '../config.js';
|
|
10
10
|
import { count, database as db } from '../database.js';
|
|
11
|
-
import { error, withError } from '../requests.js';
|
|
11
|
+
import { error, parseSearch, withError } from '../requests.js';
|
|
12
12
|
import { addRoute } from '../routes.js';
|
|
13
13
|
async function assertAdmin(route, req) {
|
|
14
14
|
const admin = await requireSession(req);
|
|
@@ -122,13 +122,7 @@ addRoute({
|
|
|
122
122
|
async GET(req) {
|
|
123
123
|
await assertAdmin(this, req);
|
|
124
124
|
const filter = { severity: Severity.Info };
|
|
125
|
-
|
|
126
|
-
const search = Object.fromEntries(new URL(req.url).searchParams);
|
|
127
|
-
Object.assign(filter, AuditFilter.parse(search));
|
|
128
|
-
}
|
|
129
|
-
catch (e) {
|
|
130
|
-
error(400, e instanceof z.core.$ZodError ? z.prettifyError(e) : 'invalid body');
|
|
131
|
-
}
|
|
125
|
+
Object.assign(filter, parseSearch(req, AuditFilter));
|
|
132
126
|
return await getEvents(filter)
|
|
133
127
|
.select(eb => jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'audit_log.userId').select(['id', 'name'])).as('user'))
|
|
134
128
|
.execute();
|
package/dist/api/users.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Register a new passkey for a new or existing user. */
|
|
2
|
-
import { preferenceDefaults,
|
|
2
|
+
import { preferenceDefaults, Preferences } from '@axium/core';
|
|
3
3
|
import { PasskeyAuthenticationResponse, PasskeyRegistration } from '@axium/core/passkeys';
|
|
4
4
|
import { LogoutSessions, UserAuthOptions, UserChangeable } from '@axium/core/user';
|
|
5
5
|
import * as webauthn from '@simplewebauthn/server';
|
|
@@ -43,7 +43,7 @@ addRoute({
|
|
|
43
43
|
if ('email' in body)
|
|
44
44
|
body.emailVerified = null;
|
|
45
45
|
if ('preferences' in body)
|
|
46
|
-
body.preferences = Object.assign(structuredClone(preferenceDefaults), await
|
|
46
|
+
body.preferences = Object.assign(structuredClone(preferenceDefaults), await Preferences.partial().parseAsync(body.preferences).catch(withError('Invalid preferences', 400)));
|
|
47
47
|
const result = await db
|
|
48
48
|
.updateTable('users')
|
|
49
49
|
.set(body)
|
package/dist/database.d.ts
CHANGED
|
@@ -591,7 +591,7 @@ export interface CheckOptions extends OpOptions {
|
|
|
591
591
|
/**
|
|
592
592
|
* Checks that a table has the expected column types, nullability, and default values.
|
|
593
593
|
*/
|
|
594
|
-
export declare function checkTableTypes<TB extends keyof Schema & string>(tableName: TB, types: Table, opt: CheckOptions): Promise<void>;
|
|
594
|
+
export declare function checkTableTypes<TB extends keyof Schema & string>(tableName: TB, types: Table, opt: CheckOptions, tableMetadata?: kysely.TableMetadata[]): Promise<void>;
|
|
595
595
|
export declare function clean(opt: Partial<OpOptions>): Promise<void>;
|
|
596
596
|
export declare function rotatePassword(): Promise<void>;
|
|
597
597
|
export {};
|
package/dist/database.js
CHANGED
|
@@ -365,8 +365,7 @@ export function* getSchemaFiles() {
|
|
|
365
365
|
yield [name, SchemaFile.parse(plugin._db)];
|
|
366
366
|
}
|
|
367
367
|
catch (e) {
|
|
368
|
-
|
|
369
|
-
throw `Invalid database configuration for plugin "${name}":\n${text}`;
|
|
368
|
+
throw `Invalid database configuration for plugin "${name}":\n${io.errorText(e)}`;
|
|
370
369
|
}
|
|
371
370
|
}
|
|
372
371
|
}
|
|
@@ -382,10 +381,16 @@ export function getFullSchema(opt = {}) {
|
|
|
382
381
|
let currentSchema = { tables: {}, indexes: [] };
|
|
383
382
|
fullSchema.versions[pluginName] = file.latest;
|
|
384
383
|
for (const [version, schema] of file.versions.entries()) {
|
|
385
|
-
if (schema.delta)
|
|
386
|
-
applyDeltaToSchema(currentSchema, schema);
|
|
387
|
-
else
|
|
384
|
+
if (!schema.delta)
|
|
388
385
|
currentSchema = schema;
|
|
386
|
+
else {
|
|
387
|
+
try {
|
|
388
|
+
applyDeltaToSchema(currentSchema, schema);
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
throw `Failed to apply version ${version - 1}->${version} delta to ${pluginName}: ${io.errorText(e)}`;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
389
394
|
if (version === file.latest)
|
|
390
395
|
break;
|
|
391
396
|
}
|
|
@@ -423,20 +428,20 @@ export function setUpgradeInfo(info) {
|
|
|
423
428
|
}
|
|
424
429
|
export function applyTableDeltaToSchema(table, delta) {
|
|
425
430
|
for (const column of delta.drop_columns) {
|
|
426
|
-
if (column in table)
|
|
431
|
+
if (column in table.columns)
|
|
427
432
|
delete table.columns[column];
|
|
428
433
|
else
|
|
429
|
-
throw `
|
|
434
|
+
throw `can't drop column ${column} because it does not exist`;
|
|
430
435
|
}
|
|
431
436
|
for (const [name, column] of Object.entries(delta.add_columns)) {
|
|
432
|
-
if (name in table)
|
|
433
|
-
throw `
|
|
437
|
+
if (name in table.columns)
|
|
438
|
+
throw `can't add column ${name} because it already exists`;
|
|
434
439
|
table.columns[name] = column;
|
|
435
440
|
}
|
|
436
441
|
for (const [name, columnDelta] of Object.entries(delta.alter_columns)) {
|
|
437
442
|
const column = table.columns[name];
|
|
438
443
|
if (!column)
|
|
439
|
-
throw `
|
|
444
|
+
throw `can't modify column ${name} because it does not exist`;
|
|
440
445
|
if (columnDelta.type)
|
|
441
446
|
column.type = columnDelta.type;
|
|
442
447
|
if (columnDelta.default)
|
|
@@ -459,11 +464,11 @@ export function applyTableDeltaToSchema(table, delta) {
|
|
|
459
464
|
if (table.constraints[name])
|
|
460
465
|
delete table.constraints[name];
|
|
461
466
|
else
|
|
462
|
-
throw `
|
|
467
|
+
throw `can't drop constraint ${name} because it does not exist`;
|
|
463
468
|
}
|
|
464
469
|
for (const [name, constraint] of Object.entries(delta.add_constraints)) {
|
|
465
470
|
if (table.constraints[name])
|
|
466
|
-
throw `
|
|
471
|
+
throw `can't add constraint ${name} because it already exists`;
|
|
467
472
|
table.constraints[name] = constraint;
|
|
468
473
|
}
|
|
469
474
|
}
|
|
@@ -472,11 +477,11 @@ export function applyDeltaToSchema(schema, delta) {
|
|
|
472
477
|
if (tableName in schema.tables)
|
|
473
478
|
delete schema.tables[tableName];
|
|
474
479
|
else
|
|
475
|
-
throw `
|
|
480
|
+
throw `can't drop table ${tableName} because it does not exist`;
|
|
476
481
|
}
|
|
477
482
|
for (const [tableName, table] of Object.entries(delta.add_tables)) {
|
|
478
483
|
if (tableName in schema.tables)
|
|
479
|
-
throw `
|
|
484
|
+
throw `can't add table ${tableName} because it already exists`;
|
|
480
485
|
else
|
|
481
486
|
schema.tables[tableName] = table;
|
|
482
487
|
}
|
|
@@ -484,7 +489,7 @@ export function applyDeltaToSchema(schema, delta) {
|
|
|
484
489
|
if (tableName in schema.tables)
|
|
485
490
|
applyTableDeltaToSchema(schema.tables[tableName], tableDelta);
|
|
486
491
|
else
|
|
487
|
-
throw `
|
|
492
|
+
throw `can't modify table ${tableName} because it does not exist`;
|
|
488
493
|
}
|
|
489
494
|
}
|
|
490
495
|
export function validateDelta(delta) {
|
|
@@ -782,10 +787,10 @@ export async function applyDelta(delta, forceAbort = false) {
|
|
|
782
787
|
/**
|
|
783
788
|
* Checks that a table has the expected column types, nullability, and default values.
|
|
784
789
|
*/
|
|
785
|
-
export async function checkTableTypes(tableName, types, opt) {
|
|
790
|
+
export async function checkTableTypes(tableName, types, opt, tableMetadata) {
|
|
786
791
|
io.start(`Checking table ${tableName}`);
|
|
787
|
-
|
|
788
|
-
const table =
|
|
792
|
+
tableMetadata ||= await database.introspection.getTables();
|
|
793
|
+
const table = tableMetadata.find(t => (t.schema == 'public' ? t.name : `${t.schema}.${t.name}`) === tableName);
|
|
789
794
|
if (!table)
|
|
790
795
|
throw 'missing.';
|
|
791
796
|
const columns = Object.fromEntries(table.columns.map(c => [c.name, c]));
|
package/dist/linking.js
CHANGED
|
@@ -40,7 +40,7 @@ export function linkRoutes(options = {}) {
|
|
|
40
40
|
unlinkSync(from);
|
|
41
41
|
}
|
|
42
42
|
catch (e) {
|
|
43
|
-
io.exit(e
|
|
43
|
+
io.exit(e);
|
|
44
44
|
}
|
|
45
45
|
io.done();
|
|
46
46
|
io.start('Re-linking ' + text);
|
|
@@ -50,7 +50,7 @@ export function linkRoutes(options = {}) {
|
|
|
50
50
|
io.done();
|
|
51
51
|
}
|
|
52
52
|
catch (e) {
|
|
53
|
-
io.exit(e
|
|
53
|
+
io.exit(e);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -65,7 +65,7 @@ export function unlinkRoutes(options = {}) {
|
|
|
65
65
|
io.done();
|
|
66
66
|
}
|
|
67
67
|
catch (e) {
|
|
68
|
-
io.exit(e
|
|
68
|
+
io.exit(e);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
}
|
package/dist/main.js
CHANGED
|
@@ -60,17 +60,17 @@ import { Argument, Option, program } from 'commander';
|
|
|
60
60
|
import { access } from 'node:fs/promises';
|
|
61
61
|
import { join, resolve } from 'node:path/posix';
|
|
62
62
|
import { createInterface } from 'node:readline/promises';
|
|
63
|
-
import {
|
|
63
|
+
import { parseArgs, styleText } from 'node:util';
|
|
64
64
|
import { getByString, isJSON, setByString } from 'utilium';
|
|
65
65
|
import * as z from 'zod';
|
|
66
66
|
import $pkg from '../package.json' with { type: 'json' };
|
|
67
67
|
import { audit, getEvents, styleSeverity } from './audit.js';
|
|
68
|
+
import { diffUpdate, lookupUser, userText } from './cli.js';
|
|
68
69
|
import config, { configFiles, FileSchema, saveConfigTo } from './config.js';
|
|
69
70
|
import * as db from './database.js';
|
|
70
71
|
import { _portActions, _portMethods, restrictedPorts } from './io.js';
|
|
71
72
|
import { linkRoutes, listRouteLinks, unlinkRoutes } from './linking.js';
|
|
72
73
|
import { serve } from './serve.js';
|
|
73
|
-
import { diffUpdate, lookupUser, userText } from './cli.js';
|
|
74
74
|
async function rlConfirm(question = 'Is this ok') {
|
|
75
75
|
const { data, error } = z
|
|
76
76
|
.stringbool()
|
|
@@ -122,13 +122,11 @@ try {
|
|
|
122
122
|
.name('axium')
|
|
123
123
|
.description('Axium server CLI')
|
|
124
124
|
.configureHelp({ showGlobalOptions: true })
|
|
125
|
-
.option('--safe', 'do not execute code from plugins')
|
|
125
|
+
.option('--safe', 'do not execute code from plugins', false)
|
|
126
126
|
.option('--debug', 'override debug mode')
|
|
127
127
|
.option('--no-debug', 'override debug mode')
|
|
128
|
-
.option('-c, --config <path>', 'path to the config file')
|
|
129
|
-
|
|
130
|
-
noAutoDB = ['init', 'serve', 'check'];
|
|
131
|
-
program.hook('preAction', (_, action) => {
|
|
128
|
+
.option('-c, --config <path>', 'path to the config file')
|
|
129
|
+
.hook('preAction', (_, action) => {
|
|
132
130
|
const opt = action.optsWithGlobals();
|
|
133
131
|
opt.force && io.warn('--force: Protections disabled.');
|
|
134
132
|
if (typeof opt.debug == 'boolean') {
|
|
@@ -142,23 +140,19 @@ try {
|
|
|
142
140
|
if (!noAutoDB.includes(action.name()))
|
|
143
141
|
throw e;
|
|
144
142
|
}
|
|
145
|
-
})
|
|
146
|
-
|
|
143
|
+
})
|
|
144
|
+
.hook('postAction', async (_, action) => {
|
|
147
145
|
if (!noAutoDB.includes(action.name()))
|
|
148
146
|
await db.database.destroy();
|
|
149
|
-
})
|
|
147
|
+
})
|
|
148
|
+
.on('option:debug', () => config.set({ debug: true }));
|
|
149
|
+
noAutoDB = ['init', 'serve', 'check'];
|
|
150
150
|
// Options shared by multiple (sub)commands
|
|
151
151
|
opts = {
|
|
152
|
-
// database specific
|
|
153
|
-
host: new Option('-H, --host <host>', 'the host of the database.').argParser(value => {
|
|
154
|
-
const [hostname, port] = value?.split(':') ?? [];
|
|
155
|
-
config.db.host = hostname || config.db.host;
|
|
156
|
-
config.db.port = port && Number.isSafeInteger(parseInt(port)) ? parseInt(port) : config.db.port;
|
|
157
|
-
}),
|
|
158
152
|
check: new Option('--check', 'check the database schema after initialization').default(false),
|
|
159
153
|
force: new Option('-f, --force', 'force the operation').default(false),
|
|
160
154
|
global: new Option('-g, --global', 'apply the operation globally').default(false),
|
|
161
|
-
timeout: new Option('-t, --timeout <ms>', 'how long to wait for commands to complete.').default(
|
|
155
|
+
timeout: new Option('-t, --timeout <ms>', 'how long to wait for commands to complete.').default(1000).argParser(value => {
|
|
162
156
|
const timeout = parseInt(value);
|
|
163
157
|
if (!Number.isSafeInteger(timeout) || timeout < 0)
|
|
164
158
|
io.warn('Invalid timeout value, using default.');
|
|
@@ -166,17 +160,17 @@ try {
|
|
|
166
160
|
}),
|
|
167
161
|
packagesDir: new Option('-p, --packages-dir <dir>', 'the directory to look for packages in'),
|
|
168
162
|
};
|
|
169
|
-
axiumDB = program.command('db').alias('database').description('Manage the database').addOption(opts.timeout)
|
|
163
|
+
axiumDB = program.command('db').alias('database').description('Manage the database').addOption(opts.timeout);
|
|
170
164
|
axiumDB
|
|
171
165
|
.command('init')
|
|
172
166
|
.description('Initialize the database')
|
|
173
167
|
.addOption(opts.force)
|
|
174
|
-
.option('-s, --skip', 'If the user, database, or schema already exists, skip trying to create it.')
|
|
168
|
+
.option('-s, --skip', 'If the user, database, or schema already exists, skip trying to create it.', false)
|
|
175
169
|
.addOption(opts.check)
|
|
176
|
-
.action(async (
|
|
177
|
-
const opt =
|
|
178
|
-
await db.init(opt).catch(io.
|
|
179
|
-
await dbInitTables().catch(io.
|
|
170
|
+
.action(async function axium_db_init() {
|
|
171
|
+
const opt = this.optsWithGlobals();
|
|
172
|
+
await db.init(opt).catch(io.exit);
|
|
173
|
+
await dbInitTables().catch(io.exit);
|
|
180
174
|
});
|
|
181
175
|
axiumDB
|
|
182
176
|
.command('status')
|
|
@@ -204,9 +198,9 @@ try {
|
|
|
204
198
|
io.warn(`Database has existing ${key}. Use --force if you really want to drop the database.`);
|
|
205
199
|
process.exit(2);
|
|
206
200
|
}
|
|
207
|
-
await db._sql('DROP DATABASE axium', 'Dropping database').catch(io.
|
|
208
|
-
await db._sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges').catch(io.
|
|
209
|
-
await db._sql('DROP USER axium', 'Dropping user').catch(io.
|
|
201
|
+
await db._sql('DROP DATABASE axium', 'Dropping database').catch(io.exit);
|
|
202
|
+
await db._sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges').catch(io.exit);
|
|
203
|
+
await db._sql('DROP USER axium', 'Dropping user').catch(io.exit);
|
|
210
204
|
await db
|
|
211
205
|
.getHBA(opt)
|
|
212
206
|
.then(([content, writeBack]) => {
|
|
@@ -249,7 +243,7 @@ try {
|
|
|
249
243
|
console.log(table + ' '.repeat(maxTableName - table.length), '|', plugins);
|
|
250
244
|
}
|
|
251
245
|
await rlConfirm('Are you sure you want to wipe these tables and any dependents');
|
|
252
|
-
await db.database.deleteFrom(Array.from(tables.keys())).execute().catch(io.
|
|
246
|
+
await db.database.deleteFrom(Array.from(tables.keys())).execute().catch(io.exit);
|
|
253
247
|
});
|
|
254
248
|
axiumDB
|
|
255
249
|
.command('check')
|
|
@@ -258,23 +252,20 @@ try {
|
|
|
258
252
|
.action(async (opt) => {
|
|
259
253
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
260
254
|
try {
|
|
261
|
-
await io.run('Checking for sudo', 'which sudo').catch(io.
|
|
262
|
-
await io.run('Checking for psql', 'which psql').catch(io.
|
|
255
|
+
await io.run('Checking for sudo', 'which sudo').catch(io.exit);
|
|
256
|
+
await io.run('Checking for psql', 'which psql').catch(io.exit);
|
|
263
257
|
const throwUnlessRows = (text) => {
|
|
264
258
|
if (text.includes('(0 rows)'))
|
|
265
259
|
throw 'missing.';
|
|
266
260
|
return text;
|
|
267
261
|
};
|
|
268
|
-
await db
|
|
269
|
-
|
|
270
|
-
.then(throwUnlessRows)
|
|
271
|
-
.catch(io.handleError);
|
|
272
|
-
await db._sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows).catch(io.handleError);
|
|
262
|
+
await db._sql(`SELECT 1 FROM pg_database WHERE datname = 'axium'`, 'Checking for database').then(throwUnlessRows).catch(io.exit);
|
|
263
|
+
await db._sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows).catch(io.exit);
|
|
273
264
|
io.start('Connecting to database');
|
|
274
265
|
const _ = __addDisposableResource(env_2, db.connect(), true);
|
|
275
266
|
io.done();
|
|
276
267
|
io.start('Getting schema metadata');
|
|
277
|
-
const schemas = await db.database.introspection.getSchemas().catch(io.
|
|
268
|
+
const schemas = await db.database.introspection.getSchemas().catch(io.exit);
|
|
278
269
|
io.done();
|
|
279
270
|
io.start('Checking for acl schema');
|
|
280
271
|
if (!schemas.find(s => s.name == 'acl'))
|
|
@@ -284,13 +275,21 @@ try {
|
|
|
284
275
|
const tablePromises = await Promise.all([
|
|
285
276
|
db.database.introspection.getTables(),
|
|
286
277
|
db.database.withSchema('acl').introspection.getTables(),
|
|
287
|
-
]).catch(io.
|
|
288
|
-
|
|
289
|
-
const tables = Object.fromEntries(
|
|
278
|
+
]).catch(io.exit);
|
|
279
|
+
const tableMetadata = tablePromises.flat();
|
|
280
|
+
const tables = Object.fromEntries(tableMetadata.map(t => [t.schema == 'public' ? t.name : `${t.schema}.${t.name}`, t]));
|
|
290
281
|
io.done();
|
|
291
|
-
|
|
282
|
+
io.start('Resolving database schemas');
|
|
283
|
+
let schema;
|
|
284
|
+
try {
|
|
285
|
+
schema = db.getFullSchema();
|
|
286
|
+
io.done();
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
io.exit(e);
|
|
290
|
+
}
|
|
292
291
|
for (const [name, table] of Object.entries(schema.tables)) {
|
|
293
|
-
await db.checkTableTypes(name, table, opt);
|
|
292
|
+
await db.checkTableTypes(name, table, opt, tableMetadata);
|
|
294
293
|
delete tables[name];
|
|
295
294
|
}
|
|
296
295
|
io.start('Checking for extra tables');
|
|
@@ -327,13 +326,13 @@ try {
|
|
|
327
326
|
.command('schema')
|
|
328
327
|
.description('Get the JSON schema for the database configuration file')
|
|
329
328
|
.option('-j, --json', 'values are JSON encoded')
|
|
330
|
-
.action(
|
|
329
|
+
.action(opt => {
|
|
331
330
|
try {
|
|
332
331
|
const schema = z.toJSONSchema(db.SchemaFile, { io: 'input' });
|
|
333
332
|
console.log(opt.json ? JSON.stringify(schema, null, 4) : schema);
|
|
334
333
|
}
|
|
335
334
|
catch (e) {
|
|
336
|
-
io.
|
|
335
|
+
io.exit(e);
|
|
337
336
|
}
|
|
338
337
|
});
|
|
339
338
|
axiumDB
|
|
@@ -342,7 +341,7 @@ try {
|
|
|
342
341
|
.alias('up')
|
|
343
342
|
.description('Upgrade the database to the latest version')
|
|
344
343
|
.option('--abort', 'Rollback changes instead of committing them')
|
|
345
|
-
.action(async (opt)
|
|
344
|
+
.action(async function axium_db_upgrade(opt) {
|
|
346
345
|
const deltas = [];
|
|
347
346
|
const info = db.getUpgradeInfo();
|
|
348
347
|
let empty = true;
|
|
@@ -401,7 +400,7 @@ try {
|
|
|
401
400
|
io.exit(e);
|
|
402
401
|
}
|
|
403
402
|
console.log('Applying delta.');
|
|
404
|
-
await db.applyDelta(delta, opt.abort).catch(io.
|
|
403
|
+
await db.applyDelta(delta, opt.abort).catch(io.exit);
|
|
405
404
|
info.upgrades.push({ timestamp: new Date(), from, to });
|
|
406
405
|
db.setUpgradeInfo(info);
|
|
407
406
|
});
|
|
@@ -449,13 +448,13 @@ try {
|
|
|
449
448
|
.command('config')
|
|
450
449
|
.description('Manage the configuration')
|
|
451
450
|
.addOption(opts.global)
|
|
452
|
-
.option('-j, --json', 'values are JSON encoded')
|
|
453
|
-
.option('-r, --redact', 'Do not output sensitive values');
|
|
451
|
+
.option('-j, --json', 'values are JSON encoded', false)
|
|
452
|
+
.option('-r, --redact', 'Do not output sensitive values', false);
|
|
454
453
|
axiumConfig
|
|
455
454
|
.command('dump')
|
|
456
455
|
.description('Output the entire current configuration')
|
|
457
|
-
.action(()
|
|
458
|
-
const opt =
|
|
456
|
+
.action(function axium_config_dump() {
|
|
457
|
+
const opt = this.optsWithGlobals();
|
|
459
458
|
const value = config.plain();
|
|
460
459
|
console.log(opt.json ? JSON.stringify(value, configReplacer(opt), 4) : value);
|
|
461
460
|
});
|
|
@@ -463,8 +462,8 @@ try {
|
|
|
463
462
|
.command('get')
|
|
464
463
|
.description('Get a config value')
|
|
465
464
|
.argument('<key>', 'the key to get')
|
|
466
|
-
.action((key)
|
|
467
|
-
const opt =
|
|
465
|
+
.action(function axium_config_get(key) {
|
|
466
|
+
const opt = this.optsWithGlobals();
|
|
468
467
|
const value = getByString(config.plain(), key);
|
|
469
468
|
console.log(opt.json ? JSON.stringify(value, configReplacer(opt), 4) : value);
|
|
470
469
|
});
|
|
@@ -473,8 +472,8 @@ try {
|
|
|
473
472
|
.description('Set a config value. Note setting objects is not supported.')
|
|
474
473
|
.argument('<key>', 'the key to set')
|
|
475
474
|
.argument('<value>', 'the value')
|
|
476
|
-
.action((key, value)
|
|
477
|
-
const opt =
|
|
475
|
+
.action(function axium_config_set(key, value) {
|
|
476
|
+
const opt = this.optsWithGlobals();
|
|
478
477
|
if (opt.json && !isJSON(value))
|
|
479
478
|
io.exit('Invalid JSON');
|
|
480
479
|
const obj = {};
|
|
@@ -500,7 +499,7 @@ try {
|
|
|
500
499
|
console.log(opt.json ? JSON.stringify(schema, configReplacer(opt), 4) : schema);
|
|
501
500
|
}
|
|
502
501
|
catch (e) {
|
|
503
|
-
io.
|
|
502
|
+
io.exit(e);
|
|
504
503
|
}
|
|
505
504
|
});
|
|
506
505
|
axiumPlugin = program.command('plugin').alias('plugins').description('Manage plugins').addOption(opts.global);
|
|
@@ -510,7 +509,7 @@ try {
|
|
|
510
509
|
.description('List loaded plugins')
|
|
511
510
|
.option('-l, --long', 'use the long listing format')
|
|
512
511
|
.option('--no-versions', 'do not show plugin versions')
|
|
513
|
-
.action(
|
|
512
|
+
.action(opt => {
|
|
514
513
|
if (!plugins.size) {
|
|
515
514
|
console.log('No plugins loaded.');
|
|
516
515
|
return;
|
|
@@ -557,7 +556,7 @@ try {
|
|
|
557
556
|
.addOption(opts.timeout)
|
|
558
557
|
.addOption(opts.check)
|
|
559
558
|
.argument('<plugin>', 'the plugin to initialize')
|
|
560
|
-
.action(async (search
|
|
559
|
+
.action(async (search) => {
|
|
561
560
|
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
562
561
|
try {
|
|
563
562
|
const plugin = _findPlugin(search);
|
|
@@ -582,7 +581,7 @@ try {
|
|
|
582
581
|
.description('List apps added by plugins')
|
|
583
582
|
.option('-l, --long', 'use the long listing format')
|
|
584
583
|
.option('-b, --builtin', 'include built-in apps')
|
|
585
|
-
.action(
|
|
584
|
+
.action(opt => {
|
|
586
585
|
if (!apps.size) {
|
|
587
586
|
console.log('No apps.');
|
|
588
587
|
return;
|
|
@@ -692,7 +691,6 @@ try {
|
|
|
692
691
|
.command('status')
|
|
693
692
|
.alias('stats')
|
|
694
693
|
.description('Get information about the server')
|
|
695
|
-
.addOption(opts.host)
|
|
696
694
|
.action(async () => {
|
|
697
695
|
console.log('Axium Server v' + $pkg.version);
|
|
698
696
|
console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
@@ -721,37 +719,37 @@ try {
|
|
|
721
719
|
.addOption(new Option('-m, --method <method>', 'the method to use').choices(_portMethods).default('node-cap'))
|
|
722
720
|
.option('-N, --node <path>', 'the path to the node binary')
|
|
723
721
|
.action(async (action, opt) => {
|
|
724
|
-
await restrictedPorts({ ...opt, action }).catch(io.
|
|
722
|
+
await restrictedPorts({ ...opt, action }).catch(io.exit);
|
|
725
723
|
});
|
|
726
724
|
program
|
|
727
725
|
.command('init')
|
|
728
726
|
.description('Install Axium server')
|
|
729
727
|
.addOption(opts.force)
|
|
730
|
-
.addOption(opts.host)
|
|
731
728
|
.addOption(opts.check)
|
|
732
729
|
.addOption(opts.packagesDir)
|
|
733
|
-
.option('-s, --skip', 'Skip already initialized steps')
|
|
730
|
+
.option('-s, --skip', 'Skip already initialized steps', false)
|
|
734
731
|
.action(async (opt) => {
|
|
735
|
-
await db.init(opt).catch(io.
|
|
736
|
-
await dbInitTables().catch(io.
|
|
737
|
-
await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(io.
|
|
732
|
+
await db.init(opt).catch(io.exit);
|
|
733
|
+
await dbInitTables().catch(io.exit);
|
|
734
|
+
await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(io.exit);
|
|
738
735
|
});
|
|
739
736
|
program
|
|
740
737
|
.command('serve')
|
|
741
738
|
.description('Start the Axium server')
|
|
742
|
-
.option('-p, --port <port>', 'the port to listen on')
|
|
739
|
+
.option('-p, --port <port>', 'the port to listen on', Number.parseInt, config.web.port)
|
|
743
740
|
.option('--ssl <prefix>', 'the prefix for the cert.pem and key.pem SSL files')
|
|
744
741
|
.option('-b, --build <path>', 'the path to the handler build')
|
|
745
742
|
.action(async (opt) => {
|
|
743
|
+
if (opt.port < 1 || opt.port > 65535)
|
|
744
|
+
io.exit('Invalid port');
|
|
746
745
|
const server = await serve({
|
|
747
746
|
secure: opt.ssl ? true : config.web.secure,
|
|
748
747
|
ssl_cert: opt.ssl ? join(opt.ssl, 'cert.pem') : config.web.ssl_cert,
|
|
749
748
|
ssl_key: opt.ssl ? join(opt.ssl, 'key.pem') : config.web.ssl_key,
|
|
750
749
|
build: opt.build ? resolve(opt.build) : config.web.build,
|
|
751
750
|
});
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
console.log('Server is listening on port ' + port);
|
|
751
|
+
server.listen(opt.port, () => {
|
|
752
|
+
console.log('Server is listening on port ' + opt.port);
|
|
755
753
|
});
|
|
756
754
|
});
|
|
757
755
|
program
|
|
@@ -761,12 +759,11 @@ try {
|
|
|
761
759
|
.addOption(new Option('-l, --list', 'list route links').conflicts('delete'))
|
|
762
760
|
.option('-d, --delete', 'delete route links')
|
|
763
761
|
.argument('[name...]', 'List of plugin names to operate on. If not specified, operates on all plugins and built-in routes.')
|
|
764
|
-
.action(async function (names) {
|
|
762
|
+
.action(async function axium_link(names) {
|
|
765
763
|
const opt = this.optsWithGlobals();
|
|
766
|
-
|
|
767
|
-
opt.only = names;
|
|
764
|
+
const linkOpts = { only: names };
|
|
768
765
|
if (opt.list) {
|
|
769
|
-
for (const link of listRouteLinks(
|
|
766
|
+
for (const link of listRouteLinks(linkOpts)) {
|
|
770
767
|
const idText = link.id.startsWith('#') ? `(${link.id.slice(1)})` : link.id;
|
|
771
768
|
const fromColor = await access(link.from)
|
|
772
769
|
.then(() => 'cyanBright')
|
|
@@ -776,10 +773,10 @@ try {
|
|
|
776
773
|
return;
|
|
777
774
|
}
|
|
778
775
|
if (opt.delete) {
|
|
779
|
-
unlinkRoutes(
|
|
776
|
+
unlinkRoutes(linkOpts);
|
|
780
777
|
return;
|
|
781
778
|
}
|
|
782
|
-
linkRoutes(
|
|
779
|
+
linkRoutes(linkOpts);
|
|
783
780
|
});
|
|
784
781
|
program
|
|
785
782
|
.command('audit')
|
package/dist/requests.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export declare function isRedirect(e: unknown): e is Redirect;
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function redirect(location: string, status?: number): never;
|
|
34
34
|
export declare function json(data: object, init?: ResponseInit): Response;
|
|
35
|
-
export declare function parseBody<const Schema extends z.ZodType
|
|
35
|
+
export declare function parseBody<const Schema extends z.ZodType>(request: Request, schema: Schema): Promise<z.infer<Schema>>;
|
|
36
|
+
export declare function parseSearch<const Schema extends z.ZodType>(request: Request, schema: Schema): z.infer<Schema>;
|
|
36
37
|
export declare function getToken(request: Request, sensitive?: boolean): string | undefined;
|
|
37
38
|
export interface CreateSessionOptions {
|
|
38
39
|
elevated?: boolean;
|
package/dist/requests.js
CHANGED
|
@@ -44,6 +44,16 @@ export async function parseBody(request, schema) {
|
|
|
44
44
|
error(400, e instanceof z.core.$ZodError ? z.prettifyError(e) : 'invalid body');
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
export function parseSearch(request, schema) {
|
|
48
|
+
const url = new URL(request.url);
|
|
49
|
+
const searchParams = Object.fromEntries(url.searchParams.entries());
|
|
50
|
+
try {
|
|
51
|
+
return schema.parse(searchParams);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
error(400, e instanceof z.core.$ZodError ? z.prettifyError(e) : 'invalid query parameters');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
47
57
|
export function getToken(request, sensitive = false) {
|
|
48
58
|
const header_token = request.headers.get('Authorization')?.replace('Bearer ', '');
|
|
49
59
|
if (header_token)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@axium/client": ">=0.9.5",
|
|
51
|
-
"@axium/core": ">=0.
|
|
51
|
+
"@axium/core": ">=0.15.0",
|
|
52
52
|
"kysely": "^0.28.0",
|
|
53
53
|
"utilium": "^2.6.0",
|
|
54
54
|
"zod": "^4.0.5"
|