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.
- package/.github/workflows/node.js.yml +2 -2
- package/lib/page.coffee +26 -10
- package/lib/search.coffee +32 -31
- package/lib/server.coffee +16 -7
- package/lib/sitemap.coffee +4 -7
- package/package.json +14 -15
- package/test/sitemap.coffee +1 -0
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
|
|
17
17
|
strategy:
|
|
18
18
|
matrix:
|
|
19
|
-
node-version: [
|
|
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@
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 +=
|
|
155
|
-
when '
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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')
|
package/lib/sitemap.coffee
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
31
|
+
"express-hbs": "^2.5.0",
|
|
32
32
|
"flates": "0.0.5",
|
|
33
33
|
"glob": "^7.2.3",
|
|
34
|
-
"
|
|
34
|
+
"jsdom": "^24.0.0",
|
|
35
35
|
"method-override": "^3.0.0",
|
|
36
|
-
"minisearch": "^6.0
|
|
37
|
-
"mkdirp": "^0.5.6",
|
|
36
|
+
"minisearch": "^6.3.0",
|
|
38
37
|
"morgan": "^1.9.1",
|
|
39
|
-
"node-fetch": "^
|
|
38
|
+
"node-fetch": "^3.3.2",
|
|
40
39
|
"write-file-atomic": "^5.0.0",
|
|
41
|
-
"xml2js": "^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.
|
|
47
|
+
"grunt-contrib-watch": "^1.1.0",
|
|
49
48
|
"grunt-git-authors": "^3.2.0",
|
|
50
|
-
"grunt-mocha-test": "^0.13.
|
|
51
|
-
"mocha": "^10.
|
|
49
|
+
"grunt-mocha-test": "^0.13.3",
|
|
50
|
+
"mocha": "^10.4.0",
|
|
52
51
|
"should": "^13.2.3",
|
|
53
|
-
"supertest": "^6.3.
|
|
54
|
-
"wiki-client": "^0.
|
|
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.
|
|
55
|
+
"wiki-plugin-video": "^0.3.1"
|
|
57
56
|
},
|
|
58
57
|
"engines": {
|
|
59
|
-
"node": ">=
|
|
58
|
+
"node": ">=18.x"
|
|
60
59
|
},
|
|
61
60
|
"testling": {
|
|
62
61
|
"harness": "mocha"
|
package/test/sitemap.coffee
CHANGED
|
@@ -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')
|