new plugins api (breaking)

Massimo Melina committed Feb 8, 2022 at 17:14 UTC 36f91f43907b42ee5e2466afdab174fae3e360d3
5 files changed +66 -56
README.md
+23 -13
@@ -187,7 +187,19 @@ but nothing is preventing a single plug-in from doing both tasks.
187 You should find some examples within your installation.
188
189 A plug-in must have a `plugin.js` file in its own folder.
190 -This file is javascript module that is supposed to expose one or more of the supported keys:
190 +This file is javascript module that exports an `init` function like this:
191 +```js
192 +exports.init = api => ({
193 + frontend_css: 'mystyle.css'
194 +})
195 +```
196 +
197 +The init function is called when the module is loaded and should return an object with things to customize.
198 +In this example we are asking a css file to be loaded in the frontend.
199 +The parameter `api` object contains some useful things we'll see later.
200 +Let's first look at the things you can return:
201 +
202 +### Things a plugin can return
203
204 - `frontend_css: string | string[]` path to one or more css files that you want the frontend to load. These are to be placed in the `public` folder (refer below).
205 - `frontend_js: string | string[]` path to one or more js files that you want the frontend to load. These are to be placed in the `public` folder (refer below).
@@ -201,14 +213,13 @@ This file is javascript module that is supposed to expose one or more of the sup
213 - `unload: function` called when unloading a plugin. This is a good place for example to clearInterval().
214 - `onDirEntry: ({ entry: DirEntry, listPath: string }) => void | false` by providing this callback you can manipulate the record
215 that is sent to the frontend (`entry`), or you can return false to exclude this entry from the results.
204 -- `init: function` called when the plugin is initialized.
205 - If you need to use the `api` object immediately after the plugin is loaded, be sure to put your code in this callback.
206 -- `api: object` if your plugin exports an empty object with name `api`, it will be filled with useful functions.
207 - You'll just need this line
208 - ```js
209 - exports.api = {}
210 - ```
211 - Now let's have a look at what you'll find inside.
216 +
217 +### api object
218 +
219 +The `api` object you get as parameter of the `init` contains the following:
220 +
221 + - `require: function` use this instead of standard `require` function to access modules already loaded by HFS.
222 +
223 - `getConfig(key: string): any` this is the way to go if you need some configuration to do your job.
224
225 Eg: you want a `message` text. This should be put by the user in the main config file, under the `plugins_config` property.
@@ -219,11 +230,11 @@ This file is javascript module that is supposed to expose one or more of the sup
230 message: Hi there!
231 ```
232 Now you can use `api.getConfig('message')` to read it.
233 +
234 - `srcDir: string` this can be useful if you need to import some extra function not available in `api`.
235 ```js
224 - exports.api = {}
225 - exports.init = function() {
226 - const { BUILD_TIMESTAMP } = require(exports.api.srcDir + '/index')
236 + exports.init = api => {
237 + const { BUILD_TIMESTAMP } = api.require(api.srcDir + '/index')
238 console.log(BUILD_TIMESTAMP)
239 }
240 ```
@@ -233,7 +244,6 @@ This file is javascript module that is supposed to expose one or more of the sup
244
245 Each plug-in can have a `public` folder, and its files will be accessible at `/~/plugins/PLUGIN_NAME/FILENAME`.
246
236 -
247 ### Front-end specific
248
249 The following information applies to the default front-end, and may not apply to a custom one.
config.yaml
+1 -1
@@ -11,7 +11,7 @@ port: 80 # 0 is a special value that will let the system pick a random availa
11 #private_key: filepath
12 mime:
13 "*.jpg|*.png|*.mp3|*.txt": auto
14 -disable_plugins: [ 'theme-example', 'download-counter' ]
14 +disable_plugins: [ 'theme-example', 'download-counter^' ]
15 plugins_config:
16 middleware-example:
17 message: ciao
plugins/download-counter/plugin.js
+32 -31
@@ -1,37 +1,38 @@
1 -const { writeFile, readFile } = require('fs')
2 -const _ = require('lodash')
3 -const yaml = require('yaml')
1 +exports.init = api => {
2 + const _ = api.require('lodash')
3 + const yaml = api.require('yaml')
4 + const { writeFile, readFile } = api.require('fs')
5
5 -const countersFile = 'counters.yaml'
6 + const countersFile = 'counters.yaml'
7
7 -const counters = {}
8 -// load previous stats
9 -readFile(countersFile, 'utf8', (err, data) => {
10 - if (err)
11 - return err.code === 'ENOENT' || console.debug(countersFile, err)
12 - Object.assign(counters, yaml.parse(String(data)))
13 - console.debug('counters loaded')
14 -})
8 + const counters = {}
9 + const save = _.debounce(() => {
10 + writeFile(countersFile, yaml.stringify(counters), err => console.debug(err || 'counters saved'))
11 + }, 5_000, { maxWait:30_000 })
12
16 -const save = _.debounce(() => {
17 - writeFile(countersFile, yaml.stringify(counters), err => console.debug(err || 'counters saved'))
18 -}, 5_000, { maxWait:30_000 })
13 + // load previous stats
14 + readFile(countersFile, 'utf8', (err, data) => {
15 + if (err)
16 + return err.code === 'ENOENT' || console.debug(countersFile, err)
17 + Object.assign(counters, yaml.parse(String(data)))
18 + console.debug('counters loaded')
19 + })
20
20 -exports.unload = ()=> save.flush() // we may have pending savings
21 -
22 -exports.middleware = (ctx) =>
23 - () => { // execute after other middlewares are done
24 - if (ctx.status >= 300 || !ctx.fileSource) return
25 - const { path } = ctx
26 - counters[path] = counters[path] + 1 || 1
27 - save()
21 + return {
22 + frontend_js: 'hits.js',
23 + unload: () => save.flush(), // we may have pending savings
24 + middleware: (ctx) =>
25 + () => { // execute after other middlewares are done
26 + if (ctx.status >= 300 || !ctx.fileSource) return
27 + const { path } = ctx
28 + counters[path] = counters[path] + 1 || 1
29 + save()
30 + },
31 + onDirEntry: ({ entry, listPath }) => {
32 + const path = listPath + entry.n
33 + const n = counters[path]
34 + if (n)
35 + entry.hits = n
36 + }
37 }
29 -
30 -exports.onDirEntry = ({ entry, listPath }) => {
31 - const path = listPath + entry.n
32 - const n = counters[path]
33 - if (n)
34 - entry.hits = n
38 }
36 -
37 -exports.frontend_js = 'hits.js'
plugins/middleware-example-disabled/plugin.js
+6 -6
@@ -1,6 +1,6 @@
1 -const api = exports.api = {}
2 -
3 -exports.middleware = function(ctx) {
4 - ctx.body = 'This plugin is stopping you: ' + api.getConfig('message')
5 - return true // true = please stop
6 -}
1 +exports.init = api => ({
2 + middleware(ctx) {
3 + ctx.body = 'This plugin is stopping you: ' + api.getConfig('message')
4 + return true // true = please stop
5 + }
6 +})
src/plugins.ts
+4 -5
@@ -134,15 +134,14 @@ async function rescan() {
134 console.log(plugins[k] ? 'reloading plugin' : 'loading plugin', k)
135 const data = await import(f)
136 deleteModule(require.resolve(f)) // avoid caching
137 - new Plugin(k, data, unwatch)
138 - const { api } = data
139 - if (!api) return
140 - Object.assign(api, {
137 + const res = await data.init?.call(null, {
138 srcDir: __dirname,
139 + require,
140 getConfig: (cfgKey: string) =>
141 getConfig('plugins_config')?.[k]?.[cfgKey]
142 })
145 - await data.init?.call(api)
143 + Object.assign(data, res)
144 + new Plugin(k, data, unwatch)
145 } catch (e) {
146 console.log('plugin error:', e)
147 }