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

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/index.js CHANGED
@@ -2,7 +2,11 @@
2
2
  // Simple file so that if you require this directory
3
3
  // in node it instead requires ./lib/server.coffee
4
4
  // with coffee-script already loaded.
5
- require('coffeescript')
6
- require('coffeescript/register')
5
+ // import('coffeescript')
6
+ // import('coffeescript/register.js')
7
7
 
8
- module.exports = require('./lib/server')
8
+ // const { default: server } = await import('./lib/server.js')
9
+ // export default server
10
+
11
+ import { default as server } from './lib/server.js'
12
+ export default server
@@ -9,13 +9,19 @@
9
9
  // **defaultargs.coffee** when called on the argv object this
10
10
  // module will create reasonable defaults for options not supplied,
11
11
  // based on what information is provided.
12
- const path = require('node:path')
12
+ import path from 'node:path'
13
+ import { randomBytes } from 'node:crypto'
14
+ import { fileURLToPath } from 'node:url'
15
+ import { dirname } from 'node:path'
16
+
17
+ const __filename = fileURLToPath(import.meta.url)
18
+ const __dirname = dirname(__filename)
13
19
 
14
20
  const getUserHome = () => {
15
21
  return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE
16
22
  }
17
23
 
18
- module.exports = argv => {
24
+ export default argv => {
19
25
  argv = argv || {}
20
26
  argv.root ||= __dirname
21
27
  // the directory that contains all the packages that makeup the wiki
@@ -32,7 +38,7 @@ module.exports = argv => {
32
38
  argv.url ||= `http://localhost${argv.port === 80 ? '' : ':' + argv.port}`
33
39
  argv.id ||= path.join(argv.status, 'owner.json')
34
40
  argv.uploadLimit ||= '5mb'
35
- argv.cookieSecret ||= require('crypto').randomBytes(64).toString('hex')
41
+ argv.cookieSecret ||= randomBytes(64).toString('hex')
36
42
  argv.secure_cookie ||= false
37
43
  argv.session_duration ||= 7
38
44
  argv.neighbors ||= ''
@@ -43,9 +49,9 @@ module.exports = argv => {
43
49
  argv.database = JSON.parse(argv.database)
44
50
  }
45
51
  argv.database ||= {}
46
- argv.database.type ||= './page'
52
+ argv.database.type ||= './page.js'
47
53
  if (argv.database.type.charAt(0) === '.') {
48
- if (argv.database.type != './page') {
54
+ if (argv.database.type != './page.js') {
49
55
  console.log('\n\nWARNING: This storage option is depeciated.')
50
56
  console.log(' See ReadMe for details of the changes required.\n\n')
51
57
  }
@@ -53,8 +59,8 @@ module.exports = argv => {
53
59
  argv.database.type = 'wiki-storage-' + argv.database.type
54
60
  }
55
61
 
56
- argv.security_type ||= './security'
57
- if (argv.security_type === './security') {
62
+ argv.security_type ||= './security.js'
63
+ if (argv.security_type === './security.js') {
58
64
  console.log('\n\nINFORMATION: Using default security module.')
59
65
  } else {
60
66
  argv.security_type = 'wiki-security-' + argv.security_type
package/lib/forward.js CHANGED
@@ -1,4 +1,4 @@
1
- const init = (app, emitter) => {
1
+ export const init = (app, emitter) => {
2
2
  let sockets = []
3
3
  app.io.on('connection', socket => {
4
4
  let listeners = []
@@ -35,4 +35,3 @@ const init = (app, emitter) => {
35
35
  })
36
36
  })
37
37
  }
38
- module.exports = { init }
package/lib/page.js CHANGED
@@ -10,15 +10,53 @@
10
10
  // Everything is stored using json flat files.
11
11
 
12
12
  // #### Requires ####
13
- const fs = require('fs')
14
- const path = require('path')
15
- const events = require('events')
16
- const glob = require('glob')
13
+ // const fs = require('fs')
14
+ // const path = require('path')
15
+ // const events = require('events')
16
+ // const glob = require('glob')
17
+ //
18
+ // const async = require('async')
19
+ //
20
+ // // const random_id = require('./random_id.cjs')
21
+ // const synopsis = require('wiki-client/lib/synopsis')
22
+
23
+ import fs from 'node:fs'
24
+ import path from 'node:path'
25
+ import url from 'node:url'
26
+ import events from 'node:events'
27
+
28
+ import { createRequire } from 'node:module'
29
+ const require = createRequire(import.meta.url)
30
+
31
+ // Use dynamic import to load package.json from the main application's working directory
32
+ const wikiPackageImport = async () => {
33
+ let done = false
34
+ return new Promise(resolve => {
35
+ import('wiki/package.json', { with: { type: 'json' } })
36
+ .then(imported => {
37
+ done = true
38
+ resolve(imported.default)
39
+ })
40
+ .catch(e => {
41
+ return e
42
+ })
43
+ .then(async () => {
44
+ if (done) return
45
+ const packageJsonPath = path.join(process.cwd(), 'package.json')
46
+ const packageJsonUrl = url.pathToFileURL(packageJsonPath).href
47
+ import(packageJsonUrl, { with: { type: 'json' } })
48
+ .then(imported => {
49
+ resolve(imported.default)
50
+ })
51
+ .catch(e => console.error('problems importing package', e))
52
+ })
53
+ })
54
+ }
17
55
 
18
- const async = require('async')
56
+ const packageJson = await wikiPackageImport()
19
57
 
20
- const random_id = require('./random_id')
21
- const synopsis = require('wiki-client/lib/synopsis')
58
+ // import random_id from './random_id.cjs'; // Uncomment if needed
59
+ import synopsis from 'wiki-client/lib/synopsis.js' // Add correct extension if necessary
22
60
 
23
61
  const asSlug = name =>
24
62
  name
@@ -28,13 +66,30 @@ const asSlug = name =>
28
66
 
29
67
  // Export a function that generates a page handler
30
68
  // when called with options object.
31
- module.exports = exports = argv => {
69
+ export default argv => {
32
70
  const wikiName = new URL(argv.url).hostname
33
71
 
34
72
  fs.mkdir(argv.db, { recursive: true }, e => {
35
73
  if (e) throw e
36
74
  })
37
75
 
76
+ // create a list of plugin pages.
77
+ const pluginPages = new Map()
78
+
79
+ Object.keys(packageJson.dependencies)
80
+ .filter(depend => depend.startsWith('wiki-plugin'))
81
+ .forEach(plugin => {
82
+ const pagesPath = path.join(path.dirname(require.resolve(`${plugin}/package`)), 'pages')
83
+ fs.readdir(pagesPath, { withFileTypes: true }, (err, entries) => {
84
+ if (err) return
85
+ entries.forEach(entry => {
86
+ if (entry.isFile() && !pluginPages.has(entry.name)) {
87
+ pluginPages.set(entry.name, { pluginName: plugin, pluginPath: entry.parentPath })
88
+ }
89
+ })
90
+ })
91
+ })
92
+
38
93
  // #### Private utility methods. ####
39
94
  const load_parse = (loc, cb, annotations = {}) => {
40
95
  let page
@@ -46,14 +101,13 @@ module.exports = exports = argv => {
46
101
  const errorPage = path.basename(loc)
47
102
  const errorPagePath = path.dirname(loc)
48
103
  const recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage)
49
- // TODO: fs.exists depreciated
50
- fs.exists(path.dirname(recyclePage), exists => {
51
- if (exists) {
104
+ fs.access(path.dirname(recyclePage), fs.constants.F_OK, err => {
105
+ if (!err) {
52
106
  fs.rename(loc, recyclePage, err => {
53
107
  if (err) {
54
- console.log('ERROR: moving problem page #{loc} to recycler', err)
108
+ console.log(`ERROR: moving problem page ${loc} to recycler`, err)
55
109
  } else {
56
- console.log('ERROR: problem page #{loc} moved to recycler')
110
+ console.log(`ERROR: problem page ${loc} moved to recycler`)
57
111
  }
58
112
  })
59
113
  } else {
@@ -63,9 +117,9 @@ module.exports = exports = argv => {
63
117
  } else {
64
118
  fs.rename(loc, recyclePage, err => {
65
119
  if (err) {
66
- console.log('ERROR: moving problem page #{loc} to recycler', err)
120
+ console.log(`ERROR: moving problem page ${loc} to recycler`, err)
67
121
  } else {
68
- console.log('ERROR: problem page #{loc} moved to recycler')
122
+ console.log(`ERROR: problem page ${loc} moved to recycler`)
69
123
  }
70
124
  })
71
125
  }
@@ -104,9 +158,8 @@ module.exports = exports = argv => {
104
158
  const tryDefaults = (file, cb) => {
105
159
  const lastDefault = cb => {
106
160
  const defloc = path.join(argv.root, 'default-data', 'pages', file)
107
- // TODO: fs.exists depreciated
108
- fs.exists(defloc, exists => {
109
- if (exists) {
161
+ fs.access(defloc, fs.constants.F_OK, err => {
162
+ if (!err) {
110
163
  cb(defloc)
111
164
  } else {
112
165
  cb(null)
@@ -115,10 +168,8 @@ module.exports = exports = argv => {
115
168
  }
116
169
  if (argv.defaults) {
117
170
  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) {
171
+ fs.access(defloc, fs.constants.F_OK, err => {
172
+ if (!err) {
122
173
  cb(defloc)
123
174
  } else {
124
175
  lastDefault(cb)
@@ -138,22 +189,19 @@ module.exports = exports = argv => {
138
189
  case 'delete':
139
190
  if (file.startsWith('recycler/')) {
140
191
  // delete from recycler
141
- // TODO: fs.exists depreciated
142
- fs.exists(loc, exists => {
143
- if (exists)
192
+ fs.access(loc, fs.constants.F_OK, err => {
193
+ if (!err)
144
194
  fs.unlink(loc, err => {
145
195
  cb(err)
146
196
  })
147
197
  })
148
198
  } else {
149
199
  // move page to recycler
150
- // TODO: fs.exists depreciated
151
- fs.exists(loc, exists => {
152
- if (exists) {
200
+ fs.access(loc, fs.constants.F_OK, err => {
201
+ if (!err) {
153
202
  const recycleLoc = path.join(argv.recycler, file)
154
- // TODO: fs.exists depreciated
155
- fs.exists(path.dirname(recycleLoc), exists => {
156
- if (exists) {
203
+ fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => {
204
+ if (!err) {
157
205
  fs.rename(loc, recycleLoc, err => {
158
206
  cb(err)
159
207
  })
@@ -203,13 +251,11 @@ module.exports = exports = argv => {
203
251
  return
204
252
  }
205
253
 
206
- // TODO: fs.exists depreciated
207
- fs.exists(loc, exists => {
208
- if (exists) {
254
+ fs.access(loc, fs.constants.F_OK, err => {
255
+ if (!err) {
209
256
  const recycleLoc = path.join(argv.recycler, file)
210
- // TODO: fs.exists depreciated
211
- fs.exists(path.dirname(recycleLoc), exists => {
212
- if (exists) {
257
+ fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => {
258
+ if (!err) {
213
259
  copyFile(loc, recycleLoc, err => {
214
260
  cb(err)
215
261
  })
@@ -229,42 +275,20 @@ module.exports = exports = argv => {
229
275
  break
230
276
  }
231
277
  case 'get':
232
- // TODO: fs.exists depreciated
233
- fs.exists(loc, exists => {
234
- if (exists) {
278
+ fs.access(loc, fs.constants.F_OK, err => {
279
+ if (!err) {
235
280
  load_parse(loc, cb, { plugin: undefined })
236
281
  } else {
237
282
  tryDefaults(file, defloc => {
238
283
  if (defloc) {
239
284
  load_parse(defloc, cb)
240
285
  } 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
- })
286
+ if (pluginPages.has(file)) {
287
+ const { pluginName, pluginPath } = pluginPages.get(file)
288
+ load_parse(path.join(pluginPath, file), cb, { plugin: pluginName.slice(12) })
289
+ } else {
290
+ cb(null, 'Page not found', 404)
291
+ }
268
292
  }
269
293
  })
270
294
  }
@@ -272,11 +296,11 @@ module.exports = exports = argv => {
272
296
  break
273
297
  case 'put':
274
298
  page = JSON.stringify(page, null, 2)
275
- fs.exists(path.dirname(loc), exists => {
276
- if (exists) {
299
+ fs.access(path.dirname(loc), fs.constants.F_OK, err => {
300
+ if (!err) {
277
301
  fs.writeFile(loc, page, err => {
278
302
  if (err) {
279
- console.log('ERROR: write file #{loc} ', err)
303
+ console.log(`ERROR: write file ${loc} `, err)
280
304
  }
281
305
  cb(err)
282
306
  })
@@ -285,7 +309,7 @@ module.exports = exports = argv => {
285
309
  if (err) cb(err)
286
310
  fs.writeFile(loc, page, err => {
287
311
  if (err) {
288
- console.log('ERROR: write file #{loc} ', err)
312
+ console.log(`ERROR: write file ${loc} `, err)
289
313
  }
290
314
  cb(err)
291
315
  })
@@ -294,7 +318,7 @@ module.exports = exports = argv => {
294
318
  })
295
319
  break
296
320
  default:
297
- console.log('pagehandler: unrecognized action #{action}')
321
+ console.log(`pagehandler: unrecognized action ${action}`)
298
322
  }
299
323
  }
300
324
 
@@ -392,43 +416,46 @@ module.exports = exports = argv => {
392
416
  }
393
417
  return collaborativeLinks
394
418
  }
419
+
395
420
  fs.readdir(argv.db, (e, files) => {
396
421
  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,
422
+ const doSitemap = async file => {
423
+ return new Promise(resolve => {
424
+ itself.get(file, (e, page, status) => {
425
+ if (file.match(/^\./)) return resolve(null)
426
+ if (e || status === 404) {
427
+ console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status)
428
+ return resolve(null) // Ignore errors in the pagehandler get.
429
+ }
430
+ let pageLinksMap
431
+ try {
432
+ pageLinksMap = page.story.reduce(extractPageLinks, new Map())
433
+ } catch (err) {
434
+ console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message)
435
+ pageLinksMap = []
436
+ }
437
+ //
438
+ const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined
439
+
440
+ resolve({
441
+ slug: file,
442
+ title: page.title,
443
+ date: editDate(page.journal),
444
+ synopsis: synopsis(page),
445
+ links: pageLinks,
446
+ })
422
447
  })
423
448
  })
424
449
  }
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
- })
450
+
451
+ Promise.all(files.map(doSitemap))
452
+ .then(sitemap => {
453
+ cb(
454
+ null,
455
+ sitemap.filter(item => item != null),
456
+ )
457
+ })
458
+ .catch(e => cb(e))
432
459
  })
433
460
  }
434
461
 
package/lib/plugins.js CHANGED
@@ -8,12 +8,11 @@
8
8
 
9
9
  // support server-side plugins
10
10
 
11
- const fs = require('node:fs')
12
- const glob = require('glob')
13
- const { pathToFileURL } = require('node:url')
14
- // forward = require './forward'
11
+ import fs from 'node:fs'
12
+ import { pathToFileURL } from 'node:url'
13
+ // import forward from './forward.cjs'; // Uncomment if needed and adjust import style if it's not a default export
15
14
 
16
- module.exports = exports = argv => {
15
+ export default argv => {
17
16
  // NOTE: plugins are now in their own package directories alongside this one...
18
17
  // Plugins are in directories of the form wiki-package-*
19
18
  // those with a server component will have a server directory
@@ -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 => {
@@ -40,14 +39,12 @@ module.exports = exports = argv => {
40
39
  }
41
40
 
42
41
  const startServers = params => {
43
- // emitter = new events.EventEmitter()
44
- // forward.init params.app, emitter
45
- // params.emitter = emitter
46
- glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, plugins) => {
47
- plugins.forEach(plugin => {
42
+ const dependencies = params.packageJson.dependencies
43
+ Object.keys(dependencies)
44
+ .filter(depend => depend.startsWith('wiki-plugin'))
45
+ .forEach(plugin => {
48
46
  startServer(params, plugin)
49
47
  })
50
- })
51
48
  }
52
49
 
53
50
  return { startServers }
package/lib/random_id.js CHANGED
@@ -10,6 +10,4 @@
10
10
  // Simple random hex generator, takes an optional number of
11
11
  // chars that defaults to 16 and returns a random id.
12
12
 
13
- const random_id = (chars = 16) => [...Array(chars)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
14
-
15
- module.exports = random_id.random_id = random_id
13
+ export default (chars = 16) => [...Array(chars)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
package/lib/search.js CHANGED
@@ -8,15 +8,15 @@
8
8
 
9
9
  // **search.js**
10
10
 
11
- const fs = require('node:fs')
12
- const path = require('node:path')
13
- const events = require('node:events')
14
- const url = require('node:url')
15
- const writeFileAtomic = require('write-file-atomic')
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
13
+ import events from 'node:events'
14
+ import url from 'node:url'
15
+ import writeFileAtomic from 'write-file-atomic'
16
16
 
17
- const miniSearch = require('minisearch')
17
+ import miniSearch from 'minisearch'
18
18
 
19
- module.exports = exports = argv => {
19
+ export default argv => {
20
20
  const wikiName = new URL(argv.url).hostname
21
21
  let siteIndex = []
22
22
  const queue = []
@@ -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
@@ -13,11 +13,11 @@
13
13
  // allow the server to run read-only.
14
14
 
15
15
  // #### Requires ####
16
- const fs = require('node:fs')
16
+ import fs from 'node:fs'
17
17
 
18
18
  // Export a function that generates security handler
19
19
  // when called with options object.
20
- module.exports = exports = (log, loga, argv) => {
20
+ export default (log, loga, argv) => {
21
21
  const security = {}
22
22
 
23
23
  // #### Private utility methods. ####
@@ -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
@@ -84,7 +84,16 @@ module.exports = exports = (log, loga, argv) => {
84
84
  }
85
85
  // Wiki server admin
86
86
  security.isAdmin = () => {
87
- return false
87
+ // nobody is admin - unless legacy support, and test
88
+ if (argv.security_legacy) {
89
+ if (argv.test) {
90
+ return true
91
+ } else {
92
+ return false
93
+ }
94
+ } else {
95
+ return false
96
+ }
88
97
  }
89
98
  security.defineRoutes = (app, cors, updateOwner) => {
90
99
  // default security does not have any routes
package/lib/server.js CHANGED
@@ -19,44 +19,72 @@
19
19
  // npm install
20
20
 
21
21
  // Standard lib
22
- const fs = require('fs')
23
- const path = require('path')
24
- const http = require('http')
25
- const url = require('url')
26
- const { pipeline } = require('node:stream/promises')
22
+ import fs from 'fs'
23
+ import path from 'path'
24
+ import url from 'url'
25
+ import { pipeline } from 'node:stream/promises'
27
26
 
28
27
  // From npm
29
- const express = require('express')
30
- const hbs = require('express-hbs')
31
- const glob = require('glob')
32
- const async = require('async')
33
- const f = require('flates')
28
+ import express from 'express'
29
+ import hbs from 'express-hbs'
30
+ import f from 'flates'
34
31
 
35
- const createDOMPurify = require('dompurify')
36
- const { JSDOM } = require('jsdom')
32
+ import createDOMPurify from 'dompurify'
33
+ import { JSDOM } from 'jsdom'
37
34
 
38
35
  const window = new JSDOM('').window
39
36
  const DOMPurify = createDOMPurify(window)
40
37
 
41
- // node-fetch is now ESM only
42
- const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args))
38
+ // Using native fetch API (available in Node.js 18+)
43
39
 
44
40
  // Express 4 middleware
45
- const logger = require('morgan')
46
- const cookieParser = require('cookie-parser')
47
- const methodOverride = require('method-override')
48
- // session = require 'express-session'
49
- const sessions = require('client-sessions')
50
- const bodyParser = require('body-parser')
51
- const errorHandler = require('errorhandler')
41
+ import logger from 'morgan'
42
+ import cookieParser from 'cookie-parser'
43
+ import methodOverride from 'method-override'
44
+ // session = require('express-session') // This one was commented out — uncomment if used
45
+ import sessions from 'client-sessions'
46
+ import bodyParser from 'body-parser'
47
+ import errorHandler from 'errorhandler'
52
48
 
53
49
  // Local files
54
- const random = require('./random_id')
55
- const defargs = require('./defaultargs')
56
- const resolveClient = require('wiki-client/lib/resolve')
57
- const pluginsFactory = require('./plugins')
58
- const sitemapFactory = require('./sitemap')
59
- const searchFactory = require('./search')
50
+ // Make sure these files are ESM modules or compatible
51
+ // If they are CommonJS, you will need to dynamically import them (see below)
52
+ import defargs from './defaultargs.js'
53
+ import resolveClient from 'wiki-client/lib/resolve.js'
54
+ import pluginsFactory from './plugins.js'
55
+ import sitemapFactory from './sitemap.js'
56
+ import searchFactory from './search.js'
57
+ import { warn } from 'node:console'
58
+ import { createRequire } from 'module'
59
+ const require = createRequire(import.meta.url)
60
+
61
+ // Use import to load package.json from the main application's working directory
62
+ //const { default: packageJson } = await import('wiki/package.json', { with: { type: 'json' } })
63
+ const wikiPackageImport = async () => {
64
+ let done = false
65
+ return new Promise(resolve => {
66
+ import('wiki/package.json', { with: { type: 'json' } })
67
+ .then(imported => {
68
+ done = true
69
+ resolve(imported.default)
70
+ })
71
+ .catch(e => {
72
+ return e
73
+ })
74
+ .then(async () => {
75
+ if (done) return
76
+ const packageJsonPath = path.join(process.cwd(), 'package.json')
77
+ const packageJsonUrl = url.pathToFileURL(packageJsonPath).href
78
+ import(packageJsonUrl, { with: { type: 'json' } })
79
+ .then(imported => {
80
+ resolve(imported.default)
81
+ })
82
+ .catch(e => console.error('problems importing package', e))
83
+ })
84
+ })
85
+ }
86
+
87
+ const packageJson = await wikiPackageImport()
60
88
 
61
89
  const render = page => {
62
90
  return (
@@ -93,7 +121,7 @@ const render = page => {
93
121
  )
94
122
  }
95
123
  // Set export objects for node and coffee to a function that generates a sfw server.
96
- module.exports = exports = argv => {
124
+ export default async argv => {
97
125
  // Create the main application object, app.
98
126
  const app = express()
99
127
 
@@ -107,6 +135,8 @@ module.exports = exports = argv => {
107
135
 
108
136
  app.startOpts = argv
109
137
 
138
+ const wikiName = new URL(argv.url).hostname
139
+
110
140
  const log = (...stuff) => {
111
141
  if (argv.debug) console.log(stuff)
112
142
  }
@@ -128,18 +158,23 @@ module.exports = exports = argv => {
128
158
  }
129
159
  next()
130
160
  }
161
+
131
162
  let pagehandler, sitemaphandler, searchhandler, securityhandler
132
- // Require the database adapter and initialize it with options.
133
- app.pagehandler = pagehandler = require(argv.database.type)(argv)
134
163
 
135
- // Require the sitemap adapter and initialize it with options.
164
+ // Dynamically import database adapter (since the module name is dynamic)
165
+ const dbModule = await import(argv.database.type)
166
+ app.pagehandler = pagehandler = dbModule.default(argv)
167
+
168
+ // Initialize sitemap handler
136
169
  app.sitemaphandler = sitemaphandler = sitemapFactory(argv)
137
170
 
138
- // Require the site indexer and initialize it with options
171
+ // Initialize search handler
139
172
  app.searchhandler = searchhandler = searchFactory(argv)
140
173
 
141
- // Require the security adapter and initialize it with options.
142
- app.securityhandler = securityhandler = require(argv.security_type)(log, loga, argv)
174
+ // Dynamically import security adapter (also dynamic)
175
+ console.log('security_type', argv.security_type)
176
+ const securityModule = await import(argv.security_type)
177
+ app.securityhandler = securityhandler = securityModule.default(log, loga, argv)
143
178
 
144
179
  // If the site is owned, owner will contain the name of the owner
145
180
  let owner = ''
@@ -163,7 +198,7 @@ module.exports = exports = argv => {
163
198
 
164
199
  const remoteGet = (remote, slug, cb) => {
165
200
  // 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()
201
+ const remoteURL = new URL(`http://${remote}/${slug}.json`).toString()
167
202
  // set a two second timeout
168
203
  fetch(remoteURL, { signal: AbortSignal.timeout(2000) })
169
204
  .then(res => {
@@ -222,7 +257,7 @@ module.exports = exports = argv => {
222
257
  // use logger, at least in development, probably needs a param to configure (or turn off).
223
258
  // use stream to direct to somewhere other than stdout.
224
259
  logger.token('vhost', (req, res) => {
225
- return colorString(req.hostname)
260
+ return colorString(wikiName)
226
261
  })
227
262
  app.use(logger(':vhost :method :url :status :res[content-length] - :response-time ms'))
228
263
  app.use(cookieParser())
@@ -271,13 +306,13 @@ module.exports = exports = argv => {
271
306
  app.use('/assets', cors, express.static(argv.assets))
272
307
 
273
308
  // 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))
309
+ Object.keys(packageJson.dependencies)
310
+ .filter(depend => depend.startsWith('wiki-plugin'))
311
+ .forEach(plugin => {
312
+ const clientPath = path.join(path.dirname(require.resolve(`${plugin}/package`)), 'client')
313
+ const pluginPath = '/plugins/' + plugin.slice(12)
314
+ app.use(pluginPath, cors, express.static(clientPath, staticPathOptions))
279
315
  })
280
- })
281
316
 
282
317
  // Add static routes to the security client.
283
318
  if (argv.security != './security') {
@@ -387,32 +422,28 @@ module.exports = exports = argv => {
387
422
  app.get('/system/factories.json', (req, res) => {
388
423
  res.status(200)
389
424
  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
425
 
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)
412
- }
413
- res.end(JSON.stringify(factories))
426
+ const factories = []
427
+
428
+ const getPackageFactory = plugin => {
429
+ return new Promise(resolve => {
430
+ import(`${plugin}/factory.json`, { with: { type: 'json' } })
431
+ .then(({ default: factory }) => {
432
+ resolve(factories.push(factory))
433
+ })
434
+ .catch(() => {
435
+ resolve()
436
+ })
414
437
  })
415
- })
438
+ }
439
+
440
+ Promise.all(
441
+ Object.keys(packageJson.dependencies)
442
+ .filter(depend => depend.startsWith('wiki-plugin'))
443
+ .map(plugin => {
444
+ return getPackageFactory(plugin)
445
+ }),
446
+ ).then(() => res.end(JSON.stringify(factories)))
416
447
  })
417
448
 
418
449
  // ###### Json Routes ######
@@ -444,7 +475,7 @@ module.exports = exports = argv => {
444
475
  // If themes doesn't exist send 404 and let the client
445
476
  // deal with it.
446
477
  app.get(/^\/theme\/(\w+\.\w+)$/, cors, (req, res) => {
447
- res.sendFile(path.join(argv.status, 'theme', req.params[0]), e => {
478
+ res.sendFile(path.join(argv.status, 'theme', req.params[0]), { dotfiles: 'allow' }, e => {
448
479
  if (e) {
449
480
  // swallow the error if the theme does not exist...
450
481
  if (req.path === '/theme/style.css') {
@@ -462,9 +493,9 @@ module.exports = exports = argv => {
462
493
  const favLoc = path.join(argv.status, 'favicon.png')
463
494
  const defaultFavLoc = path.join(argv.root, 'default-data', 'status', 'favicon.png')
464
495
  app.get('/favicon.png', cors, (req, res) => {
465
- fs.exists(favLoc, exists => {
466
- if (exists) {
467
- res.sendFile(favLoc)
496
+ fs.access(favLoc, fs.constants.F_OK, err => {
497
+ if (!err) {
498
+ res.sendFile(favLoc, { dotfiles: 'allow' })
468
499
  } else {
469
500
  res.sendFile(defaultFavLoc)
470
501
  }
@@ -484,9 +515,9 @@ module.exports = exports = argv => {
484
515
  // save it.
485
516
  app.post('/favicon.png', authorized, (req, res) => {
486
517
  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) {
518
+ const buf = Buffer.from(favicon, 'base64')
519
+ fs.access(argv.status, fs.constants.F_OK, err => {
520
+ if (!err) {
490
521
  fs.writeFile(favLoc, buf, e => {
491
522
  if (e) {
492
523
  return res.e(e)
@@ -508,7 +539,7 @@ module.exports = exports = argv => {
508
539
 
509
540
  // Redirect remote favicons to the server they are needed from.
510
541
  app.get(/^\/remote\/([a-zA-Z0-9:.-]+\/favicon.png)$/, (req, res) => {
511
- const remotefav = 'http://#{req.params[0]}'
542
+ const remotefav = `http://${req.params[0]}`
512
543
  res.redirect(remotefav)
513
544
  })
514
545
 
@@ -519,38 +550,40 @@ module.exports = exports = argv => {
519
550
  // negative space outward pointing arrows nicely indicates that items can be removed
520
551
  const recyclerFavLoc = path.join(argv.root, 'default-data', 'status', 'recycler.png')
521
552
  app.get('/recycler/favicon.png', authorized, (req, res) => {
522
- res.sendFile(recyclerFavLoc)
553
+ res.sendFile(recyclerFavLoc, { dotfiles: 'allow' })
523
554
  })
524
555
 
525
556
  // Send an array of pages currently in the recycler via json
526
557
  app.get('/recycler/system/slugs.json', authorized, (req, res) => {
527
558
  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,
559
+ if (e) {
560
+ return res.e(e)
561
+ }
562
+ const doRecyclermap = async file => {
563
+ return new Promise(resolve => {
564
+ const recycleFile = 'recycler/' + file
565
+ pagehandler.get(recycleFile, (e, page, status) => {
566
+ if (e || status === 404) {
567
+ console.log('Problem building recycler map:', file, 'e: ', e)
568
+ // this will leave an undefined/empty item in the array, which we will filter out later
569
+ return resolve(null)
570
+ }
571
+ resolve({
572
+ slug: file,
573
+ title: page.title,
574
+ })
539
575
  })
540
576
  })
541
577
  }
542
578
 
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
- })
579
+ Promise.all(files.map(doRecyclermap))
580
+ .then(recyclermap => {
581
+ recyclermap = recyclermap.filter(el => !!el)
582
+ res.send(recyclermap)
583
+ })
584
+ .catch(error => {
585
+ res.e(error)
586
+ })
554
587
  })
555
588
  })
556
589
 
@@ -590,21 +623,21 @@ module.exports = exports = argv => {
590
623
 
591
624
  // Returns a list of installed plugins. (does this get called anymore!)
592
625
  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
- })
626
+ try {
627
+ const pluginNames = Object.keys(require.main.require('./package').dependencies)
628
+ .filter(depend => depend.startsWith('wiki-plugin'))
629
+ .map(name => name.slice(12))
630
+ res.send(pluginNames)
631
+ } catch (e) {
632
+ return res.e(e)
633
+ }
601
634
  })
602
635
  //{
603
636
  const sitemapLoc = path.join(argv.status, 'sitemap.json')
604
637
  app.get('/system/sitemap.json', cors, (req, res) => {
605
- fs.exists(sitemapLoc, exists => {
606
- if (exists) {
607
- res.sendFile(sitemapLoc)
638
+ fs.access(sitemapLoc, fs.constants.F_OK, err => {
639
+ if (!err) {
640
+ res.sendFile(sitemapLoc, { dotfiles: 'allow' })
608
641
  } else {
609
642
  // only createSitemap if we are not already creating one
610
643
  if (!sitemaphandler.isWorking()) {
@@ -612,7 +645,7 @@ module.exports = exports = argv => {
612
645
  }
613
646
  // wait for the sitemap file to be written, before sending
614
647
  sitemaphandler.once('finished', () => {
615
- res.sendFile(sitemapLoc)
648
+ res.sendFile(sitemapLoc, { dotfiles: 'allow' })
616
649
  })
617
650
  }
618
651
  })
@@ -620,15 +653,15 @@ module.exports = exports = argv => {
620
653
 
621
654
  const xmlSitemapLoc = path.join(argv.status, 'sitemap.xml')
622
655
  app.get('/sitemap.xml', cors, (req, res) => {
623
- fs.exists(sitemapLoc, exists => {
624
- if (exists) {
625
- res.sendFile(xmlSitemapLoc)
656
+ fs.access(sitemapLoc, fs.constants.F_OK, err => {
657
+ if (!err) {
658
+ res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' })
626
659
  } else {
627
660
  if (!sitemaphandler.isWorking()) {
628
661
  sitemaphandler.createSitemap(pagehandler)
629
662
  }
630
663
  sitemaphandler.once('finished', () => {
631
- res.sendFile(xmlSitemapLoc)
664
+ res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' })
632
665
  })
633
666
  }
634
667
  })
@@ -636,16 +669,16 @@ module.exports = exports = argv => {
636
669
 
637
670
  const searchIndexLoc = path.join(argv.status, 'site-index.json')
638
671
  app.get('/system/site-index.json', cors, (req, res) => {
639
- fs.exists(searchIndexLoc, exists => {
640
- if (exists) {
641
- res.sendFile(searchIndexLoc)
672
+ fs.access(searchIndexLoc, fs.constants.F_OK, err => {
673
+ if (!err) {
674
+ res.sendFile(searchIndexLoc, { dotfiles: 'allow' })
642
675
  } else {
643
676
  // only create index if we are not already creating one
644
677
  if (!searchhandler.isWorking()) {
645
678
  searchhandler.createIndex(pagehandler)
646
679
  }
647
680
  searchhandler.once('indexed', () => {
648
- res.sendFile(searchIndexLoc)
681
+ res.sendFile(searchIndexLoc, { dotfiles: 'allow' })
649
682
  })
650
683
  }
651
684
  })
@@ -656,28 +689,29 @@ module.exports = exports = argv => {
656
689
  if (e) {
657
690
  return res.e(e)
658
691
  }
659
- async.map(
660
- sitemap,
661
- (stub, done) => {
692
+ const pagePromises = sitemap.map(stub => {
693
+ return new Promise((resolve, reject) => {
662
694
  pagehandler.get(stub.slug, (error, page) => {
663
- if (e) {
664
- return done(e)
695
+ if (error) {
696
+ return reject(error)
665
697
  }
666
- done(null, { slug: stub.slug, page })
698
+ resolve({ slug: stub.slug, page })
667
699
  })
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
- )
700
+ })
701
+ })
702
+
703
+ Promise.all(pagePromises)
704
+ .then(pages => {
705
+ const pageExport = pages.reduce((dict, combined) => {
706
+ dict[combined.slug] = combined.page
707
+ return dict
708
+ }, {})
709
+ // TODO: this fails for a very large site
710
+ res.json(pageExport)
711
+ })
712
+ .catch(error => {
713
+ res.e(error)
714
+ })
681
715
  })
682
716
  })
683
717
 
@@ -691,33 +725,59 @@ module.exports = exports = argv => {
691
725
  }
692
726
 
693
727
  app.get('/system/version.json', admin, (req, res) => {
694
- const versions = {}
695
- const wikiModule = module.parent.parent.parent
696
- versions[wikiModule.require('./package').name] = wikiModule.require('./package').version
697
- versions[wikiModule.require('wiki-server/package').name] = wikiModule.require('wiki-server/package').version
698
- versions[wikiModule.require('wiki-client/package').name] = wikiModule.require('wiki-client/package').version
699
- versions['security'] = {}
700
- versions['plugins'] = {}
701
-
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
728
+ const getPackageVersion = packageName => {
729
+ return new Promise(resolve => {
730
+ try {
731
+ // Use import to load package.json from the main application's working directory
732
+ import(`${packageName}/package.json`, { with: { type: 'json' } }).then(({ default: packageJson }) => {
733
+ resolve({ [packageName]: packageJson.version })
734
+ })
735
+ } catch (error) {
736
+ console.error(`Error reading package for ${packageName}:`, error)
737
+ resolve({ [packageName]: 'unknown' })
712
738
  }
713
- res.json(versions)
714
739
  })
740
+ }
741
+
742
+ const versions = {}
743
+
744
+ const security = () => {
745
+ return new Promise(resolve => {
746
+ Promise.all(
747
+ Object.keys(packageJson.dependencies)
748
+ .filter(depend => depend.startsWith('wiki-security'))
749
+ .map(key => {
750
+ return getPackageVersion(key)
751
+ }),
752
+ ).then(values => {
753
+ resolve({ security: values.reduce((acc, cV) => Object.assign(acc, cV), {}) })
754
+ })
755
+ })
756
+ }
757
+
758
+ const plugins = () => {
759
+ return new Promise(resolve => {
760
+ Promise.all(
761
+ Object.keys(packageJson.dependencies)
762
+ .filter(depend => depend.startsWith('wiki-plugin'))
763
+ .map(key => {
764
+ return getPackageVersion(key)
765
+ }),
766
+ ).then(values => {
767
+ resolve({ plugins: values.reduce((acc, cV) => Object.assign(acc, cV), {}) })
768
+ })
769
+ })
770
+ }
771
+
772
+ Promise.all([getPackageVersion('wiki-server'), getPackageVersion('wiki-client'), security(), plugins()]).then(v => {
773
+ Object.assign(versions, { [packageJson.name]: packageJson.version }, ...v)
774
+ res.json(versions)
715
775
  })
716
776
  })
717
777
 
718
778
  // ##### Proxy routes #####
719
779
 
720
- app.get('/proxy/*', authorized, (req, res) => {
780
+ app.get('/proxy/*splat', authorized, (req, res) => {
721
781
  const pathParts = req.originalUrl.split('/')
722
782
  const remoteHost = pathParts[2]
723
783
  pathParts.splice(0, 3)
@@ -731,7 +791,7 @@ module.exports = exports = argv => {
731
791
  requestURL.endsWith('.jpg') ||
732
792
  pathParts[0] === 'plugin'
733
793
  ) {
734
- fetch(requestURL, { timeout: 2000 })
794
+ fetch(requestURL, { signal: AbortSignal.timeout(2000) })
735
795
  .then(async fetchRes => {
736
796
  if (fetchRes.ok) {
737
797
  res.set('content-type', fetchRes.headers.get('content-type'))
@@ -929,7 +989,7 @@ module.exports = exports = argv => {
929
989
  // ### Plugins ###
930
990
  // Should replace most WebSocketServers below.
931
991
  const plugins = pluginsFactory(argv)
932
- plugins.startServers({ argv, app })
992
+ plugins.startServers({ argv, app, packageJson })
933
993
  // ### Sitemap ###
934
994
  // create sitemap at start-up
935
995
  sitemaphandler.createSitemap(pagehandler)
package/lib/sitemap.js CHANGED
@@ -7,14 +7,13 @@
7
7
  */
8
8
 
9
9
  // **sitemap.coffee**
10
+ import fs from 'node:fs'
11
+ import path from 'node:path'
12
+ import events from 'node:events'
13
+ import writeFileAtomic from 'write-file-atomic'
14
+ import xml2js from 'xml2js'
10
15
 
11
- const fs = require('fs')
12
- const path = require('path')
13
- const events = require('events')
14
- const writeFileAtomic = require('write-file-atomic')
15
- const xml2js = require('xml2js')
16
-
17
- const synopsis = require('wiki-client/lib/synopsis')
16
+ import synopsis from 'wiki-client/lib/synopsis.js' // Add .js if needed
18
17
 
19
18
  const asSlug = name =>
20
19
  name
@@ -22,7 +21,7 @@ const asSlug = name =>
22
21
  .replace(/[^A-Za-z0-9-]/g, '')
23
22
  .toLowerCase()
24
23
 
25
- module.exports = exports = argv => {
24
+ export default argv => {
26
25
  const wikiName = new URL(argv.url).hostname
27
26
 
28
27
  let sitemap = []
@@ -119,8 +118,8 @@ module.exports = exports = argv => {
119
118
  }
120
119
 
121
120
  const sitemapSave = (sitemap, cb) => {
122
- fs.exists(argv.status, exists => {
123
- if (exists) {
121
+ fs.access(argv.status, fs.constants.F_OK, err => {
122
+ if (!err) {
124
123
  writeFileAtomic(sitemapLoc, JSON.stringify(sitemap), e => {
125
124
  if (e) return cb(e)
126
125
  cb()
@@ -136,8 +135,8 @@ module.exports = exports = argv => {
136
135
  }
137
136
 
138
137
  const sitemapRestore = cb => {
139
- fs.exists(sitemapLoc, exists => {
140
- if (exists) {
138
+ fs.access(sitemapLoc, fs.constants.F_OK, err => {
139
+ if (!err) {
141
140
  fs.readFile(sitemapLoc, (err, data) => {
142
141
  if (err) return cb(err)
143
142
  try {
@@ -172,8 +171,8 @@ module.exports = exports = argv => {
172
171
  const xmlmap = { urlset: { $: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' }, url: xmlmapPages } }
173
172
  const builder = new xml2js.Builder()
174
173
  const xml = builder.buildObject(xmlmap)
175
- fs.exists(argv.status, exists => {
176
- if (exists) {
174
+ fs.access(argv.status, fs.constants.F_OK, err => {
175
+ if (!err) {
177
176
  writeFileAtomic(xmlSitemapLoc, xml, e => {
178
177
  if (e) return cb(e)
179
178
  cb()
package/package.json CHANGED
@@ -1,7 +1,8 @@
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.10",
5
+ "type": "module",
5
6
  "author": {
6
7
  "name": "Ward Cunningham",
7
8
  "email": "ward@c2.com",
@@ -29,40 +30,39 @@
29
30
  "Andrew Shell <andrew@andrewshell.org>",
30
31
  "jon r <jon@allmende.io>"
31
32
  ],
33
+ "main": "index.js",
32
34
  "dependencies": {
33
- "async": "^3.2.4",
34
- "body-parser": "^2.2.0",
35
+ "body-parser": "^2.2.1",
35
36
  "client-sessions": "^0.8.0",
36
37
  "coffeescript": "^2.5.0",
37
38
  "cookie-parser": "^1.4.4",
38
- "dompurify": "^3.1.0",
39
+ "dompurify": "^3.3.0",
39
40
  "errorhandler": "^1.5.1",
40
- "express": "^4.21.2",
41
+ "express": "^5.1.0",
41
42
  "express-hbs": "^2.5.0",
42
43
  "flates": "0.0.5",
43
- "glob": "^7.2.3",
44
- "jsdom": "^26.0.0",
44
+ "jsdom": "^27.2.0",
45
45
  "method-override": "^3.0.0",
46
- "minisearch": "^7.1.0",
47
- "morgan": "^1.9.1",
48
- "node-fetch": "^3.3.2",
49
- "write-file-atomic": "^6.0.0",
46
+ "minisearch": "^7.2.0",
47
+ "morgan": "^1.10.1",
48
+ "write-file-atomic": "^7.0.0",
50
49
  "xml2js": "^0.6.2"
51
50
  },
52
51
  "scripts": {
53
52
  "prettier:format": "prettier --write './**/*.js'",
54
53
  "prettier:check": "prettier --check ./**/*.js",
55
- "test": "node --test",
54
+ "test": "cd test; node --test",
55
+ "watch": "cd test; node --test --watch",
56
56
  "update-authors": "node scripts/update-authors.js"
57
57
  },
58
58
  "devDependencies": {
59
- "@eslint/js": "^9.29.0",
60
- "eslint": "^9.29.0",
61
- "globals": "^16.1.0",
59
+ "@eslint/js": "^9.39.1",
60
+ "eslint": "^9.39.1",
61
+ "globals": "^16.5.0",
62
62
  "grunt-git-authors": "^3.2.0",
63
- "prettier": "^3.5.3",
64
- "supertest": "^7.0.0",
65
- "wiki-client": "^0.31.2",
63
+ "prettier": "^3.7.3",
64
+ "supertest": "^7.1.4",
65
+ "wiki-client": "^0.31.6",
66
66
  "wiki-plugin-activity": "0.7",
67
67
  "wiki-plugin-video": "^0.4"
68
68
  },