wiki-plugin-pod 1.0.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +61 -36
- package/api/handlers.js +2 -2
- package/api/openapi.json +6 -18
- package/client/pod.js +9 -8
- package/client/pod.js.map +4 -4
- package/package.json +29 -4
- package/pages/about-pod-plugin +103 -5
- package/src/client/pod.js +132 -286
- package/src/pod/commands.js +49 -102
- package/src/pod/kinship.js +39 -0
- package/src/pod/roll.js +35 -66
- package/src/pod/vocabulary.js +20 -0
- package/server/package.json +0 -3
- package/server/server.js +0 -47
package/src/pod/commands.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This module is the single source for what a pod item's text may say. The
|
|
4
4
|
// client imports it to parse item text; the plugin's API declaration names it so
|
|
5
|
-
// the farm reads the identical table. There is no second copy to drift
|
|
6
|
-
// the same move that put the plugin's thinking in a module and left only the
|
|
7
|
-
// serving in its server.
|
|
5
|
+
// the farm reads the identical table. There is no second copy to drift.
|
|
8
6
|
//
|
|
9
|
-
// The
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// The PARSER is not here, and neither is an import of it. The FARM loads this
|
|
8
|
+
// module server-side to read the table, so it must be data and nothing else —
|
|
9
|
+
// an import of @fortyfoxes/wiki-dsl here would need that package installed on
|
|
10
|
+
// every farm, and a vocabulary that fails to load is dropped from the farm's
|
|
11
|
+
// merged document entirely. The parser is bound in ./vocabulary.js, which only
|
|
12
|
+
// the browser and the tests load.
|
|
12
13
|
//
|
|
13
14
|
// A DSL is a superset of an API, never a mirror of one — so every command
|
|
14
15
|
// declares which KIND it is, and only some kinds cross to HTTP:
|
|
@@ -33,11 +34,16 @@ export const FALLBACK = ['sisters']
|
|
|
33
34
|
|
|
34
35
|
export const COMMANDS = {
|
|
35
36
|
// ---- parameter: these become values of the `kinds` parameter -------------
|
|
37
|
+
//
|
|
38
|
+
// `heading` is what the drawn group is called. `{site}` is the site the item
|
|
39
|
+
// is on, because that is what a pod is relative to and the one thing a reader
|
|
40
|
+
// landing cold cannot work out.
|
|
36
41
|
SISTERS: {
|
|
37
42
|
kind: 'parameter',
|
|
38
43
|
parameter: 'kinds',
|
|
39
44
|
value: 'sisters',
|
|
40
45
|
label: 'Sisters',
|
|
46
|
+
heading: 'Sisters of {site}',
|
|
41
47
|
description: 'sibling sites sharing the parent domain',
|
|
42
48
|
},
|
|
43
49
|
PARENT: {
|
|
@@ -45,6 +51,7 @@ export const COMMANDS = {
|
|
|
45
51
|
parameter: 'kinds',
|
|
46
52
|
value: 'parent',
|
|
47
53
|
label: 'Parent',
|
|
54
|
+
heading: 'Parent of {site}',
|
|
48
55
|
description: 'the parent-domain site itself',
|
|
49
56
|
},
|
|
50
57
|
CHILDREN: {
|
|
@@ -52,6 +59,7 @@ export const COMMANDS = {
|
|
|
52
59
|
parameter: 'kinds',
|
|
53
60
|
value: 'children',
|
|
54
61
|
label: 'Children',
|
|
62
|
+
heading: 'Children of {site}',
|
|
55
63
|
description: 'direct sub-domains of this site',
|
|
56
64
|
},
|
|
57
65
|
DESCENDANTS: {
|
|
@@ -59,6 +67,7 @@ export const COMMANDS = {
|
|
|
59
67
|
parameter: 'kinds',
|
|
60
68
|
value: 'descendants',
|
|
61
69
|
label: 'Descendants',
|
|
70
|
+
heading: 'Descendants of {site}',
|
|
62
71
|
description: 'all sub-domains, any depth',
|
|
63
72
|
},
|
|
64
73
|
FARM: {
|
|
@@ -66,6 +75,7 @@ export const COMMANDS = {
|
|
|
66
75
|
parameter: 'kinds',
|
|
67
76
|
value: 'farm',
|
|
68
77
|
label: 'Farm',
|
|
78
|
+
heading: 'The rest of the farm',
|
|
69
79
|
description: 'every wiki in the farm',
|
|
70
80
|
},
|
|
71
81
|
|
|
@@ -81,6 +91,7 @@ export const COMMANDS = {
|
|
|
81
91
|
kind: 'client-data',
|
|
82
92
|
value: 'neighbourhood',
|
|
83
93
|
label: 'Neighbourhood',
|
|
94
|
+
heading: 'Your neighbourhood',
|
|
84
95
|
description: 'the sites currently in your neighborhood',
|
|
85
96
|
why: 'reads the neighbourhood the browser has already assembled, which no server can see',
|
|
86
97
|
},
|
|
@@ -89,113 +100,49 @@ export const COMMANDS = {
|
|
|
89
100
|
kind: 'client-data',
|
|
90
101
|
value: 'snapshot',
|
|
91
102
|
label: 'Snapshot',
|
|
103
|
+
heading: 'Your neighbourhood',
|
|
92
104
|
description: 'freeze the current neighborhood',
|
|
93
105
|
why: 'same browser-only source as NEIGHBOURHOOD',
|
|
94
106
|
},
|
|
95
|
-
TWIN: {
|
|
96
|
-
kind: 'client-data',
|
|
97
|
-
description: "show only members holding a page with this page's slug",
|
|
98
|
-
why: "matches against each neighbour's own sitemap, fetched from the browser",
|
|
99
|
-
},
|
|
100
|
-
WATCH: {
|
|
101
|
-
kind: 'client-data',
|
|
102
|
-
description: 'stricter TWIN — only members whose copy is a fork of this page',
|
|
103
|
-
why: "reads each candidate's page journal, one fetch per neighbour, from the browser",
|
|
104
|
-
},
|
|
105
107
|
|
|
106
108
|
// ---- presentation: never leaves the client -------------------------------
|
|
107
|
-
|
|
109
|
+
//
|
|
110
|
+
// FORCE is a behaviour switch rather than a drawing choice, but `presentation`
|
|
111
|
+
// is the closest of the kinds the farm's vocabulary check accepts, and the
|
|
112
|
+
// thing that matters is true of it: it never crosses to a server.
|
|
113
|
+
FORCE: {
|
|
108
114
|
kind: 'presentation',
|
|
109
|
-
|
|
115
|
+
argument: 'yes|no',
|
|
116
|
+
default: false,
|
|
117
|
+
description: 'register the gathered sites as neighbours when the page loads',
|
|
110
118
|
},
|
|
111
119
|
TITLE: {
|
|
112
120
|
kind: 'presentation',
|
|
113
121
|
argument: 'yes|no',
|
|
114
122
|
default: true,
|
|
115
|
-
description: 'whether
|
|
123
|
+
description: 'whether each group shows its heading',
|
|
116
124
|
},
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Values a trailing TITLE argument may take to mean "no". */
|
|
120
|
-
const FALSEY = ['no', 'false', 'off', '0', 'hide', 'none']
|
|
121
|
-
|
|
122
|
-
/** Follow an alias to the command it spells. */
|
|
123
|
-
export const resolve = name => {
|
|
124
|
-
const entry = COMMANDS[name]
|
|
125
|
-
return entry && entry.kind === 'alias' ? COMMANDS[entry.alias] : entry
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** The canonical name a command resolves to, for reporting. */
|
|
129
|
-
export const canonical = name => {
|
|
130
|
-
const entry = COMMANDS[name]
|
|
131
|
-
return entry && entry.kind === 'alias' ? entry.alias : name
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* The keyword a line begins with, normalised. UPPERCASE is canonical but input
|
|
136
|
-
* case is forgiven, and one trailing colon is optional — never required.
|
|
137
|
-
*/
|
|
138
|
-
export const keywordOf = line =>
|
|
139
|
-
String(line || '')
|
|
140
|
-
.trim()
|
|
141
|
-
.split(/\s+/)[0]
|
|
142
|
-
.replace(/:$/, '')
|
|
143
|
-
.toUpperCase()
|
|
144
|
-
|
|
145
|
-
/** Every value a parameter may take, in declaration order. The API's enum. */
|
|
146
|
-
export const valuesFor = parameter =>
|
|
147
|
-
Object.values(COMMANDS)
|
|
148
|
-
.filter(c => c.kind === 'parameter' && c.parameter === parameter && c.value)
|
|
149
|
-
.map(c => c.value)
|
|
150
125
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const take = name => {
|
|
174
|
-
const entry = resolve(name)
|
|
175
|
-
if (!entry) return
|
|
176
|
-
if (entry.kind === 'macro') return entry.expands.forEach(take)
|
|
177
|
-
if (!entry.value) return
|
|
178
|
-
if (!kinds.includes(entry.value)) kinds.push(entry.value)
|
|
179
|
-
}
|
|
180
|
-
for (const line of String(text || '').split('\n')) {
|
|
181
|
-
if (!line.trim()) continue
|
|
182
|
-
take(keywordOf(line))
|
|
183
|
-
}
|
|
184
|
-
return kinds.length ? kinds : [...fallback]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** Whether a bare command appears on a line of its own. */
|
|
188
|
-
export const hasCommand = (text, name) =>
|
|
189
|
-
String(text || '')
|
|
190
|
-
.split('\n')
|
|
191
|
-
.some(line => canonical(keywordOf(line)) === name)
|
|
192
|
-
|
|
193
|
-
/** TITLE yes|no — shown unless explicitly turned off. */
|
|
194
|
-
export const parseTitle = text => {
|
|
195
|
-
for (const raw of String(text || '').split('\n')) {
|
|
196
|
-
const parts = raw.trim().split(/\s+/)
|
|
197
|
-
if (canonical(keywordOf(raw)) !== 'TITLE') continue
|
|
198
|
-
return !FALSEY.includes((parts[1] || '').toLowerCase().replace(/:$/, ''))
|
|
199
|
-
}
|
|
200
|
-
return COMMANDS.TITLE.default
|
|
126
|
+
// ---- retired: kept so pages that say them still parse ---------------------
|
|
127
|
+
//
|
|
128
|
+
// A word removed from this table is a word the parser reports as a mistake, so
|
|
129
|
+
// retiring one by deleting it would put a false "not a pod command" warning on
|
|
130
|
+
// every page that still says it. These stay, as presentation (the kind the
|
|
131
|
+
// farm's check allows for a word that never crosses), carrying what became of
|
|
132
|
+
// them.
|
|
133
|
+
ROSTER: {
|
|
134
|
+
kind: 'presentation',
|
|
135
|
+
retired: 'the default — every pod is drawn as a roster of flags',
|
|
136
|
+
description: 'no longer needed: rosters are how a pod is drawn',
|
|
137
|
+
},
|
|
138
|
+
TWIN: {
|
|
139
|
+
kind: 'presentation',
|
|
140
|
+
retired: 'wiki-plugin-twin',
|
|
141
|
+
description: 'moved: which pod members hold a page with this page’s slug',
|
|
142
|
+
},
|
|
143
|
+
WATCH: {
|
|
144
|
+
kind: 'presentation',
|
|
145
|
+
retired: 'wiki-plugin-twin',
|
|
146
|
+
description: 'moved: which pod members have forked this page',
|
|
147
|
+
},
|
|
201
148
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// kinship.js — who stands as pod to whom, as arithmetic on names.
|
|
2
|
+
//
|
|
3
|
+
// Kinship needs no disk: given the names a farm holds, every pod is a predicate
|
|
4
|
+
// over strings. So selection lives here, imported by both readers — the browser,
|
|
5
|
+
// which asks the farm only what sites exist, and the API handler, which answers
|
|
6
|
+
// server-side for an agent that cannot run our client. That is why the plugin's
|
|
7
|
+
// server shrank to one directory read and no opinions.
|
|
8
|
+
|
|
9
|
+
/** The domain a site hangs from — '' for a farm root such as `localhost`. */
|
|
10
|
+
export const parentOf = origin => origin.split('.').slice(1).join('.')
|
|
11
|
+
|
|
12
|
+
/** Every pod, as predicates over site names, relative to the site asking. */
|
|
13
|
+
export const selectorsFor = origin => {
|
|
14
|
+
const parentDomain = parentOf(origin)
|
|
15
|
+
const domainOf = name => name.split('.').slice(1).join('.')
|
|
16
|
+
return {
|
|
17
|
+
sisters: name => name !== origin && domainOf(name) === parentDomain,
|
|
18
|
+
parent: name => parentDomain !== '' && name === parentDomain,
|
|
19
|
+
children: name => domainOf(name) === origin,
|
|
20
|
+
descendants: name => name !== origin && name.endsWith('.' + origin),
|
|
21
|
+
farm: name => name !== origin,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sort site names into the requested pods — names in, names out. Unknown kinds
|
|
27
|
+
* are dropped rather than refused, the same forgiveness the item text shows.
|
|
28
|
+
* Every pod is a neighbourhood, so the asker is never among its own results.
|
|
29
|
+
*/
|
|
30
|
+
export const gather = (sites, { origin, kinds }) => {
|
|
31
|
+
const selectors = selectorsFor(origin)
|
|
32
|
+
const wanted = (Array.isArray(kinds) ? kinds : String(kinds || 'sisters').split(','))
|
|
33
|
+
.map(k => String(k).trim())
|
|
34
|
+
.filter(k => selectors[k])
|
|
35
|
+
|
|
36
|
+
const groups = {}
|
|
37
|
+
for (const kind of wanted) groups[kind] = sites.filter(selectors[kind]).sort((a, b) => a.localeCompare(b))
|
|
38
|
+
return groups
|
|
39
|
+
}
|
package/src/pod/roll.js
CHANGED
|
@@ -1,81 +1,50 @@
|
|
|
1
|
-
// roll.js —
|
|
1
|
+
// roll.js — the one thing kinship cannot work out for itself: what is here.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Node-only, never imported by the client. The selectors, the sorting and the
|
|
4
|
+
// page counts have all left: selection moved to ../pod/kinship.js, and the
|
|
5
|
+
// counts went away because the client never read them — it takes them from each
|
|
6
|
+
// neighbour's own sitemap, so the farm was reading a directory per site on
|
|
7
|
+
// every render for nothing.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// A directory is a wiki when it holds pages — the same test wiki-plugin-farm
|
|
10
|
+
// and wiki-plugin-hitchhiker use. This plugin used to test for
|
|
11
|
+
// `status/sitemap.json`, which is written on first view rather than on
|
|
12
|
+
// creation, so a farm's newest sites were invisible to it alone.
|
|
11
13
|
|
|
12
14
|
import fs from 'node:fs/promises'
|
|
13
15
|
import path from 'node:path'
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
export const selectorsFor = ({ origin, parentDomain }) => {
|
|
17
|
-
const domainOf = name => name.split('.').slice(1).join('.')
|
|
18
|
-
return {
|
|
19
|
-
sisters: name => name !== origin && domainOf(name) === parentDomain,
|
|
20
|
-
parent: name => parentDomain !== '' && name === parentDomain,
|
|
21
|
-
children: name => domainOf(name) === origin,
|
|
22
|
-
descendants: name => name !== origin && name.endsWith('.' + origin),
|
|
23
|
-
farm: name => name !== origin,
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** A directory is a wiki when it carries a readable sitemap. */
|
|
28
|
-
const isWiki = (farmRoot, name) =>
|
|
29
|
-
fs
|
|
30
|
-
.access(path.join(farmRoot, name, 'status', 'sitemap.json'), fs.constants.R_OK)
|
|
31
|
-
.then(() => true)
|
|
32
|
-
.catch(() => false)
|
|
33
|
-
|
|
34
|
-
const pageCount = (farmRoot, name) =>
|
|
35
|
-
fs
|
|
36
|
-
.readdir(path.join(farmRoot, name, 'pages'))
|
|
37
|
-
.then(entries => entries.length)
|
|
38
|
-
.catch(() => 0)
|
|
39
|
-
|
|
40
|
-
/** The parent domain of a site name — '' for a farm root such as `localhost`. */
|
|
41
|
-
export const parentOf = origin => origin.split('.').slice(1).join('.')
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Gather the requested pods of the site being asked.
|
|
45
|
-
*
|
|
46
|
-
* `origin` and `farmRoot` are the context: the answer genuinely depends on WHERE
|
|
47
|
-
* the question was asked, which is why they are arguments rather than something
|
|
48
|
-
* this module reaches for. Unknown kinds are dropped rather than refused, and
|
|
49
|
-
* the default is sisters — the same forgiveness the item text shows.
|
|
50
|
-
*/
|
|
51
|
-
export const roll = async ({ kinds, origin, farmRoot }) => {
|
|
52
|
-
const parentDomain = parentOf(origin)
|
|
53
|
-
const selectors = selectorsFor({ origin, parentDomain })
|
|
54
|
-
|
|
55
|
-
const wanted = (Array.isArray(kinds) ? kinds : String(kinds || 'sisters').split(','))
|
|
56
|
-
.map(k => String(k).trim())
|
|
57
|
-
.filter(k => selectors[k])
|
|
17
|
+
import { gather, parentOf } from './kinship.js'
|
|
58
18
|
|
|
19
|
+
/** The sites of this farm — every directory holding pages. */
|
|
20
|
+
export const sitesOf = async farmRoot => {
|
|
59
21
|
let entries
|
|
60
22
|
try {
|
|
61
23
|
entries = await fs.readdir(farmRoot, { withFileTypes: true })
|
|
62
24
|
} catch {
|
|
63
|
-
return
|
|
25
|
+
return []
|
|
64
26
|
}
|
|
65
|
-
|
|
66
27
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name)
|
|
67
|
-
const
|
|
68
|
-
|
|
28
|
+
const holdsPages = await Promise.all(
|
|
29
|
+
dirs.map(name =>
|
|
30
|
+
fs
|
|
31
|
+
.access(path.join(farmRoot, name, 'pages'), fs.constants.R_OK)
|
|
32
|
+
.then(() => true)
|
|
33
|
+
.catch(() => false),
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
return dirs.filter((_, i) => holdsPages[i]).sort()
|
|
37
|
+
}
|
|
69
38
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
39
|
+
/**
|
|
40
|
+
* Gather the requested pods of the site being asked. `origin` and `farmRoot` are
|
|
41
|
+
* context: the answer depends on WHERE the question was put, which is why they
|
|
42
|
+
* are arguments rather than something this module reaches for.
|
|
43
|
+
*/
|
|
44
|
+
export const roll = async ({ kinds, origin, farmRoot }) => ({
|
|
45
|
+
origin,
|
|
46
|
+
parentDomain: parentOf(origin),
|
|
47
|
+
groups: gather(await sitesOf(farmRoot), { origin, kinds }),
|
|
48
|
+
})
|
|
79
49
|
|
|
80
|
-
|
|
81
|
-
}
|
|
50
|
+
export { parentOf }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// vocabulary.js — the table, read.
|
|
2
|
+
//
|
|
3
|
+
// ./commands.js is data with no imports, because the farm loads it server-side
|
|
4
|
+
// and a vocabulary that cannot be imported is dropped from the farm's document.
|
|
5
|
+
// Everything that READS that table lives here instead: @fortyfoxes/wiki-dsl,
|
|
6
|
+
// bundled into the client at build time and shared with every other plugin whose
|
|
7
|
+
// item text is a small language.
|
|
8
|
+
|
|
9
|
+
import { dsl } from '@fortyfoxes/wiki-dsl'
|
|
10
|
+
|
|
11
|
+
import { COMMANDS, FALLBACK } from './commands.js'
|
|
12
|
+
|
|
13
|
+
export const { parseKinds, hasCommand, argumentOf, problems, valuesFor, fieldByValue, canonical, resolve, suggest } =
|
|
14
|
+
dsl(COMMANDS, { fallback: FALLBACK })
|
|
15
|
+
|
|
16
|
+
/** What a retired command became, or undefined for a live one. */
|
|
17
|
+
export const retirementOf = word => COMMANDS[word]?.retired
|
|
18
|
+
|
|
19
|
+
export { COMMANDS, FALLBACK }
|
|
20
|
+
export { VOCABULARY_VERSION, ITEM_TYPE } from './commands.js'
|
package/server/package.json
DELETED
package/server/server.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// pod plugin — server-side component.
|
|
2
|
-
//
|
|
3
|
-
// A thin caller now. Everything it used to know how to do lives in
|
|
4
|
-
// ../src/pod/roll.js as plain functions; this file only turns a request into
|
|
5
|
-
// arguments and a result into JSON. The route it serves is unchanged, so the
|
|
6
|
-
// shipped client keeps calling the address it always called — the migration
|
|
7
|
-
// adds a second way in without disturbing the first.
|
|
8
|
-
//
|
|
9
|
-
// Started on the `running-serv` event with { argv, app }.
|
|
10
|
-
//
|
|
11
|
-
// NOTE: authored as CommonJS on purpose. wiki-server loads a plugin's
|
|
12
|
-
// server/server.js with require() (older releases) or import() (newer); CJS
|
|
13
|
-
// is the only format that works under BOTH, on every Node version. An ESM
|
|
14
|
-
// server.js throws ERR_REQUIRE_ESM on the require() loader (Node < 22.12),
|
|
15
|
-
// and the wiki swallows that error, so the plugin's routes silently vanish.
|
|
16
|
-
// The sibling server/package.json ({"type":"commonjs"}) makes Node treat this
|
|
17
|
-
// file as CJS even though the plugin's root package.json is "type":"module",
|
|
18
|
-
// so the rest of the plugin (src/, tests, build) can stay ESM.
|
|
19
|
-
//
|
|
20
|
-
// The shared module is ESM, and is reached by dynamic import() rather than
|
|
21
|
-
// require(). Dynamic import works from CommonJS on every Node that runs the
|
|
22
|
-
// wiki, while require() of an ES module only works from Node 22.12 — so this is
|
|
23
|
-
// the one form that needs no version to be true.
|
|
24
|
-
|
|
25
|
-
const path = require('node:path')
|
|
26
|
-
|
|
27
|
-
const startServer = ({ argv, app }) => {
|
|
28
|
-
// argv.status = {farmRoot}/{thisDomain}/status
|
|
29
|
-
const farmRoot = path.dirname(path.dirname(argv.status))
|
|
30
|
-
const origin = path.basename(path.dirname(argv.status)) // e.g. demoscene.localhost
|
|
31
|
-
|
|
32
|
-
// Imported once, awaited per request. Kept as the promise so a slow or failed
|
|
33
|
-
// load cannot delay the route being registered.
|
|
34
|
-
const thinking = import('../src/pod/roll.js')
|
|
35
|
-
|
|
36
|
-
app.get('/plugin/pod/roll', async (req, res) => {
|
|
37
|
-
try {
|
|
38
|
-
const { roll } = await thinking
|
|
39
|
-
res.json(await roll({ kinds: req.query.kinds || 'sisters', origin, farmRoot }))
|
|
40
|
-
} catch (e) {
|
|
41
|
-
console.log('pod plugin: roll failed —', e?.stack || e)
|
|
42
|
-
res.status(500).json({ error: e.message })
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = { startServer }
|