@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/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 { _fixOutput, run, someWarnings } from './io.js';
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
- opt.output('warn', 'already exists. (skipped)');
102
+ io.warn('already exists. (skipped)');
103
103
  return true;
104
104
  }
105
105
  if (opt.force) {
106
- opt.output('warn', 'already exists. (re-creating)');
106
+ io.warn('already exists. (re-creating)');
107
107
  return false;
108
108
  }
109
- opt.output('warn', 'already exists. Use --skip to skip or --force to re-create.');
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(opt, 'Finding pg_hba.conf', `sudo -u postgres psql -c "SHOW hba_file"`);
114
- opt.output('start', 'Resolving pg_hba.conf path');
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
- opt.output('done');
120
- opt.output('debug', `Found pg_hba.conf at ${hbaPath}`);
121
- opt.output('start', 'Reading HBA configuration');
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
- opt.output('done');
123
+ io.done();
124
124
  const writeBack = (newContent) => {
125
- opt.output('start', 'Writing HBA configuration');
125
+ io.start('Writing HBA configuration');
126
126
  writeFileSync(hbaPath, newContent);
127
- opt.output('done');
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
- opt.output('debug', 'Generated password and wrote to global config');
149
+ io.debug('Generated password and wrote to global config');
143
150
  }
144
- await run(opt, 'Checking for sudo', 'which sudo');
145
- await run(opt, 'Checking for psql', 'which psql');
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
- opt.output('start', 'Checking for Axium HBA configuration');
176
+ io.start('Checking for Axium HBA configuration');
173
177
  if (content.includes(pgHba))
174
178
  throw 'already exists.';
175
- done();
176
- opt.output('start', 'Adding Axium HBA configuration');
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(e => opt.output('warn', e));
185
+ .catch(io.warn);
182
186
  await _sql('SELECT pg_reload_conf()', 'Reloading configuration');
183
187
  const db = __addDisposableResource(env_1, connect(), true);
184
- opt.output('start', 'Creating table users');
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
- opt.output('start', 'Creating table sessions');
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
- opt.output('start', 'Creating index for sessions.userId');
209
- await db.schema.createIndex('sessions_userId_index').on('sessions').column('userId').execute().then(done).catch(warnExists);
210
- opt.output('start', 'Creating table verifications');
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
- opt.output('start', 'Creating table passkeys');
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
- opt.output('start', 'Creating index for passkeys.id');
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
- opt.output('plugin', plugin.name);
241
- await plugin.db_init(opt, db, { warnExists, done });
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
- _fixOutput(opt);
258
- await run(opt, 'Checking for sudo', 'which sudo');
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
- opt.output('start', 'Connecting to database');
269
+ io.start('Connecting to database');
270
270
  const db = __addDisposableResource(env_2, connect(), true);
271
- done();
272
- opt.output('start', `Checking users table`);
273
- await db.selectFrom('users').select(['id', 'email', 'emailVerified', 'image', 'name', 'preferences']).execute().then(done);
274
- opt.output('start', `Checking sessions table`);
275
- await db.selectFrom('sessions').select(['id', 'userId', 'token', 'created', 'expires', 'elevated']).execute().then(done);
276
- opt.output('start', `Checking verifications table`);
277
- await db.selectFrom('verifications').select(['userId', 'token', 'expires', 'role']).execute().then(done);
278
- opt.output('start', `Checking passkeys table`);
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
- opt.output('start', 'Removing expired sessions');
301
- await db.deleteFrom('sessions').where('sessions.expires', '<', now).execute().then(done);
302
- opt.output('start', 'Removing expired verifications');
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.db_clean)
303
+ if (!plugin.hooks.clean)
306
304
  continue;
307
- opt.output('plugin', plugin.name);
308
- await plugin.db_clean(opt, db);
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.db_remove)
317
+ if (!plugin.hooks.remove)
321
318
  continue;
322
- opt.output('plugin', plugin.name);
323
- await plugin.db_remove(opt, db);
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
- opt.output('start', 'Checking for Axium HBA configuration');
327
+ io.start('Checking for Axium HBA configuration');
332
328
  if (!content.includes(pgHba))
333
329
  throw 'missing.';
334
- opt.output('done');
335
- opt.output('start', 'Removing Axium HBA configuration');
330
+ io.done();
331
+ io.start('Removing Axium HBA configuration');
336
332
  const newContent = content.replace(pgHba, '');
337
- opt.output('done');
333
+ io.done();
338
334
  writeBack(newContent);
339
335
  })
340
- .catch(e => opt.output('warn', e));
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
- opt.output('plugin', plugin.name);
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
- opt.output('start', `Removing data from ${table}`);
360
+ io.start(`Removing data from ${table}`);
366
361
  await db.deleteFrom(table).execute();
367
- opt.output('done');
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(opts: WithOutput & {
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
- * TS can't tell when we do this inline
45
- * @internal
34
+ * Enable or disable debug output.
46
35
  */
47
- export declare function _fixOutput<T extends MaybeOutput>(opt: T): asserts opt is T & WithOutput;
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 extends MaybeOutput {
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(output: Output, ...allowList: [RegExp, string?][]): (error: string | Error) => void;
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(opts, message, command) {
58
+ export async function run(message, command) {
55
59
  let stderr;
56
60
  try {
57
- opts.output('start', message);
61
+ start(message);
58
62
  const { promise, resolve, reject } = Promise.withResolvers();
59
- exec(command, opts, (err, stdout, _stderr) => {
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
- opts.output('done');
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
- export function defaultOutput(tag, message = '') {
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
- * TS can't tell when we do this inline
121
- * @internal
122
- */
123
- export function _fixOutput(opt) {
124
- if (opt.output === false)
125
- opt.output = () => { };
126
- else
127
- opt.output ??= defaultOutput;
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
- _fixOutput(opt);
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
- opt.output('done');
144
- opt.output('start', 'Checking ports method');
165
+ done();
166
+ start('Checking ports method');
145
167
  if (!_portMethods.includes(opt.method))
146
168
  throw 'invalid';
147
- opt.output('done');
148
- opt.output('start', 'Checking ports action');
169
+ done();
170
+ start('Checking ports action');
149
171
  if (!_portActions.includes(opt.action))
150
172
  throw 'invalid';
151
- opt.output('done');
173
+ done();
152
174
  switch (opt.method) {
153
175
  case 'node-cap': {
154
- const setcap = await run(opt, 'Finding setcap', 'command -v setcap')
176
+ const setcap = await run('Finding setcap', 'command -v setcap')
155
177
  .then(e => e.trim())
156
178
  .catch(() => {
157
- opt.output('warn', 'not in path.');
158
- opt.output('start', 'Checking for /usr/sbin/setcap');
179
+ warn('not in path.');
180
+ start('Checking for /usr/sbin/setcap');
159
181
  fs.accessSync('/usr/sbin/setcap', fs.constants.X_OK);
160
- opt.output('done');
182
+ done();
161
183
  return '/usr/sbin/setcap';
162
184
  });
163
- opt.output('debug', 'Using setcap at ' + setcap);
185
+ debug('Using setcap at ' + setcap);
164
186
  let { node } = opt;
165
- node ||= await run(opt, 'Finding node', 'command -v node')
187
+ node ||= await run('Finding node', 'command -v node')
166
188
  .then(e => e.trim())
167
189
  .catch(() => {
168
- opt.output('warn', 'not in path.');
169
- opt.output('start', 'Checking for /usr/bin/node');
190
+ warn('not in path.');
191
+ start('Checking for /usr/bin/node');
170
192
  fs.accessSync('/usr/bin/node', fs.constants.X_OK);
171
- opt.output('done');
193
+ done();
172
194
  return '/usr/bin/node';
173
195
  });
174
- opt.output('start', 'Resolving real path for node');
196
+ start('Resolving real path for node');
175
197
  node = fs.realpathSync(node);
176
- opt.output('done');
177
- opt.output('debug', 'Using node at ' + node);
178
- await run(opt, 'Setting ports capability', `${setcap} cap_net_bind_service=${opt.action == 'enable' ? '+' : '-'}ep ${node}`);
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(output, ...allowList) {
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
- output('warn', message);
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;
@@ -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
+ }