wiki-server 0.26.0-rc.0 → 0.26.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/page.js CHANGED
@@ -13,9 +13,6 @@
13
13
  const fs = require('fs')
14
14
  const path = require('path')
15
15
  const events = require('events')
16
- const glob = require('glob')
17
-
18
- const async = require('async')
19
16
 
20
17
  const random_id = require('./random_id')
21
18
  const synopsis = require('wiki-client/lib/synopsis')
@@ -35,6 +32,25 @@ module.exports = exports = argv => {
35
32
  if (e) throw e
36
33
  })
37
34
 
35
+ // create a list of plugin pages.
36
+ const pluginPages = new Map()
37
+ Object.keys(require.main.require('./package').dependencies)
38
+ .filter(depend => depend.startsWith('wiki-plugin'))
39
+ .forEach(plugin => {
40
+ const pagesPath = path.join(
41
+ path.dirname(require.resolve(`${plugin}/package`, { paths: require.main.paths })),
42
+ 'pages',
43
+ )
44
+ fs.readdir(pagesPath, { withFileTypes: true }, (err, entries) => {
45
+ if (err) return
46
+ entries.forEach(entry => {
47
+ if (entry.isFile() && !pluginPages.has(entry.name)) {
48
+ pluginPages.set(entry.name, { pluginName: plugin, pluginPath: entry.parentPath })
49
+ }
50
+ })
51
+ })
52
+ })
53
+
38
54
  // #### Private utility methods. ####
39
55
  const load_parse = (loc, cb, annotations = {}) => {
40
56
  let page
@@ -46,14 +62,13 @@ module.exports = exports = argv => {
46
62
  const errorPage = path.basename(loc)
47
63
  const errorPagePath = path.dirname(loc)
48
64
  const recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage)
49
- // TODO: fs.exists depreciated
50
- fs.exists(path.dirname(recyclePage), exists => {
51
- if (exists) {
65
+ fs.access(path.dirname(recyclePage), fs.constants.F_OK, err => {
66
+ if (!err) {
52
67
  fs.rename(loc, recyclePage, err => {
53
68
  if (err) {
54
- console.log('ERROR: moving problem page #{loc} to recycler', err)
69
+ console.log(`ERROR: moving problem page ${loc} to recycler`, err)
55
70
  } else {
56
- console.log('ERROR: problem page #{loc} moved to recycler')
71
+ console.log(`ERROR: problem page ${loc} moved to recycler`)
57
72
  }
58
73
  })
59
74
  } else {
@@ -63,9 +78,9 @@ module.exports = exports = argv => {
63
78
  } else {
64
79
  fs.rename(loc, recyclePage, err => {
65
80
  if (err) {
66
- console.log('ERROR: moving problem page #{loc} to recycler', err)
81
+ console.log(`ERROR: moving problem page ${loc} to recycler`, err)
67
82
  } else {
68
- console.log('ERROR: problem page #{loc} moved to recycler')
83
+ console.log(`ERROR: problem page ${loc} moved to recycler`)
69
84
  }
70
85
  })
71
86
  }
@@ -104,9 +119,8 @@ module.exports = exports = argv => {
104
119
  const tryDefaults = (file, cb) => {
105
120
  const lastDefault = cb => {
106
121
  const defloc = path.join(argv.root, 'default-data', 'pages', file)
107
- // TODO: fs.exists depreciated
108
- fs.exists(defloc, exists => {
109
- if (exists) {
122
+ fs.access(defloc, fs.constants.F_OK, err => {
123
+ if (!err) {
110
124
  cb(defloc)
111
125
  } else {
112
126
  cb(null)
@@ -115,10 +129,8 @@ module.exports = exports = argv => {
115
129
  }
116
130
  if (argv.defaults) {
117
131
  const defloc = path.join(argv.data, '..', argv.defaults, 'pages', file)
118
- console.log('firstDefault', defloc)
119
- // TODO: fs.exists depreciated
120
- fs.exists(defloc, exists => {
121
- if (exists) {
132
+ fs.access(defloc, fs.constants.F_OK, err => {
133
+ if (!err) {
122
134
  cb(defloc)
123
135
  } else {
124
136
  lastDefault(cb)
@@ -138,22 +150,19 @@ module.exports = exports = argv => {
138
150
  case 'delete':
139
151
  if (file.startsWith('recycler/')) {
140
152
  // delete from recycler
141
- // TODO: fs.exists depreciated
142
- fs.exists(loc, exists => {
143
- if (exists)
153
+ fs.access(loc, fs.constants.F_OK, err => {
154
+ if (!err)
144
155
  fs.unlink(loc, err => {
145
156
  cb(err)
146
157
  })
147
158
  })
148
159
  } else {
149
160
  // move page to recycler
150
- // TODO: fs.exists depreciated
151
- fs.exists(loc, exists => {
152
- if (exists) {
161
+ fs.access(loc, fs.constants.F_OK, err => {
162
+ if (!err) {
153
163
  const recycleLoc = path.join(argv.recycler, file)
154
- // TODO: fs.exists depreciated
155
- fs.exists(path.dirname(recycleLoc), exists => {
156
- if (exists) {
164
+ fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => {
165
+ if (!err) {
157
166
  fs.rename(loc, recycleLoc, err => {
158
167
  cb(err)
159
168
  })
@@ -203,13 +212,11 @@ module.exports = exports = argv => {
203
212
  return
204
213
  }
205
214
 
206
- // TODO: fs.exists depreciated
207
- fs.exists(loc, exists => {
208
- if (exists) {
215
+ fs.access(loc, fs.constants.F_OK, err => {
216
+ if (!err) {
209
217
  const recycleLoc = path.join(argv.recycler, file)
210
- // TODO: fs.exists depreciated
211
- fs.exists(path.dirname(recycleLoc), exists => {
212
- if (exists) {
218
+ fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => {
219
+ if (!err) {
213
220
  copyFile(loc, recycleLoc, err => {
214
221
  cb(err)
215
222
  })
@@ -229,42 +236,20 @@ module.exports = exports = argv => {
229
236
  break
230
237
  }
231
238
  case 'get':
232
- // TODO: fs.exists depreciated
233
- fs.exists(loc, exists => {
234
- if (exists) {
239
+ fs.access(loc, fs.constants.F_OK, err => {
240
+ if (!err) {
235
241
  load_parse(loc, cb, { plugin: undefined })
236
242
  } else {
237
243
  tryDefaults(file, defloc => {
238
244
  if (defloc) {
239
245
  load_parse(defloc, cb)
240
246
  } else {
241
- glob('wiki-plugin-*/pages', { cwd: argv.packageDir }, (e, plugins) => {
242
- if (e) return cb(e)
243
-
244
- // if no plugins found
245
- if (plugins.length === 0) {
246
- cb(null, 'Page not found', 404)
247
- }
248
-
249
- let count = plugins.length
250
- const giveUp = () => {
251
- count -= 1
252
- if (count === 0) {
253
- cb(null, 'Page not found', 404)
254
- }
255
- }
256
- plugins.forEach(plugin => {
257
- const pluginName = plugin.slice(12, -6)
258
- const pluginloc = path.join(argv.packageDir, plugin, file)
259
- fs.exists(pluginloc, exists => {
260
- if (exists) {
261
- load_parse(pluginloc, cb, { plugin: pluginName })
262
- } else {
263
- giveUp()
264
- }
265
- })
266
- })
267
- })
247
+ if (pluginPages.has(file)) {
248
+ const { pluginName, pluginPath } = pluginPages.get(file)
249
+ load_parse(path.join(pluginPath, file), cb, { plugin: pluginName.slice(12) })
250
+ } else {
251
+ cb(null, 'Page not found', 404)
252
+ }
268
253
  }
269
254
  })
270
255
  }
@@ -272,11 +257,11 @@ module.exports = exports = argv => {
272
257
  break
273
258
  case 'put':
274
259
  page = JSON.stringify(page, null, 2)
275
- fs.exists(path.dirname(loc), exists => {
276
- if (exists) {
260
+ fs.access(path.dirname(loc), fs.constants.F_OK, err => {
261
+ if (!err) {
277
262
  fs.writeFile(loc, page, err => {
278
263
  if (err) {
279
- console.log('ERROR: write file #{loc} ', err)
264
+ console.log(`ERROR: write file ${loc} `, err)
280
265
  }
281
266
  cb(err)
282
267
  })
@@ -285,7 +270,7 @@ module.exports = exports = argv => {
285
270
  if (err) cb(err)
286
271
  fs.writeFile(loc, page, err => {
287
272
  if (err) {
288
- console.log('ERROR: write file #{loc} ', err)
273
+ console.log(`ERROR: write file ${loc} `, err)
289
274
  }
290
275
  cb(err)
291
276
  })
@@ -294,7 +279,7 @@ module.exports = exports = argv => {
294
279
  })
295
280
  break
296
281
  default:
297
- console.log('pagehandler: unrecognized action #{action}')
282
+ console.log(`pagehandler: unrecognized action ${action}`)
298
283
  }
299
284
  }
300
285
 
@@ -392,43 +377,46 @@ module.exports = exports = argv => {
392
377
  }
393
378
  return collaborativeLinks
394
379
  }
380
+
395
381
  fs.readdir(argv.db, (e, files) => {
396
382
  if (e) return cb(e)
397
- // used to make sure all of the files are read
398
- // and processesed in the site map before responding
399
- const doSitemap = (file, cb) => {
400
- itself.get(file, (e, page, status) => {
401
- if (file.match(/^\./)) return cb()
402
- if (e || status === 404) {
403
- console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status)
404
- return cb() // Ignore errors in the pagehandler get.
405
- }
406
- let pageLinksMap
407
- try {
408
- pageLinksMap = page.story.reduce(extractPageLinks, new Map())
409
- } catch (err) {
410
- console.log('METADATA *** #{wikiName} reduce to extract links on #{file} failed', err.message)
411
- pageLinksMap = []
412
- }
413
- //
414
- const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined
415
-
416
- cb(null, {
417
- slug: file,
418
- title: page.title,
419
- date: editDate(page.journal),
420
- synopsis: synopsis(page),
421
- links: pageLinks,
383
+ const doSitemap = async file => {
384
+ return new Promise(resolve => {
385
+ itself.get(file, (e, page, status) => {
386
+ if (file.match(/^\./)) return resolve(null)
387
+ if (e || status === 404) {
388
+ console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status)
389
+ return resolve(null) // Ignore errors in the pagehandler get.
390
+ }
391
+ let pageLinksMap
392
+ try {
393
+ pageLinksMap = page.story.reduce(extractPageLinks, new Map())
394
+ } catch (err) {
395
+ console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message)
396
+ pageLinksMap = []
397
+ }
398
+ //
399
+ const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined
400
+
401
+ resolve({
402
+ slug: file,
403
+ title: page.title,
404
+ date: editDate(page.journal),
405
+ synopsis: synopsis(page),
406
+ links: pageLinks,
407
+ })
422
408
  })
423
409
  })
424
410
  }
425
- async.map(files, doSitemap, (e, sitemap) => {
426
- if (e) return cb(e)
427
- cb(
428
- null,
429
- sitemap.filter(item => item != null),
430
- )
431
- })
411
+
412
+ Promise.all(files.map(doSitemap))
413
+ .then(sitemap => {
414
+ cb(
415
+ null,
416
+ sitemap.filter(item => item != null),
417
+ )
418
+ })
419
+ .catch(e => cb(e))
432
420
  })
433
421
  }
434
422
 
package/lib/plugins.js CHANGED
@@ -9,7 +9,6 @@
9
9
  // support server-side plugins
10
10
 
11
11
  const fs = require('node:fs')
12
- const glob = require('glob')
13
12
  const { pathToFileURL } = require('node:url')
14
13
  // forward = require './forward'
15
14
 
@@ -24,8 +23,8 @@ module.exports = exports = argv => {
24
23
 
25
24
  const startServer = (params, plugin) => {
26
25
  const server = `${argv.packageDir}/${plugin}/server/server.js`
27
- fs.exists(server, exists => {
28
- if (exists) {
26
+ fs.access(server, fs.constants.F_OK, err => {
27
+ if (!err) {
29
28
  console.log('starting plugin', plugin)
30
29
  import(pathToFileURL(server))
31
30
  .then(exported => {
@@ -43,11 +42,12 @@ module.exports = exports = argv => {
43
42
  // emitter = new events.EventEmitter()
44
43
  // forward.init params.app, emitter
45
44
  // params.emitter = emitter
46
- glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, plugins) => {
47
- plugins.forEach(plugin => {
45
+
46
+ Object.keys(require.main.require('./package').dependencies)
47
+ .filter(depend => depend.startsWith('wiki-plugin'))
48
+ .forEach(plugin => {
48
49
  startServer(params, plugin)
49
50
  })
50
- })
51
51
  }
52
52
 
53
53
  return { startServers }
package/lib/search.js CHANGED
@@ -85,8 +85,8 @@ module.exports = exports = argv => {
85
85
 
86
86
  const searchSave = (siteIndex, cb) => {
87
87
  // save index to file
88
- fs.exists(argv.status, exists => {
89
- if (exists) {
88
+ fs.access(argv.status, fs.constants.F_OK, err => {
89
+ if (!err) {
90
90
  writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => {
91
91
  if (e) return cb(e)
92
92
  touch(indexUpdateFlag, () => {
@@ -108,8 +108,8 @@ module.exports = exports = argv => {
108
108
 
109
109
  const searchRestore = cb => {
110
110
  // restore index, or create if it doesn't already exist
111
- fs.exists(siteIndexLoc, exists => {
112
- if (exists) {
111
+ fs.access(siteIndexLoc, fs.constants.F_OK, err => {
112
+ if (!err) {
113
113
  fs.readFile(siteIndexLoc, (err, data) => {
114
114
  if (err) return cb(err)
115
115
  try {
@@ -169,8 +169,12 @@ module.exports = exports = argv => {
169
169
  .replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ')
170
170
  .replace(/<(?:[^>])+>/g, ' ')
171
171
  .replace(/(https?:.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, match => {
172
- const myUrl = url.parse(match)
173
- return myUrl.hostname + ' ' + myUrl.pathname
172
+ try {
173
+ const myUrl = new URL(match)
174
+ return myUrl.hostname
175
+ } catch {
176
+ return ' '
177
+ }
174
178
  })
175
179
  .replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ')
176
180
  .replace(/[\p{White_Space}\n\t]+/gu, ' ')
@@ -250,7 +254,7 @@ module.exports = exports = argv => {
250
254
  // we save the pagehandler, so we can recreate the site index if it is removed
251
255
  searchPageHandler = searchPageHandler ?? pagehandler
252
256
 
253
- //timeLabel = "SITE INDEX #{wikiName} : Created"
257
+ //timeLabel = `SITE INDEX ${wikiName} : Created`
254
258
  //console.time timeLabel
255
259
 
256
260
  pagehandler.slugs((e, slugs) => {
package/lib/security.js CHANGED
@@ -41,8 +41,8 @@ module.exports = exports = (log, loga, argv) => {
41
41
 
42
42
  // Retrieve owner infomation from identity file in status directory
43
43
  security.retrieveOwner = cb => {
44
- fs.exists(idFile, exists => {
45
- if (exists) {
44
+ fs.access(idFile, fs.constants.F_OK, err => {
45
+ if (!err) {
46
46
  fs.readFile(idFile, (err, data) => {
47
47
  if (err) return cb(err)
48
48
  owner += data
package/lib/server.js CHANGED
@@ -28,8 +28,6 @@ const { pipeline } = require('node:stream/promises')
28
28
  // From npm
29
29
  const express = require('express')
30
30
  const hbs = require('express-hbs')
31
- const glob = require('glob')
32
- const async = require('async')
33
31
  const f = require('flates')
34
32
 
35
33
  const createDOMPurify = require('dompurify')
@@ -38,8 +36,7 @@ const { JSDOM } = require('jsdom')
38
36
  const window = new JSDOM('').window
39
37
  const DOMPurify = createDOMPurify(window)
40
38
 
41
- // node-fetch is now ESM only
42
- const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args))
39
+ // Using native fetch API (available in Node.js 18+)
43
40
 
44
41
  // Express 4 middleware
45
42
  const logger = require('morgan')
@@ -163,7 +160,7 @@ module.exports = exports = argv => {
163
160
 
164
161
  const remoteGet = (remote, slug, cb) => {
165
162
  // assume http, as we know no better at this point and we need to specify a protocol.
166
- const remoteURL = new URL('http://#{remote}/#{slug}.json').toString()
163
+ const remoteURL = new URL(`http://${remote}/${slug}.json`).toString()
167
164
  // set a two second timeout
168
165
  fetch(remoteURL, { signal: AbortSignal.timeout(2000) })
169
166
  .then(res => {
@@ -271,13 +268,16 @@ module.exports = exports = argv => {
271
268
  app.use('/assets', cors, express.static(argv.assets))
272
269
 
273
270
  // Add static routes to the plugins client.
274
- glob('wiki-plugin-*/client', { cwd: argv.packageDir }, (e, plugins) => {
275
- plugins.map(plugin => {
276
- const pluginName = plugin.slice(12, -7)
277
- const pluginPath = '/plugins/' + pluginName
278
- app.use(pluginPath, cors, express.static(path.join(argv.packageDir, plugin), staticPathOptions))
271
+ Object.keys(require.main.require('./package').dependencies)
272
+ .filter(depend => depend.startsWith('wiki-plugin'))
273
+ .forEach(plugin => {
274
+ const clientPath = path.join(
275
+ path.dirname(require.resolve(`${plugin}/package`, { paths: require.main.paths })),
276
+ 'client',
277
+ )
278
+ const pluginPath = '/plugins/' + plugin.slice(12)
279
+ app.use(pluginPath, cors, express.static(clientPath, staticPathOptions))
279
280
  })
280
- })
281
281
 
282
282
  // Add static routes to the security client.
283
283
  if (argv.security != './security') {
@@ -387,32 +387,17 @@ module.exports = exports = argv => {
387
387
  app.get('/system/factories.json', (req, res) => {
388
388
  res.status(200)
389
389
  res.header('Content-Type', 'application/json')
390
- // Plugins are located in packages in argv.packageDir, with package names of the form wiki-plugin-*
391
- glob(path.join(argv.packageDir, 'wiki-plugin-*', 'factory.json'), (e, files) => {
392
- if (e) {
393
- return res.e(e)
394
- }
395
-
396
- const doFactories = (file, cb) => {
397
- fs.readFile(file, (err, data) => {
398
- if (err) {
399
- return cb()
400
- }
401
- try {
402
- const factory = JSON.parse(data)
403
- cb(null, factory)
404
- } catch (err) {
405
- return cb()
406
- }
407
- })
408
- }
409
- async.map(files, doFactories, (e, factories) => {
410
- if (e) {
411
- res.e(e)
390
+ const factories = []
391
+ Object.keys(require.main.require('./package').dependencies)
392
+ .filter(depend => depend.startsWith('wiki-plugin'))
393
+ .forEach(plugin => {
394
+ try {
395
+ factories.push(require.main.require(`${plugin}/factory`))
396
+ } catch {
397
+ // do nothing if plugin doesn't have a factory category.
412
398
  }
413
- res.end(JSON.stringify(factories))
414
399
  })
415
- })
400
+ res.end(JSON.stringify(factories))
416
401
  })
417
402
 
418
403
  // ###### Json Routes ######
@@ -444,7 +429,7 @@ module.exports = exports = argv => {
444
429
  // If themes doesn't exist send 404 and let the client
445
430
  // deal with it.
446
431
  app.get(/^\/theme\/(\w+\.\w+)$/, cors, (req, res) => {
447
- res.sendFile(path.join(argv.status, 'theme', req.params[0]), e => {
432
+ res.sendFile(path.join(argv.status, 'theme', req.params[0]), { dotfiles: 'allow' }, e => {
448
433
  if (e) {
449
434
  // swallow the error if the theme does not exist...
450
435
  if (req.path === '/theme/style.css') {
@@ -462,9 +447,9 @@ module.exports = exports = argv => {
462
447
  const favLoc = path.join(argv.status, 'favicon.png')
463
448
  const defaultFavLoc = path.join(argv.root, 'default-data', 'status', 'favicon.png')
464
449
  app.get('/favicon.png', cors, (req, res) => {
465
- fs.exists(favLoc, exists => {
466
- if (exists) {
467
- res.sendFile(favLoc)
450
+ fs.access(favLoc, fs.constants.F_OK, err => {
451
+ if (!err) {
452
+ res.sendFile(favLoc, { dotfiles: 'allow' })
468
453
  } else {
469
454
  res.sendFile(defaultFavLoc)
470
455
  }
@@ -484,9 +469,9 @@ module.exports = exports = argv => {
484
469
  // save it.
485
470
  app.post('/favicon.png', authorized, (req, res) => {
486
471
  const favicon = req.body.image.replace(/^data:image\/png;base64,/, '')
487
- const buf = new Buffer(favicon, 'base64')
488
- fs.exists(argv.status, exists => {
489
- if (exists) {
472
+ const buf = Buffer.from(favicon, 'base64')
473
+ fs.access(argv.status, fs.constants.F_OK, err => {
474
+ if (!err) {
490
475
  fs.writeFile(favLoc, buf, e => {
491
476
  if (e) {
492
477
  return res.e(e)
@@ -508,7 +493,7 @@ module.exports = exports = argv => {
508
493
 
509
494
  // Redirect remote favicons to the server they are needed from.
510
495
  app.get(/^\/remote\/([a-zA-Z0-9:.-]+\/favicon.png)$/, (req, res) => {
511
- const remotefav = 'http://#{req.params[0]}'
496
+ const remotefav = `http://${req.params[0]}`
512
497
  res.redirect(remotefav)
513
498
  })
514
499
 
@@ -519,38 +504,40 @@ module.exports = exports = argv => {
519
504
  // negative space outward pointing arrows nicely indicates that items can be removed
520
505
  const recyclerFavLoc = path.join(argv.root, 'default-data', 'status', 'recycler.png')
521
506
  app.get('/recycler/favicon.png', authorized, (req, res) => {
522
- res.sendFile(recyclerFavLoc)
507
+ res.sendFile(recyclerFavLoc, { dotfiles: 'allow' })
523
508
  })
524
509
 
525
510
  // Send an array of pages currently in the recycler via json
526
511
  app.get('/recycler/system/slugs.json', authorized, (req, res) => {
527
512
  fs.readdir(argv.recycler, (e, files) => {
528
- const doRecyclermap = (file, cb) => {
529
- const recycleFile = 'recycler/' + file
530
- pagehandler.get(recycleFile, (e, page, status) => {
531
- if (e || status === 404) {
532
- console.log('Problem building recycler map:', file, 'e: ', e)
533
- // this will leave an undefined/empty item in the array, which we will filter out later
534
- return cb()
535
- }
536
- cb(null, {
537
- slug: file,
538
- title: page.title,
513
+ if (e) {
514
+ return res.e(e)
515
+ }
516
+ const doRecyclermap = async file => {
517
+ return new Promise(resolve => {
518
+ const recycleFile = 'recycler/' + file
519
+ pagehandler.get(recycleFile, (e, page, status) => {
520
+ if (e || status === 404) {
521
+ console.log('Problem building recycler map:', file, 'e: ', e)
522
+ // this will leave an undefined/empty item in the array, which we will filter out later
523
+ return resolve(null)
524
+ }
525
+ resolve({
526
+ slug: file,
527
+ title: page.title,
528
+ })
539
529
  })
540
530
  })
541
531
  }
542
532
 
543
- if (e) {
544
- return res.e(e)
545
- }
546
- async.map(files, doRecyclermap, (e, recyclermap) => {
547
- if (e) {
548
- return res.e(e)
549
- }
550
- // remove any empty items
551
- recyclermap = recyclermap.filter(el => !!el)
552
- res.send(recyclermap)
553
- })
533
+ Promise.all(files.map(doRecyclermap))
534
+ .then(recyclermap => {
535
+ recyclermap = recyclermap.filter(el => !!el)
536
+ res.send(recyclermap)
537
+ })
538
+ .catch(error => {
539
+ res.e(error)
540
+ })
554
541
  })
555
542
  })
556
543
 
@@ -590,21 +577,21 @@ module.exports = exports = argv => {
590
577
 
591
578
  // Returns a list of installed plugins. (does this get called anymore!)
592
579
  app.get('/system/plugins.json', cors, (req, res) => {
593
- glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, files) => {
594
- if (e) {
595
- return res.e(e)
596
- }
597
- // extract the plugin name from the name of the directory it's installed in
598
- files = files.map(file => file.slice(12))
599
- res.send(files)
600
- })
580
+ try {
581
+ const pluginNames = Object.keys(require.main.require('./package').dependencies)
582
+ .filter(depend => depend.startsWith('wiki-plugin'))
583
+ .map(name => name.slice(12))
584
+ res.send(pluginNames)
585
+ } catch (e) {
586
+ return res.e(e)
587
+ }
601
588
  })
602
589
  //{
603
590
  const sitemapLoc = path.join(argv.status, 'sitemap.json')
604
591
  app.get('/system/sitemap.json', cors, (req, res) => {
605
- fs.exists(sitemapLoc, exists => {
606
- if (exists) {
607
- res.sendFile(sitemapLoc)
592
+ fs.access(sitemapLoc, fs.constants.F_OK, err => {
593
+ if (!err) {
594
+ res.sendFile(sitemapLoc, { dotfiles: 'allow' })
608
595
  } else {
609
596
  // only createSitemap if we are not already creating one
610
597
  if (!sitemaphandler.isWorking()) {
@@ -612,7 +599,7 @@ module.exports = exports = argv => {
612
599
  }
613
600
  // wait for the sitemap file to be written, before sending
614
601
  sitemaphandler.once('finished', () => {
615
- res.sendFile(sitemapLoc)
602
+ res.sendFile(sitemapLoc, { dotfiles: 'allow' })
616
603
  })
617
604
  }
618
605
  })
@@ -620,15 +607,15 @@ module.exports = exports = argv => {
620
607
 
621
608
  const xmlSitemapLoc = path.join(argv.status, 'sitemap.xml')
622
609
  app.get('/sitemap.xml', cors, (req, res) => {
623
- fs.exists(sitemapLoc, exists => {
624
- if (exists) {
625
- res.sendFile(xmlSitemapLoc)
610
+ fs.access(sitemapLoc, fs.constants.F_OK, err => {
611
+ if (!err) {
612
+ res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' })
626
613
  } else {
627
614
  if (!sitemaphandler.isWorking()) {
628
615
  sitemaphandler.createSitemap(pagehandler)
629
616
  }
630
617
  sitemaphandler.once('finished', () => {
631
- res.sendFile(xmlSitemapLoc)
618
+ res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' })
632
619
  })
633
620
  }
634
621
  })
@@ -636,16 +623,16 @@ module.exports = exports = argv => {
636
623
 
637
624
  const searchIndexLoc = path.join(argv.status, 'site-index.json')
638
625
  app.get('/system/site-index.json', cors, (req, res) => {
639
- fs.exists(searchIndexLoc, exists => {
640
- if (exists) {
641
- res.sendFile(searchIndexLoc)
626
+ fs.access(searchIndexLoc, fs.constants.F_OK, err => {
627
+ if (!err) {
628
+ res.sendFile(searchIndexLoc, { dotfiles: 'allow' })
642
629
  } else {
643
630
  // only create index if we are not already creating one
644
631
  if (!searchhandler.isWorking()) {
645
632
  searchhandler.createIndex(pagehandler)
646
633
  }
647
634
  searchhandler.once('indexed', () => {
648
- res.sendFile(searchIndexLoc)
635
+ res.sendFile(searchIndexLoc, { dotfiles: 'allow' })
649
636
  })
650
637
  }
651
638
  })
@@ -656,28 +643,29 @@ module.exports = exports = argv => {
656
643
  if (e) {
657
644
  return res.e(e)
658
645
  }
659
- async.map(
660
- sitemap,
661
- (stub, done) => {
646
+ const pagePromises = sitemap.map(stub => {
647
+ return new Promise((resolve, reject) => {
662
648
  pagehandler.get(stub.slug, (error, page) => {
663
- if (e) {
664
- return done(e)
649
+ if (error) {
650
+ return reject(error)
665
651
  }
666
- done(null, { slug: stub.slug, page })
652
+ resolve({ slug: stub.slug, page })
667
653
  })
668
- },
669
- (e, pages) => {
670
- if (e) {
671
- return res.e(e)
672
- }
673
- res.json(
674
- pages.reduce((dict, combined) => {
675
- dict[combined.slug] = combined.page
676
- return dict
677
- }, {}),
678
- )
679
- },
680
- )
654
+ })
655
+ })
656
+
657
+ Promise.all(pagePromises)
658
+ .then(pages => {
659
+ const pageExport = pages.reduce((dict, combined) => {
660
+ dict[combined.slug] = combined.page
661
+ return dict
662
+ }, {})
663
+ // TODO: this fails for a very large site
664
+ res.json(pageExport)
665
+ })
666
+ .catch(error => {
667
+ res.e(error)
668
+ })
681
669
  })
682
670
  })
683
671
 
@@ -692,32 +680,31 @@ module.exports = exports = argv => {
692
680
 
693
681
  app.get('/system/version.json', admin, (req, res) => {
694
682
  const versions = {}
695
- const wikiModule = module.parent.parent.parent
683
+ const wikiModule = require.main
696
684
  versions[wikiModule.require('./package').name] = wikiModule.require('./package').version
697
685
  versions[wikiModule.require('wiki-server/package').name] = wikiModule.require('wiki-server/package').version
698
686
  versions[wikiModule.require('wiki-client/package').name] = wikiModule.require('wiki-client/package').version
687
+
699
688
  versions['security'] = {}
700
- versions['plugins'] = {}
689
+ Object.keys(require.main.require('./package').dependencies)
690
+ .filter(depend => depend.startsWith('wiki-security'))
691
+ .forEach(key => {
692
+ versions.security[key] = wikiModule.require(`${key}/package`).version
693
+ })
701
694
 
702
- glob('+(wiki-security-*|wiki-plugin-*)', { cwd: argv.packageDir }, (e, plugins) => {
703
- plugins.map(plugin => {
704
- if (plugin.includes('wiki-security')) {
705
- versions.security[wikiModule.require(plugin + '/package').name] = wikiModule.require(
706
- plugin + '/package',
707
- ).version
708
- } else {
709
- versions.plugins[wikiModule.require(plugin + '/package').name] = wikiModule.require(
710
- plugin + '/package',
711
- ).version
712
- }
713
- res.json(versions)
695
+ versions['plugins'] = {}
696
+ Object.keys(require.main.require('./package').dependencies)
697
+ .filter(depend => depend.startsWith('wiki-plugin'))
698
+ .forEach(key => {
699
+ versions.plugins[key] = wikiModule.require(`${key}/package`).version
714
700
  })
715
- })
701
+
702
+ res.json(versions)
716
703
  })
717
704
 
718
705
  // ##### Proxy routes #####
719
706
 
720
- app.get('/proxy/*', authorized, (req, res) => {
707
+ app.get('/proxy/*splat', authorized, (req, res) => {
721
708
  const pathParts = req.originalUrl.split('/')
722
709
  const remoteHost = pathParts[2]
723
710
  pathParts.splice(0, 3)
@@ -731,7 +718,7 @@ module.exports = exports = argv => {
731
718
  requestURL.endsWith('.jpg') ||
732
719
  pathParts[0] === 'plugin'
733
720
  ) {
734
- fetch(requestURL, { timeout: 2000 })
721
+ fetch(requestURL, { signal: AbortSignal.timeout(2000) })
735
722
  .then(async fetchRes => {
736
723
  if (fetchRes.ok) {
737
724
  res.set('content-type', fetchRes.headers.get('content-type'))
package/lib/sitemap.js CHANGED
@@ -119,8 +119,8 @@ module.exports = exports = argv => {
119
119
  }
120
120
 
121
121
  const sitemapSave = (sitemap, cb) => {
122
- fs.exists(argv.status, exists => {
123
- if (exists) {
122
+ fs.access(argv.status, fs.constants.F_OK, err => {
123
+ if (!err) {
124
124
  writeFileAtomic(sitemapLoc, JSON.stringify(sitemap), e => {
125
125
  if (e) return cb(e)
126
126
  cb()
@@ -136,8 +136,8 @@ module.exports = exports = argv => {
136
136
  }
137
137
 
138
138
  const sitemapRestore = cb => {
139
- fs.exists(sitemapLoc, exists => {
140
- if (exists) {
139
+ fs.access(sitemapLoc, fs.constants.F_OK, err => {
140
+ if (!err) {
141
141
  fs.readFile(sitemapLoc, (err, data) => {
142
142
  if (err) return cb(err)
143
143
  try {
@@ -172,8 +172,8 @@ module.exports = exports = argv => {
172
172
  const xmlmap = { urlset: { $: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' }, url: xmlmapPages } }
173
173
  const builder = new xml2js.Builder()
174
174
  const xml = builder.buildObject(xmlmap)
175
- fs.exists(argv.status, exists => {
176
- if (exists) {
175
+ fs.access(argv.status, fs.constants.F_OK, err => {
176
+ if (!err) {
177
177
  writeFileAtomic(xmlSitemapLoc, xml, e => {
178
178
  if (e) return cb(e)
179
179
  cb()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wiki-server",
3
3
  "description": "A Federated Wiki Server",
4
- "version": "0.26.0-rc.0",
4
+ "version": "0.26.0-rc.2",
5
5
  "author": {
6
6
  "name": "Ward Cunningham",
7
7
  "email": "ward@c2.com",
@@ -30,22 +30,19 @@
30
30
  "jon r <jon@allmende.io>"
31
31
  ],
32
32
  "dependencies": {
33
- "async": "^3.2.4",
34
33
  "body-parser": "^2.2.0",
35
34
  "client-sessions": "^0.8.0",
36
35
  "coffeescript": "^2.5.0",
37
36
  "cookie-parser": "^1.4.4",
38
37
  "dompurify": "^3.1.0",
39
38
  "errorhandler": "^1.5.1",
40
- "express": "^4.21.2",
39
+ "express": "^5.1.0",
41
40
  "express-hbs": "^2.5.0",
42
41
  "flates": "0.0.5",
43
- "glob": "^7.2.3",
44
42
  "jsdom": "^26.0.0",
45
43
  "method-override": "^3.0.0",
46
44
  "minisearch": "^7.1.0",
47
45
  "morgan": "^1.9.1",
48
- "node-fetch": "^3.3.2",
49
46
  "write-file-atomic": "^6.0.0",
50
47
  "xml2js": "^0.6.2"
51
48
  },