@axium/server 0.10.0 → 0.11.1
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/dist/api/register.js +3 -3
- package/dist/api/users.js +2 -2
- package/dist/auth.d.ts +6 -10
- package/dist/cli.js +234 -16
- package/dist/config.d.ts +10 -4
- package/dist/config.js +12 -6
- package/dist/database.d.ts +12 -12
- package/dist/database.js +79 -84
- package/dist/io.d.ts +14 -17
- package/dist/io.js +58 -36
- package/dist/linking.d.ts +10 -0
- package/dist/linking.js +76 -0
- package/dist/plugins.d.ts +25 -11
- package/dist/plugins.js +17 -14
- package/dist/requests.d.ts +6 -3
- package/dist/requests.js +24 -15
- package/dist/sveltekit.js +18 -14
- package/package.json +10 -6
- package/routes/account/+page.svelte +6 -5
- package/svelte.config.js +2 -1
- package/web/lib/Upload.svelte +58 -0
- package/web/lib/icons/index.ts +6 -3
- package/web/lib/icons/mime.json +2 -1
- package/web/tsconfig.json +0 -1
package/dist/database.js
CHANGED
|
@@ -55,7 +55,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
55
55
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
56
56
|
import pg from 'pg';
|
|
57
57
|
import config from './config.js';
|
|
58
|
-
import
|
|
58
|
+
import * as io from './io.js';
|
|
59
59
|
import { plugins } from './plugins.js';
|
|
60
60
|
const sym = Symbol.for('Axium:database');
|
|
61
61
|
export let database;
|
|
@@ -99,32 +99,32 @@ export async function statusText() {
|
|
|
99
99
|
}
|
|
100
100
|
export function shouldRecreate(opt) {
|
|
101
101
|
if (opt.skip) {
|
|
102
|
-
|
|
102
|
+
io.warn('already exists. (skipped)');
|
|
103
103
|
return true;
|
|
104
104
|
}
|
|
105
105
|
if (opt.force) {
|
|
106
|
-
|
|
106
|
+
io.warn('already exists. (re-creating)');
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
io.warn('already exists. Use --skip to skip or --force to re-create.');
|
|
110
110
|
throw 2;
|
|
111
111
|
}
|
|
112
112
|
export async function getHBA(opt) {
|
|
113
|
-
const hbaShowResult = await run(
|
|
114
|
-
|
|
113
|
+
const hbaShowResult = await io.run('Finding pg_hba.conf', `sudo -u postgres psql -c "SHOW hba_file"`);
|
|
114
|
+
io.start('Resolving pg_hba.conf path');
|
|
115
115
|
const hbaPath = hbaShowResult.match(/^\s*(.+\.conf)\s*$/m)?.[1]?.trim();
|
|
116
116
|
if (!hbaPath) {
|
|
117
117
|
throw 'failed. You will need to add password-based auth for the axium user manually.';
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
io.done();
|
|
120
|
+
io.debug(`Found pg_hba.conf at ${hbaPath}`);
|
|
121
|
+
io.start('Reading HBA configuration');
|
|
122
122
|
const content = readFileSync(hbaPath, 'utf-8');
|
|
123
|
-
|
|
123
|
+
io.done();
|
|
124
124
|
const writeBack = (newContent) => {
|
|
125
|
-
|
|
125
|
+
io.start('Writing HBA configuration');
|
|
126
126
|
writeFileSync(hbaPath, newContent);
|
|
127
|
-
|
|
127
|
+
io.done();
|
|
128
128
|
};
|
|
129
129
|
return [content, writeBack];
|
|
130
130
|
}
|
|
@@ -133,19 +133,23 @@ local axium axium md5
|
|
|
133
133
|
host axium axium 127.0.0.1/32 md5
|
|
134
134
|
host axium axium ::1/128 md5
|
|
135
135
|
`;
|
|
136
|
+
const _sql = (command, message) => io.run(message, `sudo -u postgres psql -c "${command}"`);
|
|
137
|
+
/** Shortcut to output a warning if an error is thrown because relation already exists */
|
|
138
|
+
export const warnExists = io.someWarnings([/\w+ "[\w.]+" already exists/, 'already exists.']);
|
|
139
|
+
const throwUnlessRows = (text) => {
|
|
140
|
+
if (text.includes('(0 rows)'))
|
|
141
|
+
throw 'missing.';
|
|
142
|
+
return text;
|
|
143
|
+
};
|
|
136
144
|
export async function init(opt) {
|
|
137
145
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
138
146
|
try {
|
|
139
|
-
_fixOutput(opt);
|
|
140
147
|
if (!config.db.password) {
|
|
141
148
|
config.save({ db: { password: randomBytes(32).toString('base64') } }, true);
|
|
142
|
-
|
|
149
|
+
io.debug('Generated password and wrote to global config');
|
|
143
150
|
}
|
|
144
|
-
await run(
|
|
145
|
-
await run(
|
|
146
|
-
const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
|
|
147
|
-
const warnExists = someWarnings(opt.output, [/(schema|relation) "[\w.]+" already exists/, 'already exists.']);
|
|
148
|
-
const done = () => opt.output('done');
|
|
151
|
+
await io.run('Checking for sudo', 'which sudo');
|
|
152
|
+
await io.run('Checking for psql', 'which psql');
|
|
149
153
|
await _sql('CREATE DATABASE axium', 'Creating database').catch(async (error) => {
|
|
150
154
|
if (error != 'database "axium" already exists')
|
|
151
155
|
throw error;
|
|
@@ -169,19 +173,19 @@ export async function init(opt) {
|
|
|
169
173
|
await _sql('ALTER DATABASE axium OWNER TO axium', 'Setting database owner');
|
|
170
174
|
await getHBA(opt)
|
|
171
175
|
.then(([content, writeBack]) => {
|
|
172
|
-
|
|
176
|
+
io.start('Checking for Axium HBA configuration');
|
|
173
177
|
if (content.includes(pgHba))
|
|
174
178
|
throw 'already exists.';
|
|
175
|
-
done();
|
|
176
|
-
|
|
179
|
+
io.done();
|
|
180
|
+
io.start('Adding Axium HBA configuration');
|
|
177
181
|
const newContent = content.replace(/^local\s+all\s+all.*$/m, `$&\n${pgHba}`);
|
|
178
|
-
done();
|
|
182
|
+
io.done();
|
|
179
183
|
writeBack(newContent);
|
|
180
184
|
})
|
|
181
|
-
.catch(
|
|
185
|
+
.catch(io.warn);
|
|
182
186
|
await _sql('SELECT pg_reload_conf()', 'Reloading configuration');
|
|
183
187
|
const db = __addDisposableResource(env_1, connect(), true);
|
|
184
|
-
|
|
188
|
+
io.start('Creating table users');
|
|
185
189
|
await db.schema
|
|
186
190
|
.createTable('users')
|
|
187
191
|
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
@@ -189,11 +193,15 @@ export async function init(opt) {
|
|
|
189
193
|
.addColumn('email', 'text', col => col.unique().notNull())
|
|
190
194
|
.addColumn('emailVerified', 'timestamptz')
|
|
191
195
|
.addColumn('image', 'text')
|
|
196
|
+
.addColumn('isAdmin', 'boolean', col => col.notNull().defaultTo(false))
|
|
197
|
+
.addColumn('roles', sql `text[]`, col => col.notNull().defaultTo(sql `'{}'::text[]`))
|
|
198
|
+
.addColumn('tags', sql `text[]`, col => col.notNull().defaultTo(sql `'{}'::text[]`))
|
|
192
199
|
.addColumn('preferences', 'jsonb', col => col.notNull().defaultTo(sql `'{}'::jsonb`))
|
|
200
|
+
.addColumn('registeredAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
|
|
193
201
|
.execute()
|
|
194
|
-
.then(done)
|
|
202
|
+
.then(io.done)
|
|
195
203
|
.catch(warnExists);
|
|
196
|
-
|
|
204
|
+
io.start('Creating table sessions');
|
|
197
205
|
await db.schema
|
|
198
206
|
.createTable('sessions')
|
|
199
207
|
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
@@ -203,11 +211,11 @@ export async function init(opt) {
|
|
|
203
211
|
.addColumn('expires', 'timestamptz', col => col.notNull())
|
|
204
212
|
.addColumn('elevated', 'boolean', col => col.notNull())
|
|
205
213
|
.execute()
|
|
206
|
-
.then(done)
|
|
214
|
+
.then(io.done)
|
|
207
215
|
.catch(warnExists);
|
|
208
|
-
|
|
209
|
-
await db.schema.createIndex('sessions_userId_index').on('sessions').column('userId').execute().then(done).catch(warnExists);
|
|
210
|
-
|
|
216
|
+
io.start('Creating index for sessions.userId');
|
|
217
|
+
await db.schema.createIndex('sessions_userId_index').on('sessions').column('userId').execute().then(io.done).catch(warnExists);
|
|
218
|
+
io.start('Creating table verifications');
|
|
211
219
|
await db.schema
|
|
212
220
|
.createTable('verifications')
|
|
213
221
|
.addColumn('userId', 'uuid', col => col.references('users.id').onDelete('cascade').notNull())
|
|
@@ -215,9 +223,9 @@ export async function init(opt) {
|
|
|
215
223
|
.addColumn('expires', 'timestamptz', col => col.notNull())
|
|
216
224
|
.addColumn('role', 'text', col => col.notNull())
|
|
217
225
|
.execute()
|
|
218
|
-
.then(done)
|
|
226
|
+
.then(io.done)
|
|
219
227
|
.catch(warnExists);
|
|
220
|
-
|
|
228
|
+
io.start('Creating table passkeys');
|
|
221
229
|
await db.schema
|
|
222
230
|
.createTable('passkeys')
|
|
223
231
|
.addColumn('id', 'text', col => col.primaryKey().notNull())
|
|
@@ -230,15 +238,15 @@ export async function init(opt) {
|
|
|
230
238
|
.addColumn('backedUp', 'boolean', col => col.notNull())
|
|
231
239
|
.addColumn('transports', sql `text[]`)
|
|
232
240
|
.execute()
|
|
233
|
-
.then(done)
|
|
241
|
+
.then(io.done)
|
|
234
242
|
.catch(warnExists);
|
|
235
|
-
|
|
236
|
-
await db.schema.createIndex('passkeys_id_key').on('passkeys').column('id').execute().then(done).catch(warnExists);
|
|
243
|
+
io.start('Creating index for passkeys.id');
|
|
244
|
+
await db.schema.createIndex('passkeys_id_key').on('passkeys').column('id').execute().then(io.done).catch(warnExists);
|
|
237
245
|
for (const plugin of plugins) {
|
|
238
|
-
if (!plugin.db_init)
|
|
246
|
+
if (!plugin.hooks.db_init)
|
|
239
247
|
continue;
|
|
240
|
-
|
|
241
|
-
await plugin.db_init(opt, db
|
|
248
|
+
io.plugin(plugin.name);
|
|
249
|
+
await plugin.hooks.db_init(opt, db);
|
|
242
250
|
}
|
|
243
251
|
}
|
|
244
252
|
catch (e_1) {
|
|
@@ -254,33 +262,25 @@ export async function init(opt) {
|
|
|
254
262
|
export async function check(opt) {
|
|
255
263
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
256
264
|
try {
|
|
257
|
-
|
|
258
|
-
await run(
|
|
259
|
-
await run(opt, 'Checking for psql', 'which psql');
|
|
260
|
-
const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
|
|
261
|
-
const done = () => opt.output('done');
|
|
262
|
-
const throwUnlessRows = (text) => {
|
|
263
|
-
if (text.includes('(0 rows)'))
|
|
264
|
-
throw 'missing.';
|
|
265
|
-
return text;
|
|
266
|
-
};
|
|
265
|
+
await io.run('Checking for sudo', 'which sudo');
|
|
266
|
+
await io.run('Checking for psql', 'which psql');
|
|
267
267
|
await _sql(`SELECT 1 FROM pg_database WHERE datname = 'axium'`, 'Checking for database').then(throwUnlessRows);
|
|
268
268
|
await _sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows);
|
|
269
|
-
|
|
269
|
+
io.start('Connecting to database');
|
|
270
270
|
const db = __addDisposableResource(env_2, connect(), true);
|
|
271
|
-
done();
|
|
272
|
-
|
|
273
|
-
await db.selectFrom('users').select(['id', 'email', 'emailVerified', 'image', 'name', 'preferences']).execute().then(done);
|
|
274
|
-
|
|
275
|
-
await db.selectFrom('sessions').select(['id', 'userId', 'token', 'created', 'expires', 'elevated']).execute().then(done);
|
|
276
|
-
|
|
277
|
-
await db.selectFrom('verifications').select(['userId', 'token', 'expires', 'role']).execute().then(done);
|
|
278
|
-
|
|
271
|
+
io.done();
|
|
272
|
+
io.start('Checking users table');
|
|
273
|
+
await db.selectFrom('users').select(['id', 'email', 'emailVerified', 'image', 'name', 'preferences']).execute().then(io.done);
|
|
274
|
+
io.start('Checking sessions table');
|
|
275
|
+
await db.selectFrom('sessions').select(['id', 'userId', 'token', 'created', 'expires', 'elevated']).execute().then(io.done);
|
|
276
|
+
io.start('Checking verifications table');
|
|
277
|
+
await db.selectFrom('verifications').select(['userId', 'token', 'expires', 'role']).execute().then(io.done);
|
|
278
|
+
io.start('Checking passkeys table');
|
|
279
279
|
await db
|
|
280
280
|
.selectFrom('passkeys')
|
|
281
281
|
.select(['id', 'name', 'createdAt', 'userId', 'publicKey', 'counter', 'deviceType', 'backedUp', 'transports'])
|
|
282
282
|
.execute()
|
|
283
|
-
.then(done);
|
|
283
|
+
.then(io.done);
|
|
284
284
|
}
|
|
285
285
|
catch (e_2) {
|
|
286
286
|
env_2.error = e_2;
|
|
@@ -293,19 +293,17 @@ export async function check(opt) {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
export async function clean(opt) {
|
|
296
|
-
_fixOutput(opt);
|
|
297
|
-
const done = () => opt.output('done');
|
|
298
296
|
const now = new Date();
|
|
299
297
|
const db = connect();
|
|
300
|
-
|
|
301
|
-
await db.deleteFrom('sessions').where('sessions.expires', '<', now).execute().then(done);
|
|
302
|
-
|
|
303
|
-
await db.deleteFrom('verifications').where('verifications.expires', '<', now).execute().then(done);
|
|
298
|
+
io.start('Removing expired sessions');
|
|
299
|
+
await db.deleteFrom('sessions').where('sessions.expires', '<', now).execute().then(io.done);
|
|
300
|
+
io.start('Removing expired verifications');
|
|
301
|
+
await db.deleteFrom('verifications').where('verifications.expires', '<', now).execute().then(io.done);
|
|
304
302
|
for (const plugin of plugins) {
|
|
305
|
-
if (!plugin.
|
|
303
|
+
if (!plugin.hooks.clean)
|
|
306
304
|
continue;
|
|
307
|
-
|
|
308
|
-
await plugin.
|
|
305
|
+
io.plugin(plugin.name);
|
|
306
|
+
await plugin.hooks.clean(opt, db);
|
|
309
307
|
}
|
|
310
308
|
}
|
|
311
309
|
/**
|
|
@@ -314,30 +312,28 @@ export async function clean(opt) {
|
|
|
314
312
|
export async function uninstall(opt) {
|
|
315
313
|
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
316
314
|
try {
|
|
317
|
-
_fixOutput(opt);
|
|
318
315
|
const db = __addDisposableResource(env_3, connect(), true);
|
|
319
316
|
for (const plugin of plugins) {
|
|
320
|
-
if (!plugin.
|
|
317
|
+
if (!plugin.hooks.remove)
|
|
321
318
|
continue;
|
|
322
|
-
|
|
323
|
-
await plugin.
|
|
319
|
+
io.plugin(plugin.name);
|
|
320
|
+
await plugin.hooks.remove(opt, db);
|
|
324
321
|
}
|
|
325
|
-
const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
|
|
326
322
|
await _sql('DROP DATABASE axium', 'Dropping database');
|
|
327
323
|
await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
|
|
328
324
|
await _sql('DROP USER axium', 'Dropping user');
|
|
329
325
|
await getHBA(opt)
|
|
330
326
|
.then(([content, writeBack]) => {
|
|
331
|
-
|
|
327
|
+
io.start('Checking for Axium HBA configuration');
|
|
332
328
|
if (!content.includes(pgHba))
|
|
333
329
|
throw 'missing.';
|
|
334
|
-
|
|
335
|
-
|
|
330
|
+
io.done();
|
|
331
|
+
io.start('Removing Axium HBA configuration');
|
|
336
332
|
const newContent = content.replace(pgHba, '');
|
|
337
|
-
|
|
333
|
+
io.done();
|
|
338
334
|
writeBack(newContent);
|
|
339
335
|
})
|
|
340
|
-
.catch(
|
|
336
|
+
.catch(io.warn);
|
|
341
337
|
}
|
|
342
338
|
catch (e_3) {
|
|
343
339
|
env_3.error = e_3;
|
|
@@ -353,17 +349,16 @@ export async function uninstall(opt) {
|
|
|
353
349
|
* Removes all data from tables.
|
|
354
350
|
*/
|
|
355
351
|
export async function wipe(opt) {
|
|
356
|
-
_fixOutput(opt);
|
|
357
352
|
const db = connect();
|
|
358
353
|
for (const plugin of plugins) {
|
|
359
|
-
if (!plugin.db_wipe)
|
|
354
|
+
if (!plugin.hooks.db_wipe)
|
|
360
355
|
continue;
|
|
361
|
-
|
|
362
|
-
await plugin.db_wipe(opt, db);
|
|
356
|
+
io.plugin(plugin.name);
|
|
357
|
+
await plugin.hooks.db_wipe(opt, db);
|
|
363
358
|
}
|
|
364
359
|
for (const table of ['users', 'passkeys', 'sessions', 'verifications']) {
|
|
365
|
-
|
|
360
|
+
io.start(`Removing data from ${table}`);
|
|
366
361
|
await db.deleteFrom(table).execute();
|
|
367
|
-
|
|
362
|
+
io.done();
|
|
368
363
|
}
|
|
369
364
|
}
|
package/dist/io.d.ts
CHANGED
|
@@ -16,13 +16,12 @@ export declare const output: {
|
|
|
16
16
|
log(message: string): void;
|
|
17
17
|
debug(message: string): void;
|
|
18
18
|
};
|
|
19
|
+
export declare function setCommandTimeout(value: number): void;
|
|
19
20
|
/**
|
|
20
21
|
* Run a system command with the fancy "Example... done."
|
|
21
22
|
* @internal
|
|
22
23
|
*/
|
|
23
|
-
export declare function run(
|
|
24
|
-
timeout?: number;
|
|
25
|
-
}, message: string, command: string): Promise<string>;
|
|
24
|
+
export declare function run(message: string, command: string): Promise<string>;
|
|
26
25
|
/** Yet another convenience function */
|
|
27
26
|
export declare function exit(message: string | Error, code?: number): never;
|
|
28
27
|
export declare function handleError(e: number | string | Error): void;
|
|
@@ -31,20 +30,18 @@ export interface Output {
|
|
|
31
30
|
(tag: 'done'): void;
|
|
32
31
|
(tag: Exclude<OutputTag, 'done'>, message: string): void;
|
|
33
32
|
}
|
|
34
|
-
export interface MaybeOutput {
|
|
35
|
-
output?: Output | null | false;
|
|
36
|
-
}
|
|
37
|
-
export interface WithOutput {
|
|
38
|
-
output: Output;
|
|
39
|
-
}
|
|
40
|
-
export declare function _setDebugOutput(enabled: boolean): void;
|
|
41
|
-
export declare function defaultOutput(tag: 'done'): void;
|
|
42
|
-
export declare function defaultOutput(tag: Exclude<OutputTag, 'done'>, message: string): void;
|
|
43
33
|
/**
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
34
|
+
* Enable or disable debug output.
|
|
46
35
|
*/
|
|
47
|
-
export declare function
|
|
36
|
+
export declare function _setDebugOutput(enabled: boolean): void;
|
|
37
|
+
export declare function useTaggedOutput(output: Output | null): void;
|
|
38
|
+
export declare function done(): void;
|
|
39
|
+
export declare function start(message: string): void;
|
|
40
|
+
export declare function plugin(name: string): void;
|
|
41
|
+
export declare function debug(message: string): void;
|
|
42
|
+
export declare function info(message: string): void;
|
|
43
|
+
export declare function warn(message: string): void;
|
|
44
|
+
export declare function error(message: string): void;
|
|
48
45
|
/** @internal */
|
|
49
46
|
export declare const _portMethods: readonly ["node-cap"];
|
|
50
47
|
/** @internal */
|
|
@@ -55,7 +52,7 @@ export declare const _portActions: readonly ["enable", "disable"];
|
|
|
55
52
|
* Method:
|
|
56
53
|
* - `node-cap`: Use the `cap_net_bind_service` capability on the node binary.
|
|
57
54
|
*/
|
|
58
|
-
export interface PortOptions
|
|
55
|
+
export interface PortOptions {
|
|
59
56
|
method: (typeof _portMethods)[number];
|
|
60
57
|
action: (typeof _portActions)[number];
|
|
61
58
|
node?: string;
|
|
@@ -71,4 +68,4 @@ export declare function restrictedPorts(opt: PortOptions): Promise<void>;
|
|
|
71
68
|
* The handler will allow the parent scope to continue if a relation already exists,
|
|
72
69
|
* rather than fatally exiting.
|
|
73
70
|
*/
|
|
74
|
-
export declare function someWarnings(
|
|
71
|
+
export declare function someWarnings(...allowList: [RegExp, string?][]): (error: string | Error) => void;
|
package/dist/io.js
CHANGED
|
@@ -47,16 +47,20 @@ export const output = {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
logger.attach(output);
|
|
50
|
+
let timeout = 1000;
|
|
51
|
+
export function setCommandTimeout(value) {
|
|
52
|
+
timeout = value;
|
|
53
|
+
}
|
|
50
54
|
/**
|
|
51
55
|
* Run a system command with the fancy "Example... done."
|
|
52
56
|
* @internal
|
|
53
57
|
*/
|
|
54
|
-
export async function run(
|
|
58
|
+
export async function run(message, command) {
|
|
55
59
|
let stderr;
|
|
56
60
|
try {
|
|
57
|
-
|
|
61
|
+
start(message);
|
|
58
62
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
59
|
-
exec(command,
|
|
63
|
+
exec(command, { timeout }, (err, stdout, _stderr) => {
|
|
60
64
|
stderr = _stderr.startsWith('ERROR:') ? _stderr.slice(6).trim() : _stderr;
|
|
61
65
|
if (err)
|
|
62
66
|
reject('[command]');
|
|
@@ -64,7 +68,7 @@ export async function run(opts, message, command) {
|
|
|
64
68
|
resolve(stdout);
|
|
65
69
|
});
|
|
66
70
|
const value = await promise;
|
|
67
|
-
|
|
71
|
+
done();
|
|
68
72
|
return value;
|
|
69
73
|
}
|
|
70
74
|
catch (error) {
|
|
@@ -89,10 +93,13 @@ export function handleError(e) {
|
|
|
89
93
|
exit(e);
|
|
90
94
|
}
|
|
91
95
|
let _debugOutput = false;
|
|
96
|
+
/**
|
|
97
|
+
* Enable or disable debug output.
|
|
98
|
+
*/
|
|
92
99
|
export function _setDebugOutput(enabled) {
|
|
93
100
|
_debugOutput = enabled;
|
|
94
101
|
}
|
|
95
|
-
|
|
102
|
+
function defaultOutput(tag, message = '') {
|
|
96
103
|
switch (tag) {
|
|
97
104
|
case 'debug':
|
|
98
105
|
_debugOutput && output.debug(message);
|
|
@@ -116,15 +123,31 @@ export function defaultOutput(tag, message = '') {
|
|
|
116
123
|
console.log(styleText('whiteBright', 'Running plugin: ' + message));
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
let _taggedOutput = defaultOutput;
|
|
127
|
+
export function useTaggedOutput(output) {
|
|
128
|
+
_taggedOutput = output;
|
|
129
|
+
}
|
|
130
|
+
// Shortcuts for tagged output
|
|
131
|
+
export function done() {
|
|
132
|
+
_taggedOutput?.('done');
|
|
133
|
+
}
|
|
134
|
+
export function start(message) {
|
|
135
|
+
_taggedOutput?.('start', message);
|
|
136
|
+
}
|
|
137
|
+
export function plugin(name) {
|
|
138
|
+
_taggedOutput?.('plugin', name);
|
|
139
|
+
}
|
|
140
|
+
export function debug(message) {
|
|
141
|
+
_taggedOutput?.('debug', message);
|
|
142
|
+
}
|
|
143
|
+
export function info(message) {
|
|
144
|
+
_taggedOutput?.('info', message);
|
|
145
|
+
}
|
|
146
|
+
export function warn(message) {
|
|
147
|
+
_taggedOutput?.('warn', message);
|
|
148
|
+
}
|
|
149
|
+
export function error(message) {
|
|
150
|
+
_taggedOutput?.('error', message);
|
|
128
151
|
}
|
|
129
152
|
/** @internal */
|
|
130
153
|
export const _portMethods = ['node-cap'];
|
|
@@ -136,46 +159,45 @@ export const _portActions = ['enable', 'disable'];
|
|
|
136
159
|
* If the origin has a port, passkeys do not work correctly with some password managers.
|
|
137
160
|
*/
|
|
138
161
|
export async function restrictedPorts(opt) {
|
|
139
|
-
|
|
140
|
-
opt.output('start', 'Checking for root privileges');
|
|
162
|
+
start('Checking for root privileges');
|
|
141
163
|
if (process.getuid?.() != 0)
|
|
142
164
|
throw 'root privileges are needed to change restricted ports.';
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
done();
|
|
166
|
+
start('Checking ports method');
|
|
145
167
|
if (!_portMethods.includes(opt.method))
|
|
146
168
|
throw 'invalid';
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
done();
|
|
170
|
+
start('Checking ports action');
|
|
149
171
|
if (!_portActions.includes(opt.action))
|
|
150
172
|
throw 'invalid';
|
|
151
|
-
|
|
173
|
+
done();
|
|
152
174
|
switch (opt.method) {
|
|
153
175
|
case 'node-cap': {
|
|
154
|
-
const setcap = await run(
|
|
176
|
+
const setcap = await run('Finding setcap', 'command -v setcap')
|
|
155
177
|
.then(e => e.trim())
|
|
156
178
|
.catch(() => {
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
warn('not in path.');
|
|
180
|
+
start('Checking for /usr/sbin/setcap');
|
|
159
181
|
fs.accessSync('/usr/sbin/setcap', fs.constants.X_OK);
|
|
160
|
-
|
|
182
|
+
done();
|
|
161
183
|
return '/usr/sbin/setcap';
|
|
162
184
|
});
|
|
163
|
-
|
|
185
|
+
debug('Using setcap at ' + setcap);
|
|
164
186
|
let { node } = opt;
|
|
165
|
-
node ||= await run(
|
|
187
|
+
node ||= await run('Finding node', 'command -v node')
|
|
166
188
|
.then(e => e.trim())
|
|
167
189
|
.catch(() => {
|
|
168
|
-
|
|
169
|
-
|
|
190
|
+
warn('not in path.');
|
|
191
|
+
start('Checking for /usr/bin/node');
|
|
170
192
|
fs.accessSync('/usr/bin/node', fs.constants.X_OK);
|
|
171
|
-
|
|
193
|
+
done();
|
|
172
194
|
return '/usr/bin/node';
|
|
173
195
|
});
|
|
174
|
-
|
|
196
|
+
start('Resolving real path for node');
|
|
175
197
|
node = fs.realpathSync(node);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await run(
|
|
198
|
+
done();
|
|
199
|
+
debug('Using node at ' + node);
|
|
200
|
+
await run('Setting ports capability', `${setcap} cap_net_bind_service=${opt.action == 'enable' ? '+' : '-'}ep ${node}`);
|
|
179
201
|
break;
|
|
180
202
|
}
|
|
181
203
|
}
|
|
@@ -185,13 +207,13 @@ export async function restrictedPorts(opt) {
|
|
|
185
207
|
* The handler will allow the parent scope to continue if a relation already exists,
|
|
186
208
|
* rather than fatally exiting.
|
|
187
209
|
*/
|
|
188
|
-
export function someWarnings(
|
|
210
|
+
export function someWarnings(...allowList) {
|
|
189
211
|
return (error) => {
|
|
190
212
|
error = typeof error == 'object' && 'message' in error ? error.message : error;
|
|
191
213
|
for (const [pattern, message = error] of allowList) {
|
|
192
214
|
if (!pattern.test(error))
|
|
193
215
|
continue;
|
|
194
|
-
|
|
216
|
+
warn(message);
|
|
195
217
|
return;
|
|
196
218
|
}
|
|
197
219
|
throw error;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function listRouteLinks(): Generator<{
|
|
2
|
+
id: string;
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Symlinks .svelte page routes for plugins and the server into a project's routes directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function linkRoutes(): void;
|
|
10
|
+
export declare function unlinkRoutes(): void;
|
package/dist/linking.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, symlinkSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path/posix';
|
|
3
|
+
import config from './config.js';
|
|
4
|
+
import * as io from './io.js';
|
|
5
|
+
import { getSpecifier, plugins } from './plugins.js';
|
|
6
|
+
const textFor = {
|
|
7
|
+
builtin: 'built-in routes',
|
|
8
|
+
};
|
|
9
|
+
function info(id) {
|
|
10
|
+
const text = id.startsWith('#') ? textFor[id.slice(1)] : `routes for plugin: ${id}`;
|
|
11
|
+
const link = join(config.web.routes, `(${id.startsWith('#') ? id.slice(1) : id.replaceAll('/', '__')})`);
|
|
12
|
+
return [text, link];
|
|
13
|
+
}
|
|
14
|
+
export function* listRouteLinks() {
|
|
15
|
+
const [, link] = info('#builtin');
|
|
16
|
+
yield { id: '#builtin', from: resolve(import.meta.dirname, '../routes'), to: link };
|
|
17
|
+
for (const plugin of plugins) {
|
|
18
|
+
if (!plugin.routes)
|
|
19
|
+
continue;
|
|
20
|
+
const [, link] = info(plugin.name);
|
|
21
|
+
yield { id: plugin.name, from: join(import.meta.resolve(getSpecifier(plugin)), plugin.routes), to: link };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function createLink(id, routes) {
|
|
25
|
+
const [text, link] = info(id);
|
|
26
|
+
if (existsSync(link)) {
|
|
27
|
+
io.warn('already exists.');
|
|
28
|
+
io.start('Unlinking ' + text);
|
|
29
|
+
try {
|
|
30
|
+
unlinkSync(link);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
io.exit(e && e instanceof Error ? e.message : e.toString());
|
|
34
|
+
}
|
|
35
|
+
io.done();
|
|
36
|
+
io.start('Re-linking ' + text);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
symlinkSync(link, routes, 'dir');
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
io.exit(e && e instanceof Error ? e.message : e.toString());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Symlinks .svelte page routes for plugins and the server into a project's routes directory.
|
|
47
|
+
*/
|
|
48
|
+
export function linkRoutes() {
|
|
49
|
+
io.start('Linking built-in routes');
|
|
50
|
+
createLink('#builtin', resolve(import.meta.dirname, '../routes'));
|
|
51
|
+
for (const plugin of plugins) {
|
|
52
|
+
if (!plugin.routes)
|
|
53
|
+
continue;
|
|
54
|
+
io.start(`Linking routes for plugin: ${plugin.name}`);
|
|
55
|
+
createLink(plugin.name, join(import.meta.resolve(getSpecifier(plugin)), plugin.routes));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function removeLink(id) {
|
|
59
|
+
const [text, link] = info(id);
|
|
60
|
+
if (!existsSync(link))
|
|
61
|
+
return;
|
|
62
|
+
io.start('Unlinking ' + text);
|
|
63
|
+
try {
|
|
64
|
+
unlinkSync(link);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
io.exit(e && e instanceof Error ? e.message : e.toString());
|
|
68
|
+
}
|
|
69
|
+
io.done();
|
|
70
|
+
}
|
|
71
|
+
export function unlinkRoutes() {
|
|
72
|
+
removeLink('#builtin');
|
|
73
|
+
for (const plugin of plugins) {
|
|
74
|
+
removeLink(plugin.name);
|
|
75
|
+
}
|
|
76
|
+
}
|