@jwilleke/ngdpbase-types 5.19.0 → 5.20.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.
- package/lib/src/managers/AddonsManager.d.ts +31 -0
- package/lib/src/managers/AuthManager.d.ts +10 -1
- package/lib/src/managers/ConfigurationManager.d.ts +11 -1
- package/lib/src/managers/DatabaseManager.js +1 -1
- package/lib/src/types/WikiEngine.d.ts +12 -1
- package/lib/src/utils/addonConfigLayer.d.ts +32 -1
- package/lib/src/utils/addonConfigLayer.js +62 -8
- package/package.json +1 -1
|
@@ -17,6 +17,11 @@ import type { WikiEngine } from '../types/WikiEngine.js';
|
|
|
17
17
|
import type { User } from '../types/User.js';
|
|
18
18
|
import { type StoreOwnerState } from '../utils/privateStoreDoor.js';
|
|
19
19
|
import { type AddonHost } from '../utils/addonHost.js';
|
|
20
|
+
/**
|
|
21
|
+
* #1883: what visitors see while a site-critical add-on that did not load
|
|
22
|
+
* holds the site in maintenance mode. Empty leaves only the page's heading.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SITE_CRITICAL_PUBLIC_MESSAGE_KEY = "ngdpbase.managers.addons-manager.site-critical.public-message";
|
|
20
25
|
/**
|
|
21
26
|
* Type passed to `AddonModule.profileSection()` (#534). Matches what
|
|
22
27
|
* `UserManager.getUser()` returns at runtime — `User` minus the password
|
|
@@ -280,6 +285,8 @@ declare class AddonsManager extends BaseManager {
|
|
|
280
285
|
private dashboardCards;
|
|
281
286
|
/** Name of the first domain addon loaded — only one is permitted */
|
|
282
287
|
private domainAddonName;
|
|
288
|
+
/** #1883: the enabled add-ons that did not load this start, with why — each reported once. */
|
|
289
|
+
private readonly loadFailures;
|
|
283
290
|
constructor(engine: WikiEngine);
|
|
284
291
|
/**
|
|
285
292
|
* Initialize the AddonsManager
|
|
@@ -351,6 +358,30 @@ declare class AddonsManager extends BaseManager {
|
|
|
351
358
|
* bundled/drop-in addon wins.
|
|
352
359
|
*/
|
|
353
360
|
private registerAddonFromDir;
|
|
361
|
+
/**
|
|
362
|
+
* Register an add-on that will not be loaded, so Admin → Add-ons shows it
|
|
363
|
+
* with the reason, and report it when it is enabled (#1883). The first
|
|
364
|
+
* add-on of a slug wins, as for a loadable one.
|
|
365
|
+
*/
|
|
366
|
+
private refuseAddon;
|
|
367
|
+
/**
|
|
368
|
+
* #1883 (operator, 2026-10-18): the one rule for an enabled add-on that does
|
|
369
|
+
* not load, whatever the reason. Admins get a notification at the most
|
|
370
|
+
* severe level naming it and the reason, which says how to fix it where the
|
|
371
|
+
* reason does. A site-critical add-on (`ngdpbase.addons.<slug>.site-critical`)
|
|
372
|
+
* also holds the whole site in maintenance mode through core's survivable
|
|
373
|
+
* path (#1152): visitors see only the configured public message, with no
|
|
374
|
+
* details, and administrators can still sign in and reach /admin. Once per
|
|
375
|
+
* add-on per start. Nothing is opened in its place.
|
|
376
|
+
*/
|
|
377
|
+
private recordLoadFailure;
|
|
378
|
+
/**
|
|
379
|
+
* #1883: an enabled add-on that discovery never found — its install failed
|
|
380
|
+
* (#1830), or only a stray folder answers it — did not load either.
|
|
381
|
+
*/
|
|
382
|
+
private reportUndiscoveredAddons;
|
|
383
|
+
/** A notification to administrators; best-effort, like recordSeedFailure. */
|
|
384
|
+
private notifyAdmins;
|
|
354
385
|
/**
|
|
355
386
|
* Why an addon cannot run on this core (#1757): its manifest's `minVersion`
|
|
356
387
|
* is newer than this release. Null when it can, or when it states none.
|
|
@@ -1052,6 +1052,15 @@ declare class AuthManager extends BaseManager {
|
|
|
1052
1052
|
unlockVaults(username: string, password: string): Promise<string | undefined>;
|
|
1053
1053
|
private vaultUnlocker;
|
|
1054
1054
|
private warnedNoUnlocker;
|
|
1055
|
+
/**
|
|
1056
|
+
* The stored spelling of a username a sign-in brought (#1882), or null when
|
|
1057
|
+
* no account has it. Lookups compare normalized (#1436), so "ADMIN" finds
|
|
1058
|
+
* admin's record; the session, the audit record, the roles and the vault
|
|
1059
|
+
* paths must carry the stored name, never the typed variant, which is a
|
|
1060
|
+
* roleless twin. The one rule, asked by every door a sign-in passes:
|
|
1061
|
+
* `authenticate`, `beginTwoStep` and `completeSignIn`.
|
|
1062
|
+
*/
|
|
1063
|
+
private storedUsername;
|
|
1055
1064
|
/** A fresh session for a sign-in (#1043); the one already on it ends first (#1626, #1670). */
|
|
1056
1065
|
private regenerate;
|
|
1057
1066
|
/**
|
|
@@ -1109,7 +1118,7 @@ declare class AuthManager extends BaseManager {
|
|
|
1109
1118
|
ip?: string;
|
|
1110
1119
|
userAgent?: string;
|
|
1111
1120
|
privateStoreHandle?: string;
|
|
1112
|
-
}): string
|
|
1121
|
+
}): Promise<string>;
|
|
1113
1122
|
/** The pending sign-in, for the browser that started it; null when gone, expired, or another browser. */
|
|
1114
1123
|
pendingSignIn(handle: string | undefined, binding: string | undefined): PendingSignIn | null;
|
|
1115
1124
|
/**
|
|
@@ -60,10 +60,12 @@ declare class ConfigurationManager extends BaseManager {
|
|
|
60
60
|
private addonLayers;
|
|
61
61
|
/** #1559: add-on system-categories refused, and operator values an owner's declaration overrode; #1860: database declarations refused or overridden. */
|
|
62
62
|
private systemCategoryProblems;
|
|
63
|
-
/** #1559: add-ons that must not load because an entry they declare was refused, by slug, with why; #1860: or their database declaration. */
|
|
63
|
+
/** #1559: add-ons that must not load because an entry they declare was refused, by slug, with why; #1860: or their database declaration; #1883: or a site-critical value that is not a boolean. */
|
|
64
64
|
private refusedAddons;
|
|
65
65
|
/** #1860: every installed add-on's sound database declaration, on or off. */
|
|
66
66
|
private addonDatabases;
|
|
67
|
+
/** #1883: which add-ons put the site in maintenance mode when they fail to load, by slug. */
|
|
68
|
+
private siteCriticalAddons;
|
|
67
69
|
private environment;
|
|
68
70
|
private defaultConfigPath;
|
|
69
71
|
private customConfigPath;
|
|
@@ -282,6 +284,14 @@ declare class ConfigurationManager extends BaseManager {
|
|
|
282
284
|
* ({@link getAddonRefusal}). DatabaseManager is the reader.
|
|
283
285
|
*/
|
|
284
286
|
getAddonDatabaseDeclarations(): readonly AddonDatabaseDeclaration[];
|
|
287
|
+
/**
|
|
288
|
+
* #1883: whether this add-on's failure to load puts the whole site in
|
|
289
|
+
* maintenance mode — `ngdpbase.addons.<slug>.site-critical`, from the
|
|
290
|
+
* operator's file or else the add-on's own, read even when the add-on was
|
|
291
|
+
* refused. A value that is not exactly true or false counts as true (and
|
|
292
|
+
* refuses the add-on, {@link getAddonRefusal}).
|
|
293
|
+
*/
|
|
294
|
+
isAddonSiteCritical(slug: string): boolean;
|
|
285
295
|
/**
|
|
286
296
|
* #1813: what one enabled addon's own `config/default-config.json` declares,
|
|
287
297
|
* exactly as it was folded into the merge — not the merged view, so the
|
|
@@ -255,7 +255,7 @@ class DatabaseManager extends BaseManager {
|
|
|
255
255
|
static keyRefusal(declaration, key) {
|
|
256
256
|
if (declaration.encrypt !== 'required' || key)
|
|
257
257
|
return null;
|
|
258
|
-
return `Add-on '${declaration.declaredBy}' declares database '${declaration.owner}' with encrypt "required", but ${DATABASE_KEY_ENV} is not set — it is never opened unencrypted (#1860)`;
|
|
258
|
+
return `Add-on '${declaration.declaredBy}' declares database '${declaration.owner}' with encrypt "required", but ${DATABASE_KEY_ENV} is not set — it is never opened unencrypted. Set ${DATABASE_KEY_ENV} in the environment or the instance .env (<FAST_STORAGE>/.env) and restart (#1860, #1883)`;
|
|
259
259
|
}
|
|
260
260
|
// ---- Backup and restore (#1723) ------------------------------------------
|
|
261
261
|
/**
|
|
@@ -102,10 +102,21 @@ export interface WikiEngine {
|
|
|
102
102
|
* the repair, instead of exiting and leaving the filesystem as the only way
|
|
103
103
|
* back. Only for values an administrator can fix through the admin UI; a
|
|
104
104
|
* failure of the machinery needed to serve that UI is fatal and still throws.
|
|
105
|
+
* Also a site-critical add-on that did not load (#1883, operator
|
|
106
|
+
* 2026-10-18): its fix may lie outside the UI (a key in the instance .env),
|
|
107
|
+
* and the admin notification says so.
|
|
105
108
|
*/
|
|
106
|
-
blockConfiguration(reason: string
|
|
109
|
+
blockConfiguration(reason: string, options?: {
|
|
110
|
+
publicMessage?: string;
|
|
111
|
+
}): void;
|
|
107
112
|
/** Configuration values that could not be used, in the order found (#1152). */
|
|
108
113
|
getBlockingConditions(): readonly string[];
|
|
114
|
+
/**
|
|
115
|
+
* What visitors see in place of the reasons, when a reason was recorded with
|
|
116
|
+
* a `publicMessage` (#1883: a site-critical add-on that did not load); null
|
|
117
|
+
* when every reason may be shown.
|
|
118
|
+
*/
|
|
119
|
+
getPublicBlockingMessage(): string | null;
|
|
109
120
|
/**
|
|
110
121
|
* Get all registered manager names
|
|
111
122
|
* @returns Array of manager names
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
* never merged into the configuration: DatabaseManager reads the declarations
|
|
41
41
|
* from here, so the operator's file cannot change one. An invalid declaration
|
|
42
42
|
* stops its addon loading, as a refused system-category does.
|
|
43
|
+
*
|
|
44
|
+
* A third, `ngdpbase.addons.<slug>.site-critical` (#1883): whether a failure of
|
|
45
|
+
* the addon to load puts the whole site in maintenance mode. Read from the
|
|
46
|
+
* addon's own file and from the operator's, which wins either way, and kept
|
|
47
|
+
* apart from the merge so a refused addon's value is still known. A value that
|
|
48
|
+
* is not exactly `true` or `false` refuses the addon and counts as `true`.
|
|
43
49
|
*/
|
|
44
50
|
import { type AddonDatabaseDeclaration } from './addonDatabaseRules.js';
|
|
45
51
|
export declare const ADDONS_PATH_KEY = "ngdpbase.managers.addons-manager.addons-path";
|
|
@@ -78,6 +84,8 @@ export declare function discoverAddonDirs(base: Record<string, unknown>, cwd?: s
|
|
|
78
84
|
* rather than silently skip it.
|
|
79
85
|
*/
|
|
80
86
|
export declare function discoverAddonDefaults(base: Record<string, unknown>, cwd?: string, dataFolder?: string): AddonDefaults[];
|
|
87
|
+
/** How a problem line names an addon; AddonsManager matches it to fold a refusal into its load-failure notice (#1883). */
|
|
88
|
+
export declare function addonProblemSubject(slug: string, enabled: boolean): string;
|
|
81
89
|
export interface AddonDatabaseLayer {
|
|
82
90
|
/** Every sound declaration, of every installed addon, `enabled` as its addon is. */
|
|
83
91
|
declarations: AddonDatabaseDeclaration[];
|
|
@@ -92,6 +100,25 @@ export interface AddonDatabaseLayer {
|
|
|
92
100
|
* file: a value there for a declaration's key is ignored, and said so.
|
|
93
101
|
*/
|
|
94
102
|
export declare function addonDatabaseLayer(addons: readonly AddonDefaults[], custom: Record<string, unknown>): AddonDatabaseLayer;
|
|
103
|
+
/** The configuration key that makes an addon site-critical (#1883). */
|
|
104
|
+
export declare function addonSiteCriticalKey(slug: string): string;
|
|
105
|
+
export interface AddonSiteCriticalLayer {
|
|
106
|
+
/** Each addon with a value, by slug: true when its failure to load puts the site in maintenance mode. */
|
|
107
|
+
siteCritical: Map<string, boolean>;
|
|
108
|
+
/** Addons whose value is not exactly `true` or `false`, by slug, with why: never loaded. */
|
|
109
|
+
refused: Map<string, string>;
|
|
110
|
+
problems: string[];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Which addons are site-critical (#1883): the operator's value if the
|
|
114
|
+
* operator's file sets one, else the addon's own (only under its own slug),
|
|
115
|
+
* else not. The operator's file wins in both directions, as for every addon
|
|
116
|
+
* key. The value is exactly `true` or `false`; anything else (the string
|
|
117
|
+
* `"true"` included) refuses the addon and counts as site-critical, so a
|
|
118
|
+
* mistyped value fails closed. `custom` keys for an addon with no file, or
|
|
119
|
+
* one that is not installed, count too.
|
|
120
|
+
*/
|
|
121
|
+
export declare function addonSiteCriticalLayer(addons: readonly AddonDefaults[], custom: Record<string, unknown>): AddonSiteCriticalLayer;
|
|
95
122
|
/**
|
|
96
123
|
* shipped ⊕ addons ⊕ custom, in that order of precedence (custom wins), with
|
|
97
124
|
* the problems found in the addons' system-categories (#1559).
|
|
@@ -108,6 +135,7 @@ export declare function mergeWithAddonLayer<T extends Record<string, unknown>>(s
|
|
|
108
135
|
problems: string[];
|
|
109
136
|
refused: Map<string, string>;
|
|
110
137
|
databases: AddonDatabaseDeclaration[];
|
|
138
|
+
siteCritical: Map<string, boolean>;
|
|
111
139
|
};
|
|
112
140
|
/**
|
|
113
141
|
* The full merge for a caller with the two files already read (the manager,
|
|
@@ -119,7 +147,9 @@ export declare function mergeWithAddonLayer<T extends Record<string, unknown>>(s
|
|
|
119
147
|
* could not be read; `refused` names, by slug, each addon that must not load
|
|
120
148
|
* because an entry it declares was refused, and why (#1559), or because its
|
|
121
149
|
* database declaration was (#1860); `databases` is every sound database
|
|
122
|
-
* declaration of every installed addon, on or off (#1860)
|
|
150
|
+
* declaration of every installed addon, on or off (#1860); `siteCritical`
|
|
151
|
+
* says, by slug, which addons put the site in maintenance mode when they fail
|
|
152
|
+
* to load (#1883).
|
|
123
153
|
*/
|
|
124
154
|
export declare function mergeConfigWithAddons<T extends Record<string, unknown>>(shipped: T, custom: Partial<T>, cwd?: string, dataFolder?: string): {
|
|
125
155
|
merged: T;
|
|
@@ -128,6 +158,7 @@ export declare function mergeConfigWithAddons<T extends Record<string, unknown>>
|
|
|
128
158
|
problems: string[];
|
|
129
159
|
refused: Map<string, string>;
|
|
130
160
|
databases: AddonDatabaseDeclaration[];
|
|
161
|
+
siteCritical: Map<string, boolean>;
|
|
131
162
|
};
|
|
132
163
|
export interface MergedConfigFiles {
|
|
133
164
|
merged: Record<string, unknown>;
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
* never merged into the configuration: DatabaseManager reads the declarations
|
|
41
41
|
* from here, so the operator's file cannot change one. An invalid declaration
|
|
42
42
|
* stops its addon loading, as a refused system-category does.
|
|
43
|
+
*
|
|
44
|
+
* A third, `ngdpbase.addons.<slug>.site-critical` (#1883): whether a failure of
|
|
45
|
+
* the addon to load puts the whole site in maintenance mode. Read from the
|
|
46
|
+
* addon's own file and from the operator's, which wins either way, and kept
|
|
47
|
+
* apart from the merge so a refused addon's value is still known. A value that
|
|
48
|
+
* is not exactly `true` or `false` refuses the addon and counts as `true`.
|
|
43
49
|
*/
|
|
44
50
|
import fs from 'fs';
|
|
45
51
|
import path from 'path';
|
|
@@ -127,6 +133,10 @@ export function discoverAddonDefaults(base, cwd = process.cwd(), dataFolder = in
|
|
|
127
133
|
}
|
|
128
134
|
return out;
|
|
129
135
|
}
|
|
136
|
+
/** How a problem line names an addon; AddonsManager matches it to fold a refusal into its load-failure notice (#1883). */
|
|
137
|
+
export function addonProblemSubject(slug, enabled) {
|
|
138
|
+
return `add-on '${slug}'${enabled ? '' : ' (turned off)'}`;
|
|
139
|
+
}
|
|
130
140
|
function vaultIdOf(entry) {
|
|
131
141
|
return parsePrivatestore(entry?.storageLocation?.privatestore)?.vaultId;
|
|
132
142
|
}
|
|
@@ -154,7 +164,7 @@ function addonCategoryLayer(shipped, custom, addons) {
|
|
|
154
164
|
const declared = addon.defaults[SYSTEM_CATEGORY_KEY];
|
|
155
165
|
if (declared === undefined)
|
|
156
166
|
continue;
|
|
157
|
-
const who =
|
|
167
|
+
const who = addonProblemSubject(addon.slug, addon.enabled);
|
|
158
168
|
if (!isPlainObject(declared)) {
|
|
159
169
|
const reason = `${SYSTEM_CATEGORY_KEY} must be a map of entries`;
|
|
160
170
|
layer.problems.push(`${who}: ${reason}; it is refused and the add-on is not loaded`);
|
|
@@ -246,7 +256,7 @@ export function addonDatabaseLayer(addons, custom) {
|
|
|
246
256
|
}
|
|
247
257
|
}
|
|
248
258
|
if (reasons.length > 0) {
|
|
249
|
-
const who =
|
|
259
|
+
const who = addonProblemSubject(addon.slug, addon.enabled);
|
|
250
260
|
layer.problems.push(`${who}: its database declaration is refused (${reasons.join('; ')}); the add-on is not loaded`);
|
|
251
261
|
layer.refused.set(addon.slug, `its database declaration was refused: ${reasons.join('; ')}`);
|
|
252
262
|
continue;
|
|
@@ -262,6 +272,45 @@ export function addonDatabaseLayer(addons, custom) {
|
|
|
262
272
|
}
|
|
263
273
|
return layer;
|
|
264
274
|
}
|
|
275
|
+
/** The configuration key that makes an addon site-critical (#1883). */
|
|
276
|
+
export function addonSiteCriticalKey(slug) {
|
|
277
|
+
return `ngdpbase.addons.${slug}.site-critical`;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Which addons are site-critical (#1883): the operator's value if the
|
|
281
|
+
* operator's file sets one, else the addon's own (only under its own slug),
|
|
282
|
+
* else not. The operator's file wins in both directions, as for every addon
|
|
283
|
+
* key. The value is exactly `true` or `false`; anything else (the string
|
|
284
|
+
* `"true"` included) refuses the addon and counts as site-critical, so a
|
|
285
|
+
* mistyped value fails closed. `custom` keys for an addon with no file, or
|
|
286
|
+
* one that is not installed, count too.
|
|
287
|
+
*/
|
|
288
|
+
export function addonSiteCriticalLayer(addons, custom) {
|
|
289
|
+
const layer = { siteCritical: new Map(), refused: new Map(), problems: [] };
|
|
290
|
+
const take = (slug, enabled, own) => {
|
|
291
|
+
const key = addonSiteCriticalKey(slug);
|
|
292
|
+
const fromSite = custom[key] !== undefined;
|
|
293
|
+
const value = fromSite ? custom[key] : own;
|
|
294
|
+
if (value === undefined)
|
|
295
|
+
return;
|
|
296
|
+
if (typeof value === 'boolean') {
|
|
297
|
+
layer.siteCritical.set(slug, value);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const reason = `${key} must be true or false, got ${JSON.stringify(value)} in ${fromSite ? 'app-custom-config.json' : 'its config/default-config.json'}; it is treated as true`;
|
|
301
|
+
layer.siteCritical.set(slug, true);
|
|
302
|
+
layer.refused.set(slug, reason);
|
|
303
|
+
layer.problems.push(`${addonProblemSubject(slug, enabled)}: ${reason}; the add-on is not loaded`);
|
|
304
|
+
};
|
|
305
|
+
for (const addon of addons)
|
|
306
|
+
take(addon.slug, addon.enabled, addon.defaults[addonSiteCriticalKey(addon.slug)]);
|
|
307
|
+
for (const key of Object.keys(custom)) {
|
|
308
|
+
const slug = /^ngdpbase\.addons\.([^.]+)\.site-critical$/.exec(key)?.[1];
|
|
309
|
+
if (slug && !layer.siteCritical.has(slug))
|
|
310
|
+
take(slug, custom[`ngdpbase.addons.${slug}.enabled`] === true, undefined);
|
|
311
|
+
}
|
|
312
|
+
return layer;
|
|
313
|
+
}
|
|
265
314
|
/**
|
|
266
315
|
* The owner's declaration stands (operator, 2026-09-29): for each entry an
|
|
267
316
|
* addon declared, the fields in OWNER_DECIDED_FIELDS are the addon's, never
|
|
@@ -310,8 +359,10 @@ export function mergeWithAddonLayer(shipped, addons, custom) {
|
|
|
310
359
|
const customCategories = custom[SYSTEM_CATEGORY_KEY];
|
|
311
360
|
const layer = addonCategoryLayer(shipped[SYSTEM_CATEGORY_KEY], customCategories, addons);
|
|
312
361
|
const databaseLayer = addonDatabaseLayer(addons, custom);
|
|
362
|
+
const criticalLayer = addonSiteCriticalLayer(addons, custom);
|
|
363
|
+
const siteCritical = criticalLayer.siteCritical;
|
|
313
364
|
const refused = new Map(layer.refused);
|
|
314
|
-
for (const [slug, reason] of databaseLayer.refused)
|
|
365
|
+
for (const [slug, reason] of [...databaseLayer.refused, ...criticalLayer.refused])
|
|
315
366
|
refused.set(slug, refused.has(slug) ? `${refused.get(slug)}; ${reason}` : reason);
|
|
316
367
|
const databases = databaseLayer.declarations.map((d) => ({ ...d, enabled: d.enabled && !refused.has(d.declaredBy) }));
|
|
317
368
|
const withAddons = addons
|
|
@@ -325,14 +376,14 @@ export function mergeWithAddonLayer(shipped, addons, custom) {
|
|
|
325
376
|
return deepMergeObjects(acc, rest);
|
|
326
377
|
}, shipped);
|
|
327
378
|
if (Object.keys(layer.entries).length === 0) {
|
|
328
|
-
return { merged: deepMergeConfigs(withAddons, custom), problems: [...layer.problems, ...databaseLayer.problems], refused, databases };
|
|
379
|
+
return { merged: deepMergeConfigs(withAddons, custom), problems: [...layer.problems, ...databaseLayer.problems, ...criticalLayer.problems], refused, databases, siteCritical };
|
|
329
380
|
}
|
|
330
381
|
const shippedCategories = isPlainObject(withAddons[SYSTEM_CATEGORY_KEY]) ? withAddons[SYSTEM_CATEGORY_KEY] : {};
|
|
331
382
|
const layered = { ...withAddons, [SYSTEM_CATEGORY_KEY]: deepMergeObjects(shippedCategories, layer.entries) };
|
|
332
383
|
const merged = deepMergeObjects(layered, custom);
|
|
333
384
|
const stood = ownerDeclarationsStand(merged[SYSTEM_CATEGORY_KEY], layer, customCategories);
|
|
334
385
|
merged[SYSTEM_CATEGORY_KEY] = stood.categories;
|
|
335
|
-
return { merged: merged, problems: [...layer.problems, ...stood.problems, ...databaseLayer.problems], refused, databases };
|
|
386
|
+
return { merged: merged, problems: [...layer.problems, ...stood.problems, ...databaseLayer.problems, ...criticalLayer.problems], refused, databases, siteCritical };
|
|
336
387
|
}
|
|
337
388
|
/**
|
|
338
389
|
* The full merge for a caller with the two files already read (the manager,
|
|
@@ -344,12 +395,14 @@ export function mergeWithAddonLayer(shipped, addons, custom) {
|
|
|
344
395
|
* could not be read; `refused` names, by slug, each addon that must not load
|
|
345
396
|
* because an entry it declares was refused, and why (#1559), or because its
|
|
346
397
|
* database declaration was (#1860); `databases` is every sound database
|
|
347
|
-
* declaration of every installed addon, on or off (#1860)
|
|
398
|
+
* declaration of every installed addon, on or off (#1860); `siteCritical`
|
|
399
|
+
* says, by slug, which addons put the site in maintenance mode when they fail
|
|
400
|
+
* to load (#1883).
|
|
348
401
|
*/
|
|
349
402
|
export function mergeConfigWithAddons(shipped, custom, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
|
|
350
403
|
const base = deepMergeConfigs(shipped, custom);
|
|
351
404
|
const all = discoverAddonDefaults(base, cwd, dataFolder);
|
|
352
|
-
const { merged, problems, refused, databases } = mergeWithAddonLayer(shipped, all, custom);
|
|
405
|
+
const { merged, problems, refused, databases, siteCritical } = mergeWithAddonLayer(shipped, all, custom);
|
|
353
406
|
const unreadable = all
|
|
354
407
|
.filter((a) => !a.enabled && a.error)
|
|
355
408
|
.map((a) => `add-on '${a.slug}' (turned off): config/default-config.json could not be read (${a.error}); its system-categories and database are not known`);
|
|
@@ -359,7 +412,8 @@ export function mergeConfigWithAddons(shipped, custom, cwd = process.cwd(), data
|
|
|
359
412
|
addons: all.filter((a) => a.enabled && !refused.has(a.slug)),
|
|
360
413
|
problems: [...unreadable, ...problems],
|
|
361
414
|
refused,
|
|
362
|
-
databases
|
|
415
|
+
databases,
|
|
416
|
+
siteCritical
|
|
363
417
|
};
|
|
364
418
|
}
|
|
365
419
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwilleke/ngdpbase-types",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.20.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",
|