@jwilleke/ngdpbase-types 5.36.0 → 5.37.0

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.
@@ -48,7 +48,7 @@ export function openAddonTestDatabase(owner, options, declaredBy) {
48
48
  checkAddonDatabaseRequest(owner, options);
49
49
  if (declaredBy) {
50
50
  const { slug, defaults } = declaredBy;
51
- const layer = addonDatabaseLayer([{ slug, dir: '', source: 'directory', enabled: true, defaults }], {});
51
+ const layer = addonDatabaseLayer([{ slug, dir: '', enabled: true, defaults }], {});
52
52
  const refused = layer.refused.get(slug);
53
53
  if (refused)
54
54
  throw new Error(`Add-on '${slug}' is not loaded: ${refused}`);
@@ -342,8 +342,6 @@ declare class AddonsManager extends BaseManager {
342
342
  private addonsPaths;
343
343
  /** Resolved absolute paths to addons directories */
344
344
  private resolvedAddonsPaths;
345
- /** #673: npm package globs (from `node_modules:` addons-path entries) to discover from node_modules. */
346
- private npmAddonPatterns;
347
345
  /** #1830: `<data>/addons`, where installed add-ons live; null when there is no data folder. */
348
346
  private installedDir;
349
347
  /** #1830: the installed add-ons verified this boot — the only ones loaded from `installedDir`. */
@@ -461,25 +459,10 @@ declare class AddonsManager extends BaseManager {
461
459
  * Scan a single addons directory and register any add-ons found.
462
460
  */
463
461
  private scanAddonsDirectory;
464
- /**
465
- * #673 (packaged model): discover addons published as npm packages, matching a
466
- * `node_modules:<glob>` addons-path entry (e.g. `node_modules:@jwilleke/*-addon`).
467
- * Each matching package is loaded through the same slug/module/register()
468
- * contract as bundled and drop-in addons — only discovery differs.
469
- */
470
- private scanNpmAddons;
471
- /** Locate the node_modules directory (app root / cwd). */
472
- private findNodeModules;
473
- /** Expand a `@scope/glob` (or bare `glob`) pattern to package directories. */
474
- private resolveNpmAddonDirs;
475
462
  /**
476
463
  * Load a single addon from its directory (index.js/ts + optional package.json
477
- * `ngdpbase` manifest) and register it. Shared by directory scans and npm
478
- * (#673) discovery; `sourceLabel` is for logging only. `source` selects the
479
- * no-slug identity fallback (#927): a directory addon's folder name is its
480
- * identity verbatim, while an npm package strips the conventional `-addon`
481
- * suffix. Duplicate ids are skipped — since npm discovery runs last, a
482
- * bundled/drop-in addon wins.
464
+ * `ngdpbase` manifest) and register it. `sourceLabel` is for logging only.
465
+ * Duplicate ids are skipped, so the first directory scanned wins.
483
466
  */
484
467
  private registerAddonFromDir;
485
468
  /**
@@ -141,16 +141,15 @@ declare class ConfigurationManager extends BaseManager {
141
141
  /**
142
142
  * #672: refuse to start if any `ngdpbase.addons.<id>.enabled = true` key
143
143
  * references an `<id>` that has no matching addon in any configured
144
- * `addons-path` entry — either a directory (bundled/drop-in) or, per
145
- * #673/#924, an npm package matched by a `node_modules:<glob>` entry.
144
+ * `addons-path` directory (bundled/drop-in) and is not named in
145
+ * `ngdpbase.addons.install`. A retired `node_modules:` entry is ignored (#2102).
146
146
  * The discovery logic mirrors what `AddonsManager` does at runtime —
147
147
  * directory/package present with `index.js` or `index.ts` — but without
148
148
  * importing the modules (boot-time speed; `ConfigurationManager.initialize()`
149
- * runs before any managers). The directory-vs-npm-pattern split, npm glob
150
- * matching, AND the canonical-identity resolution are all shared with
149
+ * runs before any managers). The addons-path parsing AND the
150
+ * canonical-identity resolution are shared with
151
151
  * `AddonsManager` via `utils/addonsPathResolver.ts` so the two can no
152
- * longer drift out of sync (#924: they had, and packaged addons could
153
- * never boot as a result).
152
+ * longer drift out of sync (#924).
154
153
  *
155
154
  * Catches the failure mode that caused the 2026-05-10 geohazardwatch.com
156
155
  * outage: the deploy configmap had `ngdpbase.addons.ve-geology.enabled =
@@ -18,8 +18,8 @@
18
18
  * shipped entry. The operator's file still wins over everything.
19
19
  *
20
20
  * Discovery follows `ngdpbase.managers.addons-manager.addons-path` exactly as
21
- * AddonsManager does — directories and `node_modules:` patterns — so bundled
22
- * and external addons are treated alike. Only an addon whose
21
+ * AddonsManager does — directory entries — so bundled
22
+ * and drop-in addons are treated alike. Only an addon whose
23
23
  * `ngdpbase.addons.<slug>.enabled` is true in the shipped+custom view
24
24
  * contributes; a disabled addon's opinions do not reach the catalog.
25
25
  *
@@ -63,7 +63,6 @@ export declare const ADDONS_MANAGER_ENABLED_KEY = "ngdpbase.managers.addons-mana
63
63
  export interface AddonDefaults {
64
64
  slug: string;
65
65
  dir: string;
66
- source: 'directory' | 'npm';
67
66
  /**
68
67
  * Whether the addon is switched on. A turned-off addon's file is read for its
69
68
  * system-categories alone (#1559); none of its other keys reach the merge.
@@ -83,7 +82,6 @@ export declare function isAddonEnabled(base: Record<string, unknown>, slug: stri
83
82
  */
84
83
  export declare function discoverAddonDirs(base: Record<string, unknown>, cwd?: string, dataFolder?: string): Array<{
85
84
  dir: string;
86
- source: 'directory' | 'npm';
87
85
  }>;
88
86
  /**
89
87
  * The defaults of every installed addon, enabled or not, in discovery order,
@@ -18,8 +18,8 @@
18
18
  * shipped entry. The operator's file still wins over everything.
19
19
  *
20
20
  * Discovery follows `ngdpbase.managers.addons-manager.addons-path` exactly as
21
- * AddonsManager does — directories and `node_modules:` patterns — so bundled
22
- * and external addons are treated alike. Only an addon whose
21
+ * AddonsManager does — directory entries — so bundled
22
+ * and drop-in addons are treated alike. Only an addon whose
23
23
  * `ngdpbase.addons.<slug>.enabled` is true in the shipped+custom view
24
24
  * contributes; a disabled addon's opinions do not reach the catalog.
25
25
  *
@@ -61,7 +61,7 @@ import fs from 'fs';
61
61
  import path from 'path';
62
62
  import { isDeepStrictEqual } from 'util';
63
63
  import { configFilePaths, deepMergeConfigs, deepMergeObjects, instanceDataFolder, isPlainObject, readConfigFilesSync } from './configFiles.js';
64
- import { configuredInstallSlugs, findNodeModulesDir, installedAddonsDir, matchNpmPackageDirs, resolveAddonSlug, splitAddonsPath } from './addonsPathResolver.js';
64
+ import { configuredInstallSlugs, installedAddonsDir, resolveAddonSlug, splitAddonsPath } from './addonsPathResolver.js';
65
65
  import { lockedAddonDirsSync } from './addonLock.js';
66
66
  import { LEGACY_SECRET_KEYS_KEY, SENSITIVE_VALUES_KEY } from './sensitiveValues.js';
67
67
  import { parsePrivatestore } from './privateStorePath.js';
@@ -81,7 +81,7 @@ export function isAddonEnabled(base, slug) {
81
81
  export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
82
82
  if (base[ADDONS_MANAGER_ENABLED_KEY] === false)
83
83
  return [];
84
- const { directories, npmPatterns } = splitAddonsPath(base[ADDONS_PATH_KEY] ?? './addons');
84
+ const { directories } = splitAddonsPath(base[ADDONS_PATH_KEY] ?? './addons');
85
85
  const found = [];
86
86
  for (const raw of directories) {
87
87
  const root = path.resolve(cwd, raw);
@@ -95,20 +95,13 @@ export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instan
95
95
  for (const entry of entries) {
96
96
  if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'shared')
97
97
  continue;
98
- found.push({ dir: path.join(root, entry.name), source: 'directory' });
98
+ found.push({ dir: path.join(root, entry.name) });
99
99
  }
100
100
  }
101
101
  const installed = installedAddonsDir(dataFolder, cwd);
102
102
  if (!found.some((f) => path.dirname(f.dir) === installed)) {
103
103
  for (const dir of lockedAddonDirsSync(installed, configuredInstallSlugs(base)))
104
- found.push({ dir, source: 'directory' });
105
- }
106
- const nmRoot = npmPatterns.length > 0 ? findNodeModulesDir(cwd) : null;
107
- if (nmRoot) {
108
- for (const pattern of npmPatterns) {
109
- for (const dir of matchNpmPackageDirs(nmRoot, pattern))
110
- found.push({ dir, source: 'npm' });
111
- }
104
+ found.push({ dir });
112
105
  }
113
106
  return found;
114
107
  }
@@ -122,8 +115,8 @@ export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instan
122
115
  */
123
116
  export function discoverAddonDefaults(base, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
124
117
  const out = [];
125
- for (const { dir, source } of discoverAddonDirs(base, cwd, dataFolder)) {
126
- const slug = resolveAddonSlug(dir, source);
118
+ for (const { dir } of discoverAddonDirs(base, cwd, dataFolder)) {
119
+ const slug = resolveAddonSlug(dir);
127
120
  const enabled = isAddonEnabled(base, slug);
128
121
  const file = path.join(dir, 'config', 'default-config.json');
129
122
  if (!fs.existsSync(file))
@@ -136,10 +129,10 @@ export function discoverAddonDefaults(base, cwd = process.cwd(), dataFolder = in
136
129
  for (const [key, value] of Object.entries(raw))
137
130
  if (!key.startsWith('_'))
138
131
  defaults[key] = value;
139
- out.push({ slug, dir, source, enabled, defaults });
132
+ out.push({ slug, dir, enabled, defaults });
140
133
  }
141
134
  catch (err) {
142
- out.push({ slug, dir, source, enabled, defaults: {}, error: err instanceof Error ? err.message : String(err) });
135
+ out.push({ slug, dir, enabled, defaults: {}, error: err instanceof Error ? err.message : String(err) });
143
136
  }
144
137
  }
145
138
  return out;
@@ -1,30 +1,26 @@
1
1
  /**
2
2
  * Shared parsing/discovery for the `ngdpbase.managers.addons-manager.addons-path`
3
- * config value.
3
+ * config value, used by `AddonsManager` (runtime discovery), `ConfigurationManager`
4
+ * (the #672 boot-time "does this enabled addon exist" check) and the add-on
5
+ * config layer, so the three cannot drift (#924).
4
6
  *
5
- * #924: `AddonsManager` (real runtime discovery) and `ConfigurationManager`
6
- * (the #672 boot-time "does this enabled addon exist" safety check) used to
7
- * each carry their own copy of this logic. They drifted: `AddonsManager` was
8
- * updated for #673's `node_modules:<glob>` packaged-addon syntax, but the
9
- * validator's copy was not, so it resolved a `node_modules:` entry as a
10
- * literal (non-existent) directory and treated every packaged addon as
11
- * "unknown" — crashing boot the moment one was enabled. Consolidating the
12
- * parsing/matching into one module removes the class of bug, not just this
13
- * instance of it.
7
+ * #2102: the packaged model (`node_modules:<glob>` entries) is gone. A site
8
+ * config may still hold such an entry; it is ignored, never treated as a
9
+ * directory, and never stops the boot.
14
10
  */
15
- /** Prefix marking an addons-path entry as an npm-package glob rather than a directory. */
16
- export declare const NPM_ADDON_PREFIX = "node_modules:";
17
11
  export interface SplitAddonsPath {
18
12
  /** Plain directory entries (bundled/drop-in), not yet resolved to absolute. */
19
13
  directories: string[];
20
- /** npm package name globs (the part after `node_modules:`), e.g. `@jwilleke/*-addon`. */
21
- npmPatterns: string[];
14
+ /** Retired `node_modules:` entries (#2102): ignored; AddonsManager warns about them once at start. */
15
+ retired: string[];
22
16
  }
23
17
  /**
24
18
  * Coerce the raw config value (string or string[]) and split it into
25
- * filesystem directory entries vs. `node_modules:<glob>` npm patterns.
19
+ * filesystem directory entries and retired `node_modules:` entries.
26
20
  */
27
21
  export declare function splitAddonsPath(raw: unknown): SplitAddonsPath;
22
+ /** The warning for one retired `node_modules:` entry (#2102). */
23
+ export declare function retiredAddonsPathWarning(entry: string): string;
28
24
  /** The config key naming the add-ons to install (#1830). */
29
25
  export declare const ADDONS_INSTALL_KEY = "ngdpbase.addons.install";
30
26
  /**
@@ -35,42 +31,15 @@ export declare const ADDONS_INSTALL_KEY = "ngdpbase.addons.install";
35
31
  export declare function installedAddonsDir(dataFolder: string, cwd?: string): string;
36
32
  /** The slugs `ngdpbase.addons.install` names, from a merged or shipped+custom view. */
37
33
  export declare function configuredInstallSlugs(config: Record<string, unknown>): string[];
38
- /** Locate the `node_modules` directory to search for packaged addons (resolved from cwd). */
39
- export declare function findNodeModulesDir(cwd?: string): string | null;
40
- /**
41
- * Expand a `@scope/glob` (or bare `glob`) npm pattern to matching package
42
- * directories under `nmRoot`.
43
- */
44
- export declare function matchNpmPackageDirs(nmRoot: string, pattern: string): string[];
45
- /**
46
- * Best-effort addon identity for a packaged (npm) addon directory, derived
47
- * from its package-directory name alone — no module import, matching the
48
- * same boot-time-speed, no-module-loading design already accepted for the
49
- * directory-scan case (see `ConfigurationManager.assertConfiguredAddonsExist`).
50
- *
51
- * Follows the documented packaged-addon naming convention
52
- * (`@scope/<slug>-addon`, see docs/platform/deployment/addon-packaged.md):
53
- * strips a trailing `-addon` suffix if present, else returns the folder name
54
- * as-is.
55
- *
56
- * Caveat (same class as the existing directory-name heuristic): the addon's
57
- * *real* identity is whatever its module exports as `name` at runtime. A
58
- * package that doesn't follow the `-addon` naming convention, or whose
59
- * module exports a different `name`, won't be matched precisely here — this
60
- * is a validation-time approximation, not the source of truth.
61
- */
62
- export declare function deriveAddonSlugFromPackageDirName(dirName: string): string;
63
34
  /**
64
35
  * Canonical identity for an addon directory (#927), resolved the SAME
65
36
  * import-free way in every layer (discovery, `isEnabled`, the boot-time
66
- * validator) so the four historical identities (folder name, module `name`,
67
- * config key, manifest slug) can no longer drift:
37
+ * validator):
68
38
  *
69
- * canonicalId = package.json `ngdpbase.slug` ?? deriveAddonSlugFromPackageDirName(folder)
39
+ * canonicalId = package.json `ngdpbase.slug` ?? the folder name verbatim
70
40
  *
71
- * Reads `package.json` statically — never imports the module — which is what
72
- * lets the boot validator compute the exact id the runtime will register
73
- * under, instead of guessing. The module's exported `name` is a display
74
- * label validated against this at load time, not a source of identity.
41
+ * Reads `package.json` statically — never imports the module. The module's
42
+ * exported `name` is a display label validated against this at load time,
43
+ * not a source of identity.
75
44
  */
76
- export declare function resolveAddonSlug(addonDir: string, source: 'directory' | 'npm'): string;
45
+ export declare function resolveAddonSlug(addonDir: string): string;
@@ -1,36 +1,32 @@
1
1
  /**
2
2
  * Shared parsing/discovery for the `ngdpbase.managers.addons-manager.addons-path`
3
- * config value.
3
+ * config value, used by `AddonsManager` (runtime discovery), `ConfigurationManager`
4
+ * (the #672 boot-time "does this enabled addon exist" check) and the add-on
5
+ * config layer, so the three cannot drift (#924).
4
6
  *
5
- * #924: `AddonsManager` (real runtime discovery) and `ConfigurationManager`
6
- * (the #672 boot-time "does this enabled addon exist" safety check) used to
7
- * each carry their own copy of this logic. They drifted: `AddonsManager` was
8
- * updated for #673's `node_modules:<glob>` packaged-addon syntax, but the
9
- * validator's copy was not, so it resolved a `node_modules:` entry as a
10
- * literal (non-existent) directory and treated every packaged addon as
11
- * "unknown" — crashing boot the moment one was enabled. Consolidating the
12
- * parsing/matching into one module removes the class of bug, not just this
13
- * instance of it.
7
+ * #2102: the packaged model (`node_modules:<glob>` entries) is gone. A site
8
+ * config may still hold such an entry; it is ignored, never treated as a
9
+ * directory, and never stops the boot.
14
10
  */
15
11
  import * as fs from 'fs';
16
12
  import * as path from 'path';
17
- import { minimatch } from 'minimatch';
18
- /** Prefix marking an addons-path entry as an npm-package glob rather than a directory. */
19
- export const NPM_ADDON_PREFIX = 'node_modules:';
13
+ /** The retired packaged-model prefix, recognised only so the entry can be ignored with a warning. */
14
+ const RETIRED_PACKAGED_PREFIX = 'node_modules:';
20
15
  /**
21
16
  * Coerce the raw config value (string or string[]) and split it into
22
- * filesystem directory entries vs. `node_modules:<glob>` npm patterns.
17
+ * filesystem directory entries and retired `node_modules:` entries.
23
18
  */
24
19
  export function splitAddonsPath(raw) {
25
20
  const entries = Array.isArray(raw) ? raw.map(String) : [String(raw)];
26
21
  return {
27
- directories: entries.filter(p => !p.startsWith(NPM_ADDON_PREFIX)),
28
- npmPatterns: entries
29
- .filter(p => p.startsWith(NPM_ADDON_PREFIX))
30
- .map(p => p.slice(NPM_ADDON_PREFIX.length).trim())
31
- .filter(Boolean)
22
+ directories: entries.filter(p => !p.trim().startsWith(RETIRED_PACKAGED_PREFIX)),
23
+ retired: entries.filter(p => p.trim().startsWith(RETIRED_PACKAGED_PREFIX))
32
24
  };
33
25
  }
26
+ /** The warning for one retired `node_modules:` entry (#2102). */
27
+ export function retiredAddonsPathWarning(entry) {
28
+ return `addons-path entry '${entry}' is no longer supported and is ignored; install add-ons with ngdpbase.addons.install`;
29
+ }
34
30
  /** The config key naming the add-ons to install (#1830). */
35
31
  export const ADDONS_INSTALL_KEY = 'ngdpbase.addons.install';
36
32
  /**
@@ -46,69 +42,18 @@ export function configuredInstallSlugs(config) {
46
42
  const map = config[ADDONS_INSTALL_KEY];
47
43
  return map && typeof map === 'object' && !Array.isArray(map) ? Object.keys(map) : [];
48
44
  }
49
- /** Locate the `node_modules` directory to search for packaged addons (resolved from cwd). */
50
- export function findNodeModulesDir(cwd = process.cwd()) {
51
- const nm = path.resolve(cwd, 'node_modules');
52
- return fs.existsSync(nm) && fs.statSync(nm).isDirectory() ? nm : null;
53
- }
54
- /**
55
- * Expand a `@scope/glob` (or bare `glob`) npm pattern to matching package
56
- * directories under `nmRoot`.
57
- */
58
- export function matchNpmPackageDirs(nmRoot, pattern) {
59
- let baseDir = nmRoot;
60
- let nameGlob = pattern;
61
- if (pattern.startsWith('@')) {
62
- const slash = pattern.indexOf('/');
63
- const scope = slash > 0 ? pattern.slice(0, slash) : pattern;
64
- nameGlob = slash > 0 ? pattern.slice(slash + 1) : '*';
65
- baseDir = path.join(nmRoot, scope);
66
- }
67
- if (!fs.existsSync(baseDir))
68
- return [];
69
- const out = [];
70
- for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
71
- if (entry.isDirectory() && !entry.name.startsWith('.') && minimatch(entry.name, nameGlob)) {
72
- out.push(path.join(baseDir, entry.name));
73
- }
74
- }
75
- return out;
76
- }
77
- /**
78
- * Best-effort addon identity for a packaged (npm) addon directory, derived
79
- * from its package-directory name alone — no module import, matching the
80
- * same boot-time-speed, no-module-loading design already accepted for the
81
- * directory-scan case (see `ConfigurationManager.assertConfiguredAddonsExist`).
82
- *
83
- * Follows the documented packaged-addon naming convention
84
- * (`@scope/<slug>-addon`, see docs/platform/deployment/addon-packaged.md):
85
- * strips a trailing `-addon` suffix if present, else returns the folder name
86
- * as-is.
87
- *
88
- * Caveat (same class as the existing directory-name heuristic): the addon's
89
- * *real* identity is whatever its module exports as `name` at runtime. A
90
- * package that doesn't follow the `-addon` naming convention, or whose
91
- * module exports a different `name`, won't be matched precisely here — this
92
- * is a validation-time approximation, not the source of truth.
93
- */
94
- export function deriveAddonSlugFromPackageDirName(dirName) {
95
- const suffix = '-addon';
96
- return dirName.endsWith(suffix) ? dirName.slice(0, -suffix.length) : dirName;
97
- }
98
45
  /**
99
46
  * Canonical identity for an addon directory (#927), resolved the SAME
100
47
  * import-free way in every layer (discovery, `isEnabled`, the boot-time
101
- * validator) so the four historical identities (folder name, module `name`,
102
- * config key, manifest slug) can no longer drift:
48
+ * validator):
103
49
  *
104
- * canonicalId = package.json `ngdpbase.slug` ?? deriveAddonSlugFromPackageDirName(folder)
50
+ * canonicalId = package.json `ngdpbase.slug` ?? the folder name verbatim
105
51
  *
106
- * Reads `package.json` statically — never imports the module — which is what
107
- * lets the boot validator compute the exact id the runtime will register
108
- * under, instead of guessing. The module's exported `name` is a display
109
- * label validated against this at load time, not a source of identity.
52
+ * Reads `package.json` statically — never imports the module. The module's
53
+ * exported `name` is a display label validated against this at load time,
54
+ * not a source of identity.
110
55
  */
111
- export function resolveAddonSlug(addonDir, source) {
56
+ export function resolveAddonSlug(addonDir) {
112
57
  const dirName = path.basename(addonDir);
113
58
  const pkgPath = path.join(addonDir, 'package.json');
114
59
  try {
@@ -118,12 +63,7 @@ export function resolveAddonSlug(addonDir, source) {
118
63
  return slug.trim();
119
64
  }
120
65
  catch {
121
- /* no/invalid package.json — fall back to the folder-derived id */
66
+ /* no/invalid package.json — fall back to the folder name */
122
67
  }
123
- // Fallback when no slug is declared. npm packages follow the
124
- // `@scope/<slug>-addon` publishing convention, so strip the conventional
125
- // trailing `-addon`. A plain directory (bundled/drop-in) addon's folder
126
- // name IS its identity verbatim — an addon may legitimately be named
127
- // `something-addon` on disk — so never strip there.
128
- return source === 'npm' ? deriveAddonSlugFromPackageDirName(dirName) : dirName;
68
+ return dirName;
129
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwilleke/ngdpbase-types",
3
- "version": "5.36.0",
3
+ "version": "5.37.0",
4
4
  "description": "TypeScript types for ngdpbase add-ons, and a test kit of core's own values. A dev dependency, versioned with ngdpbase.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Jim Willeke",
@@ -41,15 +41,14 @@
41
41
  "@types/multer": "^2.3.0",
42
42
  "@types/node": "^24.19.2",
43
43
  "better-sqlite3-multiple-ciphers": "^13.0.3",
44
- "dotenv": "^18.0.8",
44
+ "dotenv": "^18.0.9",
45
45
  "express": "^5.3.0",
46
46
  "express-session": "^1.19.0",
47
47
  "fs-extra": "^11.4.1",
48
48
  "ipaddr.js": "^2.5.0",
49
- "jose": "^6.2.12",
50
- "linkedom": "^0.18.13",
49
+ "jose": "^6.2.13",
50
+ "linkedom": "^0.18.14",
51
51
  "markdown-it": "^14.3.3",
52
- "minimatch": "^10.2.6",
53
52
  "minisearch": "7.2.0",
54
53
  "multer": "^2.4.0",
55
54
  "rrule-temporal": "2.2.8",