wiki-server 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,11 +16,11 @@ jobs:
16
16
 
17
17
  strategy:
18
18
  matrix:
19
- node-version: [16.x, 18.x]
19
+ node-version: [18.x, 20.x, 21.x]
20
20
  # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v3
23
+ - uses: actions/checkout@v4
24
24
  - name: Use Node.js ${{ matrix.node-version }}
25
25
  uses: actions/setup-node@v3
26
26
  with:
package/lib/page.coffee CHANGED
@@ -15,7 +15,6 @@ path = require 'path'
15
15
  events = require 'events'
16
16
  glob = require 'glob'
17
17
 
18
- mkdirp = require 'mkdirp'
19
18
  async = require 'async'
20
19
 
21
20
  random_id = require './random_id'
@@ -31,7 +30,7 @@ module.exports = exports = (argv) ->
31
30
 
32
31
  wikiName = new URL(argv.url).hostname
33
32
 
34
- mkdirp argv.db, (e) ->
33
+ fs.mkdir argv.db, { recursive: true }, (e) ->
35
34
  if e then throw e
36
35
 
37
36
  #### Private utility methods. ####
@@ -53,7 +52,7 @@ module.exports = exports = (argv) ->
53
52
  console.log "ERROR: problem page #{loc} moved to recycler"
54
53
  )
55
54
  else
56
- mkdirp(path.dirname(recyclePage), (err) ->
55
+ fs.mkdir(path.dirname(recyclePage), { recursive: true }, (err) ->
57
56
  if err
58
57
  console.log "ERROR: creating recycler", err
59
58
  else
@@ -87,6 +86,25 @@ module.exports = exports = (argv) ->
87
86
  # Reads and writes are async, but serially queued to avoid race conditions.
88
87
  queue = []
89
88
 
89
+ tryDefaults = (file, cb) ->
90
+ lastDefault = (cb) ->
91
+ defloc = path.join(argv.root, 'default-data', 'pages', file)
92
+ fs.exists defloc, (exists) ->
93
+ if exists
94
+ cb(defloc)
95
+ else
96
+ cb(null)
97
+ if argv.defaults
98
+ defloc = path.join(argv.data, '..', argv.defaults, 'pages', file)
99
+ console.log 'firstDefault', defloc
100
+ fs.exists defloc, (exists) ->
101
+ if exists
102
+ cb(defloc)
103
+ else
104
+ lastDefault(cb)
105
+ else
106
+ lastDefault(cb)
107
+
90
108
  # Main file io function, when called without page it reads,
91
109
  # when called with page it writes.
92
110
  fileio = (action, file, page, cb) ->
@@ -115,7 +133,7 @@ module.exports = exports = (argv) ->
115
133
  cb(err)
116
134
  )
117
135
  else
118
- mkdirp(path.dirname(recycleLoc), (err) ->
136
+ fs.mkdir(path.dirname(recycleLoc), { recursive: true }, (err) ->
119
137
  if err then cb(err)
120
138
  fs.rename(loc, recycleLoc, (err) ->
121
139
  cb(err)
@@ -160,7 +178,7 @@ module.exports = exports = (argv) ->
160
178
  cb(err)
161
179
  )
162
180
  else
163
- mkdirp(path.dirname(recycleLoc), (err) ->
181
+ fs.mkdir(path.dirname(recycleLoc), { recursive: true }, (err) ->
164
182
  if err then cb(err)
165
183
  copyFile(loc, recycleLoc, (err) ->
166
184
  cb(err)
@@ -175,12 +193,10 @@ module.exports = exports = (argv) ->
175
193
  if exists
176
194
  load_parse(loc, cb, {plugin: undefined})
177
195
  else
178
- defloc = path.join(argv.root, 'default-data', 'pages', file)
179
- fs.exists(defloc, (exists) ->
180
- if exists
196
+ tryDefaults(file, (defloc) ->
197
+ if defloc
181
198
  load_parse(defloc, cb)
182
199
  else
183
-
184
200
  glob "wiki-plugin-*/pages", {cwd: argv.packageDir}, (e, plugins) ->
185
201
  if e then return cb(e)
186
202
 
@@ -217,7 +233,7 @@ module.exports = exports = (argv) ->
217
233
  cb(err)
218
234
  )
219
235
  else
220
- mkdirp(path.dirname(loc), (err) ->
236
+ fs.mkdir(path.dirname(loc), { recursive: true }, (err) ->
221
237
  if err then cb(err)
222
238
  fs.writeFile(loc, page, (err) ->
223
239
  if err
package/lib/search.coffee CHANGED
@@ -11,8 +11,8 @@
11
11
  fs = require 'fs'
12
12
  path = require 'path'
13
13
  events = require 'events'
14
+ url = require 'node:url'
14
15
  writeFileAtomic = require 'write-file-atomic'
15
- mkdirp = require 'mkdirp'
16
16
 
17
17
  miniSearch = require 'minisearch'
18
18
 
@@ -46,9 +46,6 @@ module.exports = exports = (argv) ->
46
46
 
47
47
  searchPageUpdate = (slug, page, cb) ->
48
48
  # to update we have to remove the page first, and then readd it
49
- timeLabel = "SITE INDEX update #{slug} - #{wikiName}"
50
- console.time timeLabel
51
-
52
49
  try
53
50
  pageText = page.story.reduce( extractPageText, '')
54
51
  catch err
@@ -66,37 +63,29 @@ module.exports = exports = (argv) ->
66
63
  'title': page.title
67
64
  'content': pageText
68
65
  }
69
- console.timeEnd timeLabel
70
66
  cb()
71
67
 
72
68
  searchPageRemove = (slug, cb) ->
73
69
  # remove page from index
74
70
  timeLabel = "SITE INDEX page remove #{slug} - #{wikiName}"
75
- console.time timeLabel
76
71
  try
77
72
  siteIndex.discard slug
78
73
  catch err
79
74
  # swallow error, if the page was not in index
80
75
  console.log "removing #{slug} from index #{wikiName} failed", err unless err.message.includes('not in the index')
81
- console.timeEnd timeLabel
82
76
  cb()
83
77
 
84
78
  searchSave = (siteIndex, cb) ->
85
79
  # save index to file
86
- timeLabel = "SITE INDEX #{wikiName} : Saved"
87
- console.time timeLabel
88
-
89
80
  fs.exists argv.status, (exists) ->
90
81
  if exists
91
82
  writeFileAtomic siteIndexLoc, JSON.stringify(siteIndex), (e) ->
92
- console.timeEnd timeLabel
93
83
  return cb(e) if e
94
84
  touch indexUpdateFlag, (err) ->
95
85
  cb()
96
86
  else
97
- mkdirp argv.status, ->
87
+ fs.mkdir argv.status, { recursive: true }, ->
98
88
  writeFileAtomic siteIndexLoc, JSON.stringify(siteIndex), (e) ->
99
- console.timeEnd timeLabel
100
89
  return cb(e) if e
101
90
  touch indexUpdateFlag, (err) ->
102
91
  cb()
@@ -104,8 +93,6 @@ module.exports = exports = (argv) ->
104
93
 
105
94
  searchRestore = (cb) ->
106
95
  # restore index, or create if it doesn't already exist
107
- timeLabel = "SITE INDEX #{wikiName} : Restored"
108
- console.time timeLabel
109
96
  fs.exists siteIndexLoc, (exists) ->
110
97
  if exists
111
98
  fs.readFile(siteIndexLoc, (err, data) ->
@@ -115,7 +102,6 @@ module.exports = exports = (argv) ->
115
102
  fields: ['title', 'content']
116
103
  catch e
117
104
  return cb(e)
118
- console.timeEnd timeLabel
119
105
  process.nextTick( ->
120
106
  serial(queue.shift())))
121
107
 
@@ -145,24 +131,39 @@ module.exports = exports = (argv) ->
145
131
  console.log "SITE INDEX *** save failed: " + e if e
146
132
  itself.stop()
147
133
 
134
+ extractItemText = (text) ->
135
+ return text.replace(/\[([^\]]*?)\][\[\(].*?[\]\)]/g, " $1 ")
136
+ .replace(/\[{2}|\[(?:[\S]+)|\]{1,2}/g, ' ')
137
+ .replace(/\n/g, ' ')
138
+ .replace(/<style.*?<\/style>/g, ' ')
139
+ .replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ')
140
+ .replace(/<(?:[^>])+>/g, ' ')
141
+ .replace(/(https?.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, (match) ->
142
+ myUrl = url.parse(match)
143
+ return myUrl.hostname + myUrl.pathname)
144
+ .replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ')
145
+ .replace /[\p{White_Space}\n\t]+/gu, ' '
146
+
147
+
148
148
  extractPageText = (pageText, currentItem, currentIndex, array) ->
149
149
  # console.log('extractPageText', pageText, currentItem, currentIndex, array)
150
150
  try
151
151
  if currentItem.text?
152
152
  switch currentItem.type
153
- when 'paragraph'
154
- pageText += ' ' + currentItem.text.replace /\[{2}|\[(?:[\S]+)|\]{1,2}/g, ''
155
- when 'markdown'
156
- # really need to extract text from the markdown, but for now just remove link brackets, urls...
157
- pageText += ' ' + currentItem.text.replace /\[{2}|\[(?:[\S]+)|\]{1,2}|\\n/g, ' '
158
- when 'html'
159
- pageText += ' ' + currentItem.text.replace /<[^\>]*>?/g, ''
160
- else
161
- if currentItem.text?
162
- for line in currentItem.text.split /\r\n?|\n/
163
- pageText += ' ' + line.replace /\[{2}|\[(?:[\S]+)|\]{1,2}/g, '' unless line.match /^[A-Z]+[ ].*/
153
+ when 'paragraph', 'markdown', 'html', 'reference', 'image', 'pagefold', 'math', 'mathjax', 'code'
154
+ pageText += extractItemText currentItem.text
155
+ when 'audio', 'video', 'frame'
156
+ pageText += extractItemText(currentItem.text.split(/\r\n?|\n/)
157
+ .map((line) ->
158
+ firstWord = line.split(/\p{White_Space}/u)[0]
159
+ if firstWord.startsWith('http') or firstWord.toUpperCase() is firstWord or firstWord.startsWith('//')
160
+ # line is markup
161
+ return ''
162
+ else
163
+ return line
164
+ ).join(' '))
164
165
  catch err
165
- throw new Error("Error extracting text from #{currentIndex}, #{err}")
166
+ throw new Error("Error extracting text from #{currentIndex}, #{JSON.stringify(currentItem)} #{err}, #{err.stack}")
166
167
  pageText
167
168
 
168
169
 
@@ -192,8 +193,8 @@ module.exports = exports = (argv) ->
192
193
  # we save the pagehandler, so we can recreate the site index if it is removed
193
194
  searchPageHandler = pagehandler if !searchPageHandler?
194
195
 
195
- timeLabel = "SITE INDEX #{wikiName} : Created"
196
- console.time timeLabel
196
+ #timeLabel = "SITE INDEX #{wikiName} : Created"
197
+ #console.time timeLabel
197
198
 
198
199
  pagehandler.slugs (e, slugs) ->
199
200
  if e
@@ -227,7 +228,7 @@ module.exports = exports = (argv) ->
227
228
 
228
229
  Promise.all(indexPromises)
229
230
  .then () ->
230
- console.timeEnd timeLabel
231
+ # console.timeEnd timeLabel
231
232
  process.nextTick ( ->
232
233
  serial(queue.shift()))
233
234
 
package/lib/server.coffee CHANGED
@@ -26,14 +26,20 @@ url = require 'url'
26
26
  { pipeline } = require 'node:stream/promises'
27
27
 
28
28
  # From npm
29
- mkdirp = require 'mkdirp'
30
29
  express = require 'express'
31
30
  hbs = require 'express-hbs'
32
31
  glob = require 'glob'
33
32
  async = require 'async'
34
33
  f = require('flates')
35
- sanitize = require '@mapbox/sanitize-caja'
36
- fetch = require 'node-fetch'
34
+
35
+ createDOMPurify = require('dompurify')
36
+ { JSDOM } = require('jsdom')
37
+
38
+ window = new JSDOM('').window
39
+ DOMPurify = createDOMPurify(window)
40
+
41
+ # node-fetch is now ESM only
42
+ fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
37
43
 
38
44
  # Express 4 middleware
39
45
  logger = require 'morgan'
@@ -71,7 +77,7 @@ render = (page) ->
71
77
  f.p(resolveClient.resolveLinks(story.text or story.caption or 'uploaded image'))
72
78
  else if story.type is 'html'
73
79
  f.div {class: "item html"},
74
- f.p(resolveClient.resolveLinks(story.text or '', sanitize))
80
+ f.p(resolveClient.resolveLinks(story.text or '', DOMPurify.sanitize))
75
81
  else f.div {class: "item"}, f.p(resolveClient.resolveLinks(story.text or ''))
76
82
  ).join('\n')
77
83
 
@@ -145,7 +151,7 @@ module.exports = exports = (argv) ->
145
151
  # assume http, as we know no better at this point and we need to specify a protocol.
146
152
  remoteURL = new URL("http://#{remote}/#{slug}.json").toString()
147
153
  # set a two second timeout
148
- fetch(remoteURL, {timeout: 2000})
154
+ fetch(remoteURL, {signal: AbortSignal.timeout(2000)})
149
155
  .then (res) ->
150
156
  if res.ok
151
157
  return res
@@ -182,7 +188,10 @@ module.exports = exports = (argv) ->
182
188
 
183
189
  # use logger, at least in development, probably needs a param to configure (or turn off).
184
190
  # use stream to direct to somewhere other than stdout.
185
- app.use(logger('tiny'))
191
+ wikiName = new URL(argv.url).hostname
192
+ logger.token('vhost', () ->
193
+ return wikiName)
194
+ app.use(logger(':vhost :method :url :status :res[content-length] - :response-time ms'))
186
195
  app.use(cookieParser())
187
196
  app.use(bodyParser.json({ limit: argv.uploadLimit}))
188
197
  app.use(bodyParser.urlencoded({ extended: true, limit: argv.uploadLimit}))
@@ -425,7 +434,7 @@ module.exports = exports = (argv) ->
425
434
  res.send('Favicon Saved')
426
435
 
427
436
  else
428
- mkdirp argv.status, ->
437
+ fs.mkdir argv.status, { recursive: true }, ->
429
438
  fs.writeFile favLoc, buf, (e) ->
430
439
  if e then return res.e e
431
440
  res.send('Favicon Saved')
@@ -12,11 +12,8 @@ fs = require 'fs'
12
12
  path = require 'path'
13
13
  events = require 'events'
14
14
  writeFileAtomic = require 'write-file-atomic'
15
- _ = require 'lodash'
16
15
  xml2js = require 'xml2js'
17
16
 
18
- mkdirp = require 'mkdirp'
19
-
20
17
  synopsis = require 'wiki-client/lib/synopsis'
21
18
 
22
19
  asSlug = (name) ->
@@ -100,7 +97,7 @@ module.exports = exports = (argv) ->
100
97
  idx = slugs.indexOf(file)
101
98
 
102
99
  if ~idx
103
- _.pullAt(sitemap, idx)
100
+ sitemap.splice(idx,1)
104
101
 
105
102
  cb()
106
103
 
@@ -111,7 +108,7 @@ module.exports = exports = (argv) ->
111
108
  return cb(e) if e
112
109
  cb()
113
110
  else
114
- mkdirp argv.status, ->
111
+ fs.mkdir argv.status, { recursive: true }, ->
115
112
  writeFileAtomic sitemapLoc, JSON.stringify(sitemap), (e) ->
116
113
  return cb(e) if e
117
114
  cb()
@@ -134,7 +131,7 @@ module.exports = exports = (argv) ->
134
131
 
135
132
  xmlSitemapSave = (sitemap, cb) ->
136
133
  xmlmap = []
137
- _.each sitemap, (page) ->
134
+ sitemap.forEach (page) ->
138
135
  result = {}
139
136
  result["loc"] = argv.url + "/" + page.slug + ".html"
140
137
  if page.date?
@@ -151,7 +148,7 @@ module.exports = exports = (argv) ->
151
148
  return cb(e) if e
152
149
  cb()
153
150
  else
154
- mkdirp argv.status, ->
151
+ fs.mkdir argv.status, { recursive: true }, ->
155
152
  writeFileAtomic xmlSitemapLoc, xml, (e) ->
156
153
  return cb(e) if e
157
154
  cb()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wiki-server",
3
3
  "description": "A Federated Wiki Server",
4
- "version": "0.23.0",
4
+ "version": "0.24.1",
5
5
  "author": {
6
6
  "name": "Ward Cunningham",
7
7
  "email": "ward@c2.com",
@@ -20,43 +20,42 @@
20
20
  }
21
21
  ],
22
22
  "dependencies": {
23
- "@mapbox/sanitize-caja": "^0.1.4",
24
23
  "async": "^3.2.4",
25
24
  "body-parser": "^1.20.2",
26
25
  "client-sessions": "^0.8.0",
27
26
  "coffeescript": "^2.5.0",
28
27
  "cookie-parser": "^1.4.4",
28
+ "dompurify": "^3.1.0",
29
29
  "errorhandler": "^1.5.1",
30
30
  "express": "^4.17.1",
31
- "express-hbs": "^2.3.3",
31
+ "express-hbs": "^2.5.0",
32
32
  "flates": "0.0.5",
33
33
  "glob": "^7.2.3",
34
- "lodash": "^4.17.19",
34
+ "jsdom": "^24.0.0",
35
35
  "method-override": "^3.0.0",
36
- "minisearch": "^6.0.1",
37
- "mkdirp": "^0.5.6",
36
+ "minisearch": "^6.3.0",
38
37
  "morgan": "^1.9.1",
39
- "node-fetch": "^2.6.8",
38
+ "node-fetch": "^3.3.2",
40
39
  "write-file-atomic": "^5.0.0",
41
- "xml2js": "^0.5.0"
40
+ "xml2js": "^0.6.2"
42
41
  },
43
42
  "scripts": {
44
43
  "test": "grunt mochaTest"
45
44
  },
46
45
  "devDependencies": {
47
46
  "grunt": "^1.6.1",
48
- "grunt-contrib-watch": "^1.0.0",
47
+ "grunt-contrib-watch": "^1.1.0",
49
48
  "grunt-git-authors": "^3.2.0",
50
- "grunt-mocha-test": "^0.13.2",
51
- "mocha": "^10.2.0",
49
+ "grunt-mocha-test": "^0.13.3",
50
+ "mocha": "^10.4.0",
52
51
  "should": "^13.2.3",
53
- "supertest": "^6.3.3",
54
- "wiki-client": "^0.27.1",
52
+ "supertest": "^6.3.4",
53
+ "wiki-client": "^0.29.1",
55
54
  "wiki-plugin-activity": "^0.5.0",
56
- "wiki-plugin-video": "^0.3.0"
55
+ "wiki-plugin-video": "^0.3.1"
57
56
  },
58
57
  "engines": {
59
- "node": ">=12.x"
58
+ "node": ">=18.x"
60
59
  },
61
60
  "testling": {
62
61
  "harness": "mocha"
@@ -19,6 +19,7 @@ describe 'sitemap', ->
19
19
 
20
20
 
21
21
  request = request('http://localhost:55556')
22
+ fs.mkdirSync path.join('/tmp', 'sfwtests', testid, 'pages'), {recursive: true}
22
23
 
23
24
  # location of the sitemap
24
25
  sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json')