diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 2ad17e9ee..ad09cdd99 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -119,6 +119,7 @@ cydir
cyrusimap
datacenter
DATAERR
+datafiles
datalake
datastack
datastax
@@ -541,6 +542,7 @@ noout
nopassword
nopipelining
nordirplus
+noreferrer
noscheme
noselect
nosep
diff --git a/.gitignore b/.gitignore
index 1e5d613ed..3fcc33693 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ lib/data/*.mjs
.vitepress/local.js
.vitepress/.temp
/man/
+public/datafiles/
diff --git a/.vitepress/config.js b/.vitepress/config.js
index 48541fc75..87e5c5851 100644
--- a/.vitepress/config.js
+++ b/.vitepress/config.js
@@ -4,6 +4,7 @@ import { pagefindPlugin } from 'vitepress-plugin-pagefind'
import { generateSidebar } from 'vitepress-sidebar'
import { dovecotMdExtend } from '../lib/markdown.js'
import { getExcludes } from '../lib/utility.js'
+import dovecotVitepressInit from '../lib/dovecot_vitepress_init.js'
const base = '/2.4'
const base_url = 'https://doc.dovecot.org'
@@ -62,7 +63,8 @@ export default defineConfig({
chunkSizeWarningLimit: 1000,
},
plugins: [
- pagefindPlugin()
+ pagefindPlugin(),
+ dovecotVitepressInit()
],
},
diff --git a/components/DoveadmComponent.vue b/components/DoveadmComponent.vue
index 050cec2b9..33c45e9d6 100644
--- a/components/DoveadmComponent.vue
+++ b/components/DoveadmComponent.vue
@@ -15,7 +15,7 @@ const d = Object.fromEntries(Object.entries(data.doveadm).filter(([k, v]) =>
(v.plugin && v.plugin == props.plugin)) ||
(props.tag &&
((v.plugin && v.plugin == props.tag) ||
- (v.tags.includes(props.tag))))
+ (v.tags?.includes(props.tag))))
).sort())
const cliComponent = ref({})
diff --git a/components/EventsComponent.vue b/components/EventsComponent.vue
index a383622e0..ec3d40add 100644
--- a/components/EventsComponent.vue
+++ b/components/EventsComponent.vue
@@ -14,7 +14,7 @@ const d = Object.fromEntries(Object.entries(data).filter(([k, v]) =>
(v.root && v.root == props.root)) ||
(props.tag &&
((v.root && v.root == props.tag) ||
- (v.tags.includes(props.tag)))))
+ (v.tags?.includes(props.tag)))))
).sort())
diff --git a/components/SettingsComponent.vue b/components/SettingsComponent.vue
index a5814fc48..e3388cd1e 100644
--- a/components/SettingsComponent.vue
+++ b/components/SettingsComponent.vue
@@ -22,10 +22,10 @@ const d = Object.fromEntries(Object.entries(data).filter(([k, v]) =>
/* Filter entries (by plugin or tag). */
((!props.plugin && !tag) ||
(props.plugin &&
- (v.plugin && v.plugin.includes(props.plugin))) ||
+ v.plugin?.includes(props.plugin)) ||
(tag && tag.find((t) =>
- (v.plugin && v.plugin.includes(t)) ||
- (v.tags.includes(t)))
+ v.plugin?.includes(t) ||
+ v.tags?.includes(t))
)) &&
/* Apply filter. */
((filter == 'all') ||
@@ -88,13 +88,13 @@ const d = Object.fromEntries(Object.entries(data).filter(([k, v]) =>
-
| See Also |
diff --git a/data/doveadm.js b/data/doveadm.js
index 79d41d14f..f985117b1 100644
--- a/data/doveadm.js
+++ b/data/doveadm.js
@@ -24,7 +24,7 @@ export const doveadm = {
// If set, will use as command example argument.
// i.e., for HTTP API requests, this argument will be added
// to the example argument string.
- example: false,
+ // example: "foo",
// If true, this is an optional positional argument.
// optional: true,
diff --git a/data/events.js b/data/events.js
index f99910ec1..eb4ea9a79 100644
--- a/data/events.js
+++ b/data/events.js
@@ -29,7 +29,7 @@ export const events = {
// Event(s) this field inherits fields from.
// Can be a single event or an array of events.
- inherit: '',
+ // inherit: '',
// List of fields emitted. Keys are field names, values are
// descriptions (string) or an object with multiple keys (see below for
diff --git a/data/settings.js b/data/settings.js
index cb3377d0f..e9160b2e9 100644
--- a/data/settings.js
+++ b/data/settings.js
@@ -2131,8 +2131,7 @@ fts_header_includes {
fts_header_includes: {
plugin: 'fts',
values: setting_types.BOOLLIST,
- seealso: [ 'fts_header_excludes' ],
- text: ``
+ seealso: [ 'fts_header_excludes' ]
},
fts_search_timeout: {
diff --git a/docs/core/datafiles.md b/docs/core/datafiles.md
new file mode 100644
index 000000000..3d66d2088
--- /dev/null
+++ b/docs/core/datafiles.md
@@ -0,0 +1,40 @@
+---
+layout: doc
+title: Raw Data Files
+---
+
+
+
+# Raw Data Files
+
+This page provides download links to the raw data files used to generate the
+Dovecot documentation.
+
+The format and content of each data file is available by looking at the data
+file source in GitHub (links for each file below).
+
+## Data File List
+
+
diff --git a/lib/data/datafiles.data.js b/lib/data/datafiles.data.js
new file mode 100644
index 000000000..222c09eee
--- /dev/null
+++ b/lib/data/datafiles.data.js
@@ -0,0 +1,16 @@
+import { dataFileList } from '../datafiles.js'
+
+const filedata = []
+for (const d of dataFileList) {
+ filedata.push({
+ download: d.download,
+ github: d.github,
+ name: d.name
+ })
+}
+
+export default {
+ load() {
+ return { files: filedata }
+ }
+}
diff --git a/lib/data/doveadm.data.js b/lib/data/doveadm.data.js
index 63fd222af..326414dcf 100644
--- a/lib/data/doveadm.data.js
+++ b/lib/data/doveadm.data.js
@@ -1,160 +1,8 @@
-import { doveadm_arg_types, doveadm_flag_types, getDoveadmCmdLine } from '../doveadm.js'
-import { getVitepressMd } from '../markdown.js'
-import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js'
-import camelCase from 'camelcase'
-import slugify from '@sindresorhus/slugify'
-
-const doveadm_userargs = {
- 'all-users': {
- cli: 'A',
- example: false,
- type: doveadm_arg_types.BOOL,
- text: `Apply operation to all users.`
- },
- 'socket-path': {
- cli: 'S',
- example: '/var/run/dovecot/doveadm-server',
- type: doveadm_arg_types.STRING,
- text: `Path to doveadm socket.`
- },
- user: {
- cli: 'u',
- example: 'username',
- type: doveadm_arg_types.STRING,
- text: `UID of user to apply operation to.`,
- },
-}
-
-const doveadm_userfileargs = {
- /* Hidden from documentation.
- 'trans-flags': {
- type: doveadm_arg_types.INTEGER,
- text: `Transaction flags.`
- },
- */
- 'user-file': {
- cli: 'F',
- type: doveadm_arg_types.STRING,
- text: `A filename. If set, fetch usernames from file. One username per line.`
- },
-}
-
-function typeToString(type) {
- switch (type) {
- case doveadm_arg_types.ARRAY:
- return 'array'
- case doveadm_arg_types.BOOL:
- return 'boolean'
- case doveadm_arg_types.INTEGER:
- return 'integer'
- case doveadm_arg_types.STRING:
- return 'string'
- case doveadm_arg_types.SEARCH_QUERY:
- return 'search_query'
- case doveadm_arg_types.ISTREAM:
- return 'istream'
- }
-}
-
-function argToHttpParam(arg) {
- return arg.split('-').reduce((s, c) =>
- s + (c.charAt(0).toUpperCase() + c.slice(1)))
-}
-
-async function normalizeDoveadm(doveadm) {
- const md = await getVitepressMd()
-
- for (const [k, v] of Object.entries(doveadm)) {
- if (!v) {
- delete doveadm[k]
- continue
- }
-
- if (v.flags && (v.flags & doveadm_flag_types.USER)) {
- v.args = { ...v.args, ...doveadm_userargs }
- }
-
- if (v.flags && (v.flags & doveadm_flag_types.USERFILE)) {
- v.args = { ...v.args, ...doveadm_userfileargs }
- }
-
- /* Convert 'man' entry into a markdown link to man page.
- * We will add the hash to all URLs for simplicity; for those man
- * pages that don't have individual command names, this will just
- * be ignored by the browser. */
- if (v.man) {
- v.man_link = md.renderInline('[[man,' + v.man + ',' + slugify(k) + ']]')
- }
-
- /* Change entries. */
- for (const k2 of ['added', 'changed', 'deprecated', 'removed']) {
- if (v[k2]) {
- const changes = []
- for (const[k3, v3] of Object.entries(v[k2])) {
- changes.push({
- text: v3 ? md.render(v3.trim()) : null,
- version: md.renderInline('[[' + k2 + ',' + k3 + ']]')
- })
- }
- v[k2] = changes
- }
- }
-
- /* Response values. */
- if (v.response) {
- if (v.response.text) {
- v.response.text = md.render(String(v.response.text))
- }
- } else {
- delete v['response']
- }
-
- /* Text Description. */
- if (v.text) {
- v.text = md.render(v.text)
- }
-
- /* Cmd line arguments. */
- v.usage = k + (v.args ? ' ' + getDoveadmCmdLine(v.args) : '')
-
- if (v.args) {
- const args = []
- for (const [k2, v2] of Object.entries(v.args)) {
- if (!v2.hidden) {
- args.push({
- /* Undefined examples will resolve to undefined. */
- cli_only: v2.cli_only,
- example: v2.example,
- flag: v2.cli ? '-' + v2.cli : (v2.positional ? k2 : '--' + k2),
- param: argToHttpParam(k2),
- type: typeToString(v2.type),
- text: v2.text ? md.render(v2.text) : null
- })
- }
- }
- v.args = args
- }
- if (!v.args || !v.args.length) {
- delete v['args']
- }
-
- /* HTTP API info. */
- v.http_cmd = camelCase(k)
- }
-
- return {
- doveadm: normalizeArrayData(
- doveadm,
- ['tags']
- ),
- http_api_link: md.renderInline('[[link,doveadm_http_api,HTTP API]]')
- }
-}
+import { addWatchPaths } from '../utility.js'
+import { loadDoveadm } from '../doveadm.js'
export default addWatchPaths({
async load() {
- return await normalizeDoveadm(
- structuredClone(loadData('doveadm').doveadm)
- )
+ return await loadDoveadm()
}
})
diff --git a/lib/data/event_categories.data.js b/lib/data/event_categories.data.js
index 7a72fdbb3..c543f006f 100644
--- a/lib/data/event_categories.data.js
+++ b/lib/data/event_categories.data.js
@@ -1,20 +1,8 @@
-import { getVitepressMd } from '../markdown.js'
-import { addWatchPaths, loadData } from '../utility.js'
-
-async function normalizeEventCategories(categories) {
- const md = await getVitepressMd()
-
- for (const [k, v] of Object.entries(categories)) {
- v.description = md.renderInline(v.description)
- }
-
- return categories
-}
+import { loadEventCategories } from '../event_categories.js'
+import { addWatchPaths } from '../utility.js'
export default addWatchPaths({
async load() {
- return await normalizeEventCategories(
- structuredClone(loadData('event_categories').categories)
- )
+ return await loadEventCategories()
}
})
diff --git a/lib/data/event_reasons.data.js b/lib/data/event_reasons.data.js
index 15f8f2abf..2ab1c327b 100644
--- a/lib/data/event_reasons.data.js
+++ b/lib/data/event_reasons.data.js
@@ -1,20 +1,8 @@
-import { getVitepressMd } from '../markdown.js'
-import { addWatchPaths, loadData } from '../utility.js'
-
-async function normalizeEventReasons(reasons) {
- const md = await getVitepressMd()
-
- for (const [k, v] of Object.entries(reasons)) {
- v.description = md.renderInline(v.description)
- }
-
- return reasons
-}
+import { addWatchPaths } from '../utility.js'
+import { loadEventReasons } from '../event_reasons.js'
export default addWatchPaths({
async load() {
- return await normalizeEventReasons(
- structuredClone(loadData('event_reasons').reasons)
- )
+ return await loadEventReasons()
}
})
diff --git a/lib/data/lua.data.js b/lib/data/lua.data.js
index e42f2ee86..24b7f2ef0 100644
--- a/lib/data/lua.data.js
+++ b/lib/data/lua.data.js
@@ -1,95 +1,8 @@
-import { getVitepressMd } from '../markdown.js'
-import { addWatchPaths, loadData } from '../utility.js'
-
-async function normalizeLuaConstants(lua) {
- const md = await getVitepressMd()
- const out = {}
-
- for (const v of lua.values()) {
- if (v.text) {
- v.text = md.render(v.text)
- }
-
- for (const tag of v.tags) {
- const v2 = structuredClone(v)
- v2.tags = tag
- out[tag + '.' + v.name] = v2
- }
- }
-
- return out
-}
-
-async function normalizeLuaFunctions(lua) {
- const md = await getVitepressMd()
- let set = false
- const out = {}
-
- for (const v of lua.values()) {
- if (v.args) {
- for (const [k2, v2] of Object.entries(v.args)) {
- /* Merge information from Dovecot settings. */
- if (v2.dovecot_setting) {
- if (!set) {
- set = structuredClone(loadData('settings').settings)
- }
-
- if (!v2.type) {
- v2.type = set[v2.dovecot_setting].values?.label
- }
-
- if (!v2.text) {
- v2.text = set[v2.dovecot_setting].text.trim()
- }
-
- if (v2.default === undefined) {
- v2.default = set[v2.dovecot_setting].default
- }
- }
-
- v2.text = md.render(v2.text)
- }
- }
-
- v.text = md.render(v.text)
-
- for (const tag of v.tags) {
- const v2 = structuredClone(v)
- v2.tags = tag
- out[tag + '.' + v.name] = v2
- }
- }
-
- return out
-}
-
-async function normalizeLuaVariables(lua) {
- const md = await getVitepressMd()
- const out = {}
-
- for (const v of lua.values()) {
- if (v.text) {
- v.text = md.render(v.text)
- }
-
- for (const tag of v.tags) {
- const v2 = structuredClone(v)
- v2.tags = tag
- out[tag + '.' + v.name] = v2
- }
- }
-
- return out
-}
+import { addWatchPaths } from '../utility.js'
+import { loadLua } from '../lua.js'
export default addWatchPaths({
async load() {
- const data = loadData('lua')
-
- return {
- constants: await normalizeLuaConstants(data.lua_constants),
- functions: await normalizeLuaFunctions(data.lua_functions),
- variables: await normalizeLuaVariables(data.lua_variables)
- }
+ return await loadLua()
}
})
diff --git a/lib/data/settings.data.js b/lib/data/settings.data.js
index a3c67aa44..ad7adf2a9 100644
--- a/lib/data/settings.data.js
+++ b/lib/data/settings.data.js
@@ -1,137 +1,8 @@
-import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js'
-import { getVitepressMd } from '../markdown.js'
-
-function wrapInTag(str, tag) {
- if (tag)
- return `<${tag}>${str}${tag}>`
- return str
-}
-
-/* Resolve links in given parameter. If no singular link is detected, it is
- * rendered with the provided tag surrounding the value. */
-function normalizeString(md, str, tag = null) {
- let out = ''
-
- if (str) {
- /* FIXME: This makes the following .startsWith() call work, but might
- * lead to type-specific errors, e.g. String({}) yields
- * '[object Object]'. This still needs to be verified manually. */
- out = String(str)
- if (!out.startsWith('[[')) {
- out = wrapInTag(out, tag)
- }
- return md.renderInline(out)
- }
-
- return str
-}
-
-/* Mark a plain item as an inter-settings dovecot-specific link, i.e.
- * [[setting,- ]]. Don't process already marked links. */
-function normalizeArray(md, arr) {
- if (arr) {
- return arr.map(entry => (
- md.renderInline(
- entry.startsWith('[[')
- ? entry
- : `[[setting,${entry}]]`
- )
- ))
- }
-
- return arr
-}
-
-async function normalizeSettings(settings) {
- const data = normalizeArrayData(
- settings,
- ['dependencies', 'seealso', 'tags', 'values_enum']
- )
-
- const md = await getVitepressMd()
-
- for (const [k, v] of Object.entries(data)) {
- if (!v) {
- delete data[k]
- continue
- }
-
- /* Style default entry. */
- if (!!v.default) {
- if (['string', 'number'].includes(typeof v.default) ||
- v.default instanceof String)
- v.default = normalizeString(md, v.default, 'code')
- else {
- let out = normalizeString(md, v.default.value ?? '', 'code')
- if (out.length > 0)
- out += '
'
- if (!!v.default.text)
- out += `${normalizeString(md, v.default.text ?? '')}`
- v.default = out
- }
- }
-
- /* Add list of dependencies. */
- v.dependencies = normalizeArray(md, v.dependencies)
-
- /* Add markdown to seealso settings. */
- v.seealso = normalizeArray(md, v.seealso)
-
- /* Plugin. */
- if (v.plugin) {
- v.plugin = [ v.plugin ].flat()
- v.plugin_link = v.plugin.map((x) =>
- md.renderInline('[[plugin,' + x + ']]')
- ).join(', ')
- }
-
- /* There can be multiple value entries. */
- if (!Array.isArray(v.values)) {
- v.values = [ v.values ]
- }
-
- for (const v2 of v.values) {
- if (!v2) {
- throw new Error("Incorrect value type for " + k)
- }
-
- if (v2.default_required && (v.default === undefined)) {
- throw new Error("Default value missing for " + k)
- }
- if (v2.enum_required && !v.values_enum) {
- throw new Error("Enum array missing for " + k)
- }
-
- v2.url = md.renderInline(v2.url)
-
- if (v2.no_default) {
- v.no_default = true
- }
- }
-
- for (const k2 of ['added', 'changed', 'deprecated', 'removed']) {
- if (v[k2]) {
- const changes = []
- for (const[k3, v3] of Object.entries(v[k2])) {
- changes.push({
- text: v3 ? md.render(v3.trim()) : null,
- version: md.renderInline('[[' + k2 + ',' + k3 + ']]')
- })
- }
- v[k2] = changes
- }
- }
-
- v.text = md.render(v.text.trim())
- }
-
- return data
-}
+import { addWatchPaths } from '../utility.js'
+import { loadSettings } from '../settings.js'
export default addWatchPaths({
async load() {
- return await normalizeSettings(
- structuredClone(loadData('settings').settings)
- )
+ return await loadSettings()
}
})
diff --git a/lib/datafiles.js b/lib/datafiles.js
new file mode 100644
index 000000000..e73dc61bd
--- /dev/null
+++ b/lib/datafiles.js
@@ -0,0 +1,64 @@
+import path from 'path'
+import { lib_dirname } from './utility.js'
+
+const download_base = 'datafiles'
+const github_base = 'https://github.com/dovecot/documentation'
+
+export const publicDataDir = path.resolve(lib_dirname, `../public/${download_base}`)
+
+export const dataFileList = [
+ createEntry(
+ 'doveadm.js',
+ async () => {
+ const { loadDoveadm } = await import(path.resolve(lib_dirname, './doveadm.js'))
+ return await loadDoveadm()
+ }
+ ),
+ createEntry(
+ 'event_categories.js',
+ async () => {
+ const { loadEventCategories } = await import(path.resolve(lib_dirname, './event_categories.js'))
+ return await loadEventCategories()
+ }
+ ),
+ createEntry(
+ 'event_reasons.js',
+ async () => {
+ const { loadEventReasons } = await import(path.resolve(lib_dirname, './event_reasons.js'))
+ return await loadEventReasons()
+ }
+ ),
+ createEntry(
+ 'events.js',
+ async () => {
+ const { loadEvents } = await import(path.resolve(lib_dirname, './events.js'))
+ return await loadEvents()
+ }
+ ),
+ createEntry(
+ 'lua.js',
+ async () => {
+ const { loadLua } = await import(path.resolve(lib_dirname, './lua.js'))
+ return await loadLua()
+ }
+ ),
+ createEntry(
+ 'settings.js',
+ async () => {
+ const { loadSettings } = await import(path.resolve(lib_dirname, './settings.js'))
+ return await loadSettings()
+ }
+ )
+]
+
+function createEntry(id, func) {
+ const json = `${path.basename(id, '.js')}.json`
+
+ return {
+ data: func,
+ download: `/${download_base}/${json}`,
+ github: `${github_base}/blob/main/data/${id}`,
+ json: path.resolve(publicDataDir, json),
+ name: json
+ }
+}
diff --git a/lib/doveadm.js b/lib/doveadm.js
index fadac3b96..e5f11a0e1 100644
--- a/lib/doveadm.js
+++ b/lib/doveadm.js
@@ -1,3 +1,9 @@
+import { getVitepressMd } from './markdown.js'
+import { loadData, normalizeArrayData } from './utility.js'
+import camelCase from 'camelcase'
+import cleanDeep from 'clean-deep'
+import slugify from '@sindresorhus/slugify'
+
/* List of Doveadm argument value types. */
export const doveadm_arg_types = {
ARRAY: 1,
@@ -68,3 +74,159 @@ export function getDoveadmCmdLine(args) {
return ret.trim()
}
+
+const doveadm_userargs = {
+ 'all-users': {
+ cli: 'A',
+ example: false,
+ type: doveadm_arg_types.BOOL,
+ text: `Apply operation to all users.`
+ },
+ 'socket-path': {
+ cli: 'S',
+ example: '/var/run/dovecot/doveadm-server',
+ type: doveadm_arg_types.STRING,
+ text: `Path to doveadm socket.`
+ },
+ user: {
+ cli: 'u',
+ example: 'username',
+ type: doveadm_arg_types.STRING,
+ text: `UID of user to apply operation to.`,
+ },
+}
+
+const doveadm_userfileargs = {
+ /* Hidden from documentation.
+ 'trans-flags': {
+ type: doveadm_arg_types.INTEGER,
+ text: `Transaction flags.`
+ },
+ */
+ 'user-file': {
+ cli: 'F',
+ type: doveadm_arg_types.STRING,
+ text: `A filename. If set, fetch usernames from file. One username per line.`
+ },
+}
+
+function typeToString(type) {
+ switch (type) {
+ case doveadm_arg_types.ARRAY:
+ return 'array'
+ case doveadm_arg_types.BOOL:
+ return 'boolean'
+ case doveadm_arg_types.INTEGER:
+ return 'integer'
+ case doveadm_arg_types.STRING:
+ return 'string'
+ case doveadm_arg_types.SEARCH_QUERY:
+ return 'search_query'
+ case doveadm_arg_types.ISTREAM:
+ return 'istream'
+ }
+}
+
+function argToHttpParam(arg) {
+ return arg.split('-').reduce((s, c) =>
+ s + (c.charAt(0).toUpperCase() + c.slice(1)))
+}
+
+async function normalizeDoveadm(doveadm) {
+ const md = await getVitepressMd()
+
+ for (const [k, v] of Object.entries(doveadm)) {
+ if (!v) {
+ delete doveadm[k]
+ continue
+ }
+
+ if (v.flags && (v.flags & doveadm_flag_types.USER)) {
+ v.args = { ...v.args, ...doveadm_userargs }
+ }
+
+ if (v.flags && (v.flags & doveadm_flag_types.USERFILE)) {
+ v.args = { ...v.args, ...doveadm_userfileargs }
+ }
+
+ /* Convert 'man' entry into a markdown link to man page.
+ * We will add the hash to all URLs for simplicity; for those
+ * man pages that don't have individual command names, this
+ * will just be ignored by the browser. */
+ if (v.man) {
+ v.man_link = md.renderInline('[[man,' + v.man + ',' + slugify(k) + ']]')
+ }
+
+ /* Change entries. */
+ for (const k2 of ['added', 'changed', 'deprecated', 'removed']) {
+ if (v[k2]) {
+ const changes = []
+ for (const[k3, v3] of Object.entries(v[k2])) {
+ changes.push({
+ text: v3 ? md.render(v3.trim()) : null,
+ version: md.renderInline('[[' + k2 + ',' + k3 + ']]')
+ })
+ }
+ v[k2] = changes
+ }
+ }
+
+ /* Response values. */
+ if (v.response) {
+ if (v.response.text) {
+ v.response.text = md.render(String(v.response.text))
+ }
+ } else {
+ delete v['response']
+ }
+
+ /* Text Description. */
+ if (v.text) {
+ v.text = md.render(v.text)
+ }
+
+ /* Cmd line arguments. */
+ v.usage = k + (v.args ? ' ' + getDoveadmCmdLine(v.args) : '')
+
+ if (v.args) {
+ const args = []
+ for (const [k2, v2] of Object.entries(v.args)) {
+ if (!v2.hidden) {
+ args.push({
+ cli_only: v2.cli_only,
+ /* Undefined examples will
+ * resolve to undefined. */
+ example: v2.example,
+ flag: v2.cli ? '-' + v2.cli : (v2.positional ? k2 : '--' + k2),
+ param: argToHttpParam(k2),
+ type: typeToString(v2.type),
+ text: v2.text ? md.render(v2.text) : null
+ })
+ }
+ }
+ v.args = args
+ }
+ if (!v.args || !v.args.length) {
+ delete v['args']
+ }
+
+ /* HTTP API info. */
+ v.http_cmd = camelCase(k)
+ }
+
+ return {
+ doveadm: normalizeArrayData(
+ doveadm,
+ ['tags']
+ ),
+ http_api_link: md.renderInline('[[link,doveadm_http_api,HTTP API]]')
+ }
+}
+
+export async function loadDoveadm() {
+ return cleanDeep(
+ await normalizeDoveadm(
+ structuredClone((await loadData('doveadm')).doveadm)
+ )
+ )
+}
diff --git a/lib/dovecot_vitepress_init.js b/lib/dovecot_vitepress_init.js
new file mode 100644
index 000000000..41558fe7e
--- /dev/null
+++ b/lib/dovecot_vitepress_init.js
@@ -0,0 +1,41 @@
+import { dataFileList, publicDataDir } from './datafiles.js'
+import { dovecotMdInit } from './markdown.js'
+import fs from 'fs'
+
+let has_run = false
+
+export default function dovecotVitepressInit() {
+ return {
+ name: 'dovecot-vitepress-init',
+ async configResolved(config) {
+ /*** Init Dovecot Markdown. ***/
+
+ /* We need to synchronously initialize markdown,
+ * since we need to pre-populate various internal
+ * tables (e.g. links). */
+ await dovecotMdInit()
+ console.log('\nā
Dovecot Markdown initialized.')
+
+ /*** Create static downloadable data files. ***/
+
+ if (has_run) {
+ return
+ }
+ has_run = true
+
+ /* Clean old data files (if they exist) and prepare directory. */
+ fs.rmSync(publicDataDir, { force: true, recursive: true });
+ fs.mkdirSync(publicDataDir, { recursive: true });
+ console.log(`ā
Data files: Created ${publicDataDir}.`)
+
+ /* Build the data files. */
+ for (const d of dataFileList) {
+ fs.writeFileSync(
+ d.json,
+ JSON.stringify(await d.data(), null, 2)
+ )
+ console.log(`ā
Data files: Generated ${d.json}.`)
+ }
+ }
+ }
+}
diff --git a/lib/event_categories.js b/lib/event_categories.js
new file mode 100644
index 000000000..04a1686a6
--- /dev/null
+++ b/lib/event_categories.js
@@ -0,0 +1,18 @@
+import { getVitepressMd } from './markdown.js'
+import { loadData } from './utility.js'
+
+async function normalizeEventCategories(categories) {
+ const md = await getVitepressMd()
+
+ for (const [k, v] of Object.entries(categories)) {
+ v.description = md.renderInline(v.description)
+ }
+
+ return categories
+}
+
+export async function loadEventCategories() {
+ return await normalizeEventCategories(
+ structuredClone((await loadData('event_categories')).categories)
+ )
+}
diff --git a/lib/event_reasons.js b/lib/event_reasons.js
new file mode 100644
index 000000000..f195b567f
--- /dev/null
+++ b/lib/event_reasons.js
@@ -0,0 +1,18 @@
+import { getVitepressMd } from './markdown.js'
+import { loadData } from './utility.js'
+
+async function normalizeEventReasons(reasons) {
+ const md = await getVitepressMd()
+
+ for (const [k, v] of Object.entries(reasons)) {
+ v.description = md.renderInline(v.description)
+ }
+
+ return reasons
+}
+
+export async function loadEventReasons() {
+ return await normalizeEventReasons(
+ structuredClone((await loadData('event_reasons')).reasons)
+ )
+}
diff --git a/lib/events.js b/lib/events.js
index 20ccf728d..f6f47f619 100644
--- a/lib/events.js
+++ b/lib/events.js
@@ -1,5 +1,6 @@
import { loadData, normalizeArrayData } from './utility.js'
import { getVitepressMd } from './markdown.js'
+import cleanDeep from 'clean-deep'
/* Take the events list and normalize entries and process inheritance. */
function processInherit(i, ob) {
@@ -115,17 +116,21 @@ async function normalizeEvents(events, global_inherits, inherits) {
v.fields = fields
/* Prefix the list of changes to the event's description. */
- v.text = v.text ? md.render(`${appendListOfUpdates(v)}\n${v.text}`) : null
+ if (v.text) {
+ v.text = md.render(`${appendListOfUpdates(v)}\n${v.text}`)
+ }
}
return events
}
export async function loadEvents() {
- const data = loadData('events')
- return await normalizeEvents(
- structuredClone(data.events),
- structuredClone(data.global_inherits),
- structuredClone(data.inherits)
+ const data = await loadData('events')
+ return cleanDeep(
+ await normalizeEvents(
+ structuredClone(data.events),
+ structuredClone(data.global_inherits),
+ structuredClone(data.inherits)
+ )
)
}
diff --git a/lib/lua.js b/lib/lua.js
new file mode 100644
index 000000000..a81f6d2c9
--- /dev/null
+++ b/lib/lua.js
@@ -0,0 +1,89 @@
+import { getVitepressMd } from './markdown.js'
+import { loadData } from './utility.js'
+
+async function normalizeLuaConstants(lua) {
+ const md = await getVitepressMd()
+ const out = {}
+
+ for (const v of lua.values()) {
+ if (v.text) {
+ v.text = md.render(v.text)
+ }
+
+ for (const tag of v.tags) {
+ const v2 = structuredClone(v)
+ v2.tags = tag
+ out[tag + '.' + v.name] = v2
+ }
+ }
+
+ return out
+}
+
+async function normalizeLuaFunctions(lua) {
+ const set = structuredClone((await loadData('settings')).settings)
+ const md = await getVitepressMd()
+ const out = {}
+
+ for (const v of lua.values()) {
+ if (v.args) {
+ for (const [k2, v2] of Object.entries(v.args)) {
+ /* Merge information from Dovecot settings. */
+ if (v2.dovecot_setting) {
+ if (!v2.type) {
+ v2.type = set[v2.dovecot_setting].values?.label
+ }
+
+ if (!v2.text) {
+ v2.text = set[v2.dovecot_setting].text.trim()
+ }
+
+ if (v2.default === undefined) {
+ v2.default = set[v2.dovecot_setting].default
+ }
+ }
+
+ v2.text = md.render(v2.text)
+ }
+ }
+
+ v.text = md.render(v.text)
+
+ for (const tag of v.tags) {
+ const v2 = structuredClone(v)
+ v2.tags = tag
+ out[tag + '.' + v.name] = v2
+ }
+ }
+
+ return out
+}
+
+async function normalizeLuaVariables(lua) {
+ const md = await getVitepressMd()
+ const out = {}
+
+ for (const v of lua.values()) {
+ if (v.text) {
+ v.text = md.render(v.text)
+ }
+
+ for (const tag of v.tags) {
+ const v2 = structuredClone(v)
+ v2.tags = tag
+ out[tag + '.' + v.name] = v2
+ }
+ }
+
+ return out
+}
+
+export async function loadLua() {
+ const data = await loadData('lua')
+
+ return {
+ constants: await normalizeLuaConstants(data.lua_constants),
+ functions: await normalizeLuaFunctions(data.lua_functions),
+ variables: await normalizeLuaVariables(data.lua_variables)
+ }
+}
diff --git a/lib/markdown.js b/lib/markdown.js
index ac98a2c9d..f36dc408a 100644
--- a/lib/markdown.js
+++ b/lib/markdown.js
@@ -5,7 +5,56 @@ import path from 'path'
import { createMarkdownRenderer } from 'vitepress'
import { dovecotSetting, frontmatterIter, loadData } from './utility.js'
-export function dovecotMdExtend(md) {
+let md_conf = false
+export async function dovecotMdInit() {
+ if (md_conf) {
+ return md_conf
+ }
+
+ md_conf = {
+ doveadm: (await loadData('doveadm')).doveadm,
+ events: (await loadData('events')).events,
+ links: await (async () => {
+ const links = {}
+ const rewrites = globalThis.VITEPRESS_CONFIG.rewrites.map
+
+ frontmatterIter(Object.keys(rewrites), function (f, data) {
+ if (!data.dovecotlinks) {
+ return
+ }
+
+ for (const [k, v] of Object.entries(data.dovecotlinks)) {
+ if (links[k]) {
+ throw new Error("Duplicate Dovecot Link key: " + k)
+ }
+
+ links[k] = {
+ url: resolveURL(rewrites[f].substring(0, rewrites[f].lastIndexOf('.')) + '.html')
+ }
+
+ if ((typeof v) == 'object') {
+ links[k].text = v.text
+ if (v.hash) {
+ links[k].url += '#' + v.hash
+ }
+ } else {
+ links[k].text = v
+ }
+ }
+ })
+
+ return {
+ ...links, ...((await loadData('links_overrides')).links_overrides)
+ }
+ })(),
+ settings: (await loadData('settings')).settings,
+ updates: (await loadData('updates')).updates
+ }
+
+ return md_conf
+}
+
+export async function dovecotMdExtend(md) {
md.use(containerPlugin, 'todo', {
render: function(tokens, idx) {
if (tokens[idx].nesting === 1) {
@@ -16,7 +65,7 @@ export function dovecotMdExtend(md) {
}
})
md.use(deflistPlugin)
- md.use(dovecot_markdown)
+ md.use(dovecot_markdown, await dovecotMdInit())
return md
}
@@ -26,7 +75,7 @@ export async function getVitepressMd() {
if (vitepress_md === null) {
const config = globalThis.VITEPRESS_CONFIG
- vitepress_md = dovecotMdExtend(await createMarkdownRenderer(
+ vitepress_md = await dovecotMdExtend(await createMarkdownRenderer(
config.srcDir,
config.markdown,
config.site.base,
@@ -40,7 +89,7 @@ export async function getVitepressMd() {
/* This is a dovecot markdown extension to support the "[[...]]" syntax.
* Much of this is copied from existing markdown-it plugins. See, e.g.,
* https://github.com/markdown-it/markdown-it-sub/blob/master/index.mjs */
-function dovecot_markdown(md) {
+function dovecot_markdown(md, opts) {
function process_brackets(state, silent) {
const max = state.posMax
const start = state.pos
@@ -130,8 +179,6 @@ function dovecot_markdown(md) {
let page = mode
switch (mode) {
case 'doveadm':
- initDoveadm()
-
if (!opts.doveadm[env.inner]) {
if (!Object.values(opts.doveadm).find((x) => (x.man == 'doveadm-' + env.inner))) {
handle_error('doveadm link missing: ' + env.inner)
@@ -141,8 +188,6 @@ function dovecot_markdown(md) {
break
case 'event':
- initEvents()
-
if (!opts.events[env.inner]) {
handle_error('event link missing: ' + env.inner)
return ''
@@ -152,8 +197,6 @@ function dovecot_markdown(md) {
case 'setting':
case 'setting_text':
- initSettings()
-
/* Settings names can have brackets, so we need to unescape
* input for purposes of searching settings keys. */
const search_str = env.inner.replaceAll('>', '>')
@@ -175,14 +218,12 @@ function dovecot_markdown(md) {
let url = '#'
env.inner = false
- initDovecotLinks()
-
- if (!opts.dovecotlinks[parts[1]]) {
+ if (!opts.links[parts[1]]) {
handle_error('Dovecot link missing: ' + parts[1])
return ''
}
- const d = opts.dovecotlinks[parts[1]]
+ const d = opts.links[parts[1]]
env.inner = parts[2] ? parts[2] : (d.text ? d.text : false)
return ''
@@ -287,8 +328,6 @@ function dovecot_markdown(md) {
case 'changed':
case 'deprecated':
case 'removed':
- initUpdates()
-
if (!opts.updates[env.args]) {
handle_error('Missing updates entry for: ' + env.args)
return env.args
@@ -369,56 +408,6 @@ function dovecot_markdown(md) {
console.error(msg)
}
- function initDoveadm() {
- if (!opts.doveadm) {
- opts.doveadm = loadData('doveadm').doveadm
- }
- }
-
- function initDovecotLinks() {
- if (opts.dovecotlinks) {
- return
- }
-
- const links = {}
- const rewrites = globalThis.VITEPRESS_CONFIG.rewrites.map
-
- frontmatterIter(Object.keys(rewrites), function (f, data) {
- if (!data.dovecotlinks) {
- return
- }
-
- for (const [k, v] of Object.entries(data.dovecotlinks)) {
- if (links[k]) {
- throw new Error("Duplicate Dovecot Link key: " + k)
- }
-
- links[k] = {
- url: resolveURL(rewrites[f].substring(0, rewrites[f].lastIndexOf('.')) + '.html')
- }
-
- if ((typeof v) == 'object') {
- links[k].text = v.text
- if (v.hash) {
- links[k].url += '#' + v.hash
- }
- } else {
- links[k].text = v
- }
- }
- })
-
- opts.dovecotlinks = {
- ...links, ...(loadData('links_overrides').links_overrides)
- }
- }
-
- function initEvents() {
- if (!opts.events) {
- opts.events = loadData('events').events
- }
- }
-
function initManFiles() {
if (!opts.man) {
opts.man = dovecotSetting('man_paths').flatMap((x) => {
@@ -444,37 +433,17 @@ function dovecot_markdown(md) {
}
}
- function initSettings() {
- if (!opts.settings) {
- opts.settings = loadData('settings').settings
- }
- }
-
- function initUpdates() {
- if (!opts.updates) {
- opts.updates = loadData('updates').updates
- }
- }
-
- function resolveURL(url) {
- if (!('url_rewrite' in opts)) {
- opts.url_rewrite = dovecotSetting('url_rewrite')
- }
-
- const new_url =
- (opts.base.endsWith('/') ? opts.base.slice(0, -1) : opts.base) +
- '/' + url
- return (opts.url_rewrite) ? opts.url_rewrite(new_url) : new_url
- }
-
- const opts = {
- base: globalThis.VITEPRESS_CONFIG.site.base
- }
-
md.inline.ruler.after('emphasis', 'dovecot_brackets', process_brackets)
md.renderer.rules.dovecot_open = dovecot_open
md.renderer.rules.dovecot_body = dovecot_body
md.renderer.rules.dovecot_close = dovecot_close
+}
- opts.resolveURL = resolveURL
+export function resolveURL(url) {
+ const base = globalThis.VITEPRESS_CONFIG.site.base
+ const url_rewrite = dovecotSetting('url_rewrite')
+ const new_url =
+ (base.endsWith('/') ? base.slice(0, -1) : base) +
+ '/' + url
+ return (url_rewrite) ? url_rewrite(new_url) : new_url
}
diff --git a/lib/settings.js b/lib/settings.js
index 27a5ff65c..5fe85d66b 100644
--- a/lib/settings.js
+++ b/lib/settings.js
@@ -1,3 +1,7 @@
+import { getVitepressMd } from './markdown.js'
+import { loadData, normalizeArrayData } from './utility.js'
+import cleanDeep from 'clean-deep'
+
/* List of Dovecot settings value types. */
export const setting_types = {
BOOLEAN: {
@@ -91,3 +95,145 @@ export const setting_types = {
url: '[[link,settings_groups_includes]]'
}
}
+
+function wrapInTag(str, tag) {
+ if (tag)
+ return `<${tag}>${str}${tag}>`
+ return str
+}
+
+/* Resolve links in given parameter. If no singular link is detected, it is
+ * rendered with the provided tag surrounding the value. */
+function normalizeString(md, str, tag = null) {
+ let out = ''
+
+ if (str) {
+ /* FIXME: This makes the following .startsWith() call work,
+ * but might lead to type-specific errors, e.g. String({})
+ * yields '[object Object]'. This still needs to be verified
+ * manually. */
+ out = String(str)
+ if (!out.startsWith('[[')) {
+ out = wrapInTag(out, tag)
+ }
+ return md.renderInline(out)
+ }
+
+ return str
+}
+
+/* Mark a plain item as an inter-settings dovecot-specific link, i.e.
+ * [[setting,- ]]. Don't process already marked links. */
+function normalizeArray(md, arr) {
+ if (arr) {
+ return arr.map(entry => (
+ md.renderInline(
+ entry.startsWith('[[')
+ ? entry
+ : `[[setting,${entry}]]`
+ )
+ ))
+ }
+
+ return arr
+}
+
+async function normalizeSettings(settings) {
+ const data = normalizeArrayData(
+ settings,
+ ['dependencies', 'seealso', 'tags', 'values_enum']
+ )
+
+ const md = await getVitepressMd()
+
+ for (const [k, v] of Object.entries(data)) {
+ if (!v) {
+ delete data[k]
+ continue
+ }
+
+ /* Style default entry. */
+ if (!!v.default) {
+ if (['string', 'number'].includes(typeof v.default) ||
+ v.default instanceof String)
+ v.default = normalizeString(md, v.default, 'code')
+ else {
+ let out = normalizeString(md, v.default.value ?? '', 'code')
+ if (out.length > 0)
+ out += '
'
+ if (!!v.default.text)
+ out += `${normalizeString(md, v.default.text ?? '')}`
+ v.default = out
+ }
+ }
+
+ /* Add list of dependencies. */
+ v.dependencies = normalizeArray(md, v.dependencies)
+
+ /* Add markdown to seealso settings. */
+ v.seealso = normalizeArray(md, v.seealso)
+
+ /* Plugin. */
+ if (v.plugin) {
+ v.plugin = [ v.plugin ].flat()
+ v.plugin_link = v.plugin.map((x) =>
+ md.renderInline('[[plugin,' + x + ']]')
+ ).join(', ')
+ }
+
+ /* There can be multiple value entries. */
+ if (!Array.isArray(v.values)) {
+ v.values = [ v.values ]
+ }
+
+ for (const v2 of v.values) {
+ if (!v2) {
+ throw new Error("Incorrect value type for " + k)
+ }
+
+ if (v2.default_required && (v.default === undefined)) {
+ throw new Error("Default value missing for " + k)
+ }
+ if (v2.enum_required && !v.values_enum) {
+ throw new Error("Enum array missing for " + k)
+ }
+
+ v2.url = md.renderInline(v2.url)
+
+ if (v2.no_default) {
+ v.no_default = true
+ }
+ }
+
+ for (const k2 of ['added', 'changed', 'deprecated', 'removed']) {
+ if (v[k2]) {
+ const changes = []
+ for (const[k3, v3] of Object.entries(v[k2])) {
+ changes.push({
+ text: v3 ? md.render(v3.trim()) : null,
+ version: md.renderInline('[[' + k2 + ',' + k3 + ']]')
+ })
+ }
+ v[k2] = changes
+ }
+ }
+
+ if (v.text) {
+ v.text = md.render(v.text.trim())
+ }
+ }
+
+ return data
+}
+
+export async function loadSettings() {
+ return cleanDeep(
+ await normalizeSettings(
+ structuredClone((await loadData('settings')).settings)
+ ),
+ // Clean empty arrays and null values.
+ // Maintain empty string (""), since it is used for
+ // certain default settings.
+ { emptyStrings: false }
+ )
+}
diff --git a/lib/utility.js b/lib/utility.js
index 07352df8f..c8a880506 100644
--- a/lib/utility.js
+++ b/lib/utility.js
@@ -3,12 +3,11 @@
import fg from 'fast-glob'
import fs from 'fs'
import matter from 'gray-matter'
-import importSync from 'import-sync'
import { dirname } from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
+export const lib_dirname = dirname(__filename);
export function normalizeArrayData(data, keys) {
for (const [k, v] of Object.entries(data)) {
@@ -27,16 +26,21 @@ export function normalizeArrayData(data, keys) {
return data
}
-export function loadData(id) {
- const path = globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.data_paths?.[id]
- ?? ('../data/' + id + '.js')
+const dataOb = {}
+export async function loadData(id) {
+ if (!dataOb[id]) {
+ const path = globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.data_paths?.[id]
+ ?? ('../data/' + id + '.js')
- try {
- return importSync(__dirname + '/' + path)
- } catch (e) {
- throw new Error('Unable to import module (' + __dirname + '/' +
- path + '):' + e)
+ try {
+ dataOb[id] = await import(lib_dirname + '/' + path)
+ } catch (e) {
+ throw new Error('Unable to import module (' + lib_dirname + '/' +
+ path + '):' + e)
+ }
}
+
+ return dataOb[id]
}
function _dovecotSetting(name, setting) {
diff --git a/package-lock.json b/package-lock.json
index dc9e419a9..1a328562b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,11 +8,11 @@
"devDependencies": {
"@sindresorhus/slugify": "^2.2.1",
"camelcase": "^8.0.0",
+ "clean-deep": "^3.4.0",
"commander": "^13.1.0",
"dayjs": "^1.11.13",
"fast-glob": "^3.3.3",
"git-commit-info": "^2.0.2",
- "import-sync": "^2.2.3",
"markdown-it-container": "^4.0.0",
"markdown-it-deflist": "^3.0.0",
"markdown-it-mathjax3": "^4.3.2",
@@ -765,16 +765,6 @@
"node": ">=12"
}
},
- "node_modules/@httptoolkit/esm": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@httptoolkit/esm/-/esm-3.3.1.tgz",
- "integrity": "sha512-XvWsT5qskZQoiHgg0kEoIonB+Zj/0T/W0rosjzyPuY++iBwO5c9fMfgvPBCffwY3cTrTD4KYpTPUEtLD0I1lmQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@iconify-json/simple-icons": {
"version": "1.2.21",
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.21.tgz",
@@ -2006,6 +1996,21 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/clean-deep": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/clean-deep/-/clean-deep-3.4.0.tgz",
+ "integrity": "sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash.isempty": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.transform": "^4.6.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2617,18 +2622,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/fuse.js": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
- "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/get-intrinsic": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
@@ -3070,16 +3063,6 @@
"node": ">=8.12.0"
}
},
- "node_modules/import-sync": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/import-sync/-/import-sync-2.2.3.tgz",
- "integrity": "sha512-ZnF84+eGjetsXwYEuFZADO4eCYr1ngBo6UK476Oq4q6dkiDM1TN+6D5iQ5/e3erCyjo7O6xT3xHE6xdtCgDYhw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@httptoolkit/esm": "^3.3.1"
- }
- },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -3496,6 +3479,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/lodash.isempty": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
+ "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.transform": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
+ "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
diff --git a/package.json b/package.json
index 4792a0830..e56a015b6 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,11 @@
"devDependencies": {
"@sindresorhus/slugify": "^2.2.1",
"camelcase": "^8.0.0",
+ "clean-deep": "^3.4.0",
"commander": "^13.1.0",
"dayjs": "^1.11.13",
"fast-glob": "^3.3.3",
"git-commit-info": "^2.0.2",
- "import-sync": "^2.2.3",
"markdown-it-container": "^4.0.0",
"markdown-it-deflist": "^3.0.0",
"markdown-it-mathjax3": "^4.3.2",
|