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 +7 -3
- package/lib/defaultargs.js +13 -7
- package/lib/forward.js +1 -2
- package/lib/page.js +131 -104
- package/lib/plugins.js +10 -13
- package/lib/random_id.js +1 -3
- package/lib/search.js +18 -14
- package/lib/security.js +14 -5
- package/lib/server.js +218 -158
- package/lib/sitemap.js +13 -14
- package/package.json +18 -18
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
|
-
|
|
6
|
-
|
|
5
|
+
// import('coffeescript')
|
|
6
|
+
// import('coffeescript/register.js')
|
|
7
7
|
|
|
8
|
-
|
|
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
|
package/lib/defaultargs.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 ||=
|
|
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
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
|
|
56
|
+
const packageJson = await wikiPackageImport()
|
|
19
57
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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(
|
|
108
|
+
console.log(`ERROR: moving problem page ${loc} to recycler`, err)
|
|
55
109
|
} else {
|
|
56
|
-
console.log(
|
|
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(
|
|
120
|
+
console.log(`ERROR: moving problem page ${loc} to recycler`, err)
|
|
67
121
|
} else {
|
|
68
|
-
console.log(
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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.
|
|
276
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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.
|
|
28
|
-
if (
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
17
|
+
import miniSearch from 'minisearch'
|
|
18
18
|
|
|
19
|
-
|
|
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.
|
|
89
|
-
if (
|
|
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.
|
|
112
|
-
if (
|
|
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
|
-
|
|
173
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
45
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// session = require
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
171
|
+
// Initialize search handler
|
|
139
172
|
app.searchhandler = searchhandler = searchFactory(argv)
|
|
140
173
|
|
|
141
|
-
//
|
|
142
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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.
|
|
466
|
-
if (
|
|
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 =
|
|
488
|
-
fs.
|
|
489
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
res.
|
|
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.
|
|
606
|
-
if (
|
|
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.
|
|
624
|
-
if (
|
|
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.
|
|
640
|
-
if (
|
|
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
|
-
|
|
660
|
-
|
|
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 (
|
|
664
|
-
return
|
|
695
|
+
if (error) {
|
|
696
|
+
return reject(error)
|
|
665
697
|
}
|
|
666
|
-
|
|
698
|
+
resolve({ slug: stub.slug, page })
|
|
667
699
|
})
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
123
|
-
if (
|
|
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.
|
|
140
|
-
if (
|
|
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.
|
|
176
|
-
if (
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
39
|
+
"dompurify": "^3.3.0",
|
|
39
40
|
"errorhandler": "^1.5.1",
|
|
40
|
-
"express": "^
|
|
41
|
+
"express": "^5.1.0",
|
|
41
42
|
"express-hbs": "^2.5.0",
|
|
42
43
|
"flates": "0.0.5",
|
|
43
|
-
"
|
|
44
|
-
"jsdom": "^26.0.0",
|
|
44
|
+
"jsdom": "^27.2.0",
|
|
45
45
|
"method-override": "^3.0.0",
|
|
46
|
-
"minisearch": "^7.
|
|
47
|
-
"morgan": "^1.
|
|
48
|
-
"
|
|
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.
|
|
60
|
-
"eslint": "^9.
|
|
61
|
-
"globals": "^16.
|
|
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.
|
|
64
|
-
"supertest": "^7.
|
|
65
|
-
"wiki-client": "^0.31.
|
|
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
|
},
|