@jwilleke/ngdpbase-types 0.0.0 → 5.18.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.
Files changed (302) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +16 -2
  3. package/lib/packages/ngdpbase-types/index.d.ts +32 -0
  4. package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
  5. package/lib/packages/ngdpbase-types/testing.js +53 -0
  6. package/lib/src/cache/CacheDependencies.d.ts +50 -0
  7. package/lib/src/cache/CacheDependencies.js +50 -0
  8. package/lib/src/cache/ICacheAdapter.d.ts +86 -0
  9. package/lib/src/cache/RegionCache.d.ts +171 -0
  10. package/lib/src/context/ActorContext.d.ts +48 -0
  11. package/lib/src/context/ActorContext.js +33 -0
  12. package/lib/src/context/ApiContext.d.ts +194 -0
  13. package/lib/src/context/ApiContext.js +273 -0
  14. package/lib/src/context/BaseContext.d.ts +110 -0
  15. package/lib/src/context/BaseContext.js +143 -0
  16. package/lib/src/context/JobContext.d.ts +153 -0
  17. package/lib/src/context/JobContext.js +144 -0
  18. package/lib/src/context/WikiContext.d.ts +404 -0
  19. package/lib/src/context/bootActions.d.ts +64 -0
  20. package/lib/src/context/bootActions.js +111 -0
  21. package/lib/src/converters/HtmlConverter.d.ts +70 -0
  22. package/lib/src/converters/IContentConverter.d.ts +94 -0
  23. package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
  24. package/lib/src/converters/conversionWarning.d.ts +15 -0
  25. package/lib/src/converters/ncm/bridge.d.ts +22 -0
  26. package/lib/src/converters/ncm/existing.d.ts +23 -0
  27. package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
  28. package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
  29. package/lib/src/converters/ncm/fix/index.d.ts +36 -0
  30. package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
  31. package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
  32. package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
  33. package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
  34. package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
  35. package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
  36. package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
  37. package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
  38. package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
  39. package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
  40. package/lib/src/converters/ncm/fix/types.d.ts +33 -0
  41. package/lib/src/converters/ncm/footnotes.d.ts +39 -0
  42. package/lib/src/converters/ncm/images.d.ts +49 -0
  43. package/lib/src/converters/ncm/index.d.ts +20 -0
  44. package/lib/src/converters/ncm/links.d.ts +21 -0
  45. package/lib/src/converters/ncm/normalize.d.ts +39 -0
  46. package/lib/src/converters/ncm/placeholder.d.ts +25 -0
  47. package/lib/src/converters/ncm/tables.d.ts +36 -0
  48. package/lib/src/converters/ncm/types.d.ts +42 -0
  49. package/lib/src/http/egressPolicy.d.ts +52 -0
  50. package/lib/src/http/guardedFetch.d.ts +89 -0
  51. package/lib/src/http/guardedFetch.js +113 -0
  52. package/lib/src/http/guardedLookup.d.ts +48 -0
  53. package/lib/src/http/guardedLookup.js +73 -0
  54. package/lib/src/http/ssrf.d.ts +83 -0
  55. package/lib/src/http/ssrf.js +311 -0
  56. package/lib/src/mail/MailProvider.d.ts +36 -0
  57. package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
  58. package/lib/src/managers/AddonsManager.d.ts +691 -0
  59. package/lib/src/managers/AgentTokenManager.d.ts +269 -0
  60. package/lib/src/managers/AssetManager.d.ts +116 -0
  61. package/lib/src/managers/AttachmentManager.d.ts +1032 -0
  62. package/lib/src/managers/AuthManager.d.ts +1257 -0
  63. package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
  64. package/lib/src/managers/BaseManager.d.ts +391 -0
  65. package/lib/src/managers/BaseManager.js +415 -0
  66. package/lib/src/managers/CacheManager.d.ts +159 -0
  67. package/lib/src/managers/CatalogManager.d.ts +235 -0
  68. package/lib/src/managers/CommentManager.d.ts +75 -0
  69. package/lib/src/managers/ConfigurationManager.d.ts +747 -0
  70. package/lib/src/managers/DatabaseManager.d.ts +89 -0
  71. package/lib/src/managers/DatabaseManager.js +236 -0
  72. package/lib/src/managers/EmailManager.d.ts +47 -0
  73. package/lib/src/managers/ExportManager.d.ts +129 -0
  74. package/lib/src/managers/ExportManager.js +420 -0
  75. package/lib/src/managers/FootnoteManager.d.ts +118 -0
  76. package/lib/src/managers/MediaManager.d.ts +279 -0
  77. package/lib/src/managers/MetricsManager.d.ts +89 -0
  78. package/lib/src/managers/NotificationManager.d.ts +165 -0
  79. package/lib/src/managers/OidcManager.d.ts +210 -0
  80. package/lib/src/managers/OrganizationManager.d.ts +107 -0
  81. package/lib/src/managers/PageManager.d.ts +1645 -0
  82. package/lib/src/managers/PersonManager.d.ts +48 -0
  83. package/lib/src/managers/PluginManager.d.ts +179 -0
  84. package/lib/src/managers/PluginManager.js +393 -0
  85. package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
  86. package/lib/src/managers/RenderingManager.d.ts +358 -0
  87. package/lib/src/managers/RoleManager.d.ts +134 -0
  88. package/lib/src/managers/SearchManager.d.ts +402 -0
  89. package/lib/src/managers/ShareManager.d.ts +251 -0
  90. package/lib/src/managers/TemplateManager.d.ts +176 -0
  91. package/lib/src/managers/ThemeManager.d.ts +56 -0
  92. package/lib/src/managers/UserManager.d.ts +480 -0
  93. package/lib/src/managers/ValidationManager.d.ts +426 -0
  94. package/lib/src/managers/VariableManager.d.ts +145 -0
  95. package/lib/src/middleware/csrf.d.ts +3 -0
  96. package/lib/src/parsers/LinkParser.d.ts +457 -0
  97. package/lib/src/parsers/MarkupParser.d.ts +769 -0
  98. package/lib/src/parsers/context/ParseContext.d.ts +314 -0
  99. package/lib/src/parsers/data/emoji-map.d.ts +18 -0
  100. package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
  101. package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
  102. package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
  103. package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
  104. package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
  105. package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
  106. package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
  107. package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
  108. package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
  109. package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
  110. package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
  111. package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
  112. package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
  113. package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
  114. package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
  115. package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
  116. package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
  117. package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
  118. package/lib/src/plugins/types.d.ts +79 -0
  119. package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
  120. package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
  121. package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
  122. package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
  123. package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
  124. package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
  125. package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
  126. package/lib/src/providers/BaseDatabaseProvider.js +12 -0
  127. package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
  128. package/lib/src/providers/BaseLoggingProvider.js +37 -0
  129. package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
  130. package/lib/src/providers/BasePageProvider.d.ts +337 -0
  131. package/lib/src/providers/BaseProvider.d.ts +98 -0
  132. package/lib/src/providers/BaseProvider.js +74 -0
  133. package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
  134. package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
  135. package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
  136. package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
  137. package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
  138. package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
  139. package/lib/src/providers/FileLoggingProvider.js +120 -0
  140. package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
  141. package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
  142. package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
  143. package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
  144. package/lib/src/providers/NullCacheProvider.d.ts +72 -0
  145. package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
  146. package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
  147. package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
  148. package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
  149. package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
  150. package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
  151. package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
  152. package/lib/src/providers/databaseMigrations.d.ts +8 -0
  153. package/lib/src/providers/databaseMigrations.js +47 -0
  154. package/lib/src/providers/jobState.d.ts +99 -0
  155. package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
  156. package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
  157. package/lib/src/providers/sqliteLocation.d.ts +17 -0
  158. package/lib/src/providers/sqliteLocation.js +55 -0
  159. package/lib/src/providers/sqliteMigrations.d.ts +48 -0
  160. package/lib/src/providers/sqliteMigrations.js +72 -0
  161. package/lib/src/rendering/githubAlerts.d.ts +22 -0
  162. package/lib/src/rendering/htmlPolicy.d.ts +76 -0
  163. package/lib/src/rendering/markdownConverter.d.ts +73 -0
  164. package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
  165. package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
  166. package/lib/src/security/pageActions.d.ts +40 -0
  167. package/lib/src/security/permissions.generated.d.ts +78 -0
  168. package/lib/src/types/Asset.d.ts +414 -0
  169. package/lib/src/types/Catalog.d.ts +61 -0
  170. package/lib/src/types/Comment.d.ts +11 -0
  171. package/lib/src/types/Config.d.ts +388 -0
  172. package/lib/src/types/Organization.d.ts +78 -0
  173. package/lib/src/types/OrganizationProvider.d.ts +38 -0
  174. package/lib/src/types/Page.d.ts +220 -0
  175. package/lib/src/types/Person.d.ts +56 -0
  176. package/lib/src/types/PersonProvider.d.ts +25 -0
  177. package/lib/src/types/Policy.d.ts +139 -0
  178. package/lib/src/types/Policy.js +17 -0
  179. package/lib/src/types/Provider.d.ts +810 -0
  180. package/lib/src/types/Role.d.ts +84 -0
  181. package/lib/src/types/RoleProvider.d.ts +35 -0
  182. package/lib/src/types/Schema.d.ts +425 -0
  183. package/lib/src/types/Share.d.ts +172 -0
  184. package/lib/src/types/Share.js +81 -0
  185. package/lib/src/types/User.d.ts +304 -0
  186. package/lib/src/types/Version.d.ts +189 -0
  187. package/lib/src/types/WikiEngine.d.ts +128 -0
  188. package/lib/src/types/express.d.ts +120 -0
  189. package/lib/src/types/express.js +1 -0
  190. package/lib/src/types/guards.d.ts +78 -0
  191. package/lib/src/types/heic-decode.d.ts +16 -0
  192. package/lib/src/types/index.d.ts +19 -0
  193. package/lib/src/types/linkedom.d.ts +77 -0
  194. package/lib/src/types/markdown-it-plugins.d.ts +21 -0
  195. package/lib/src/utils/DeltaStorage.d.ts +118 -0
  196. package/lib/src/utils/LocaleUtils.d.ts +105 -0
  197. package/lib/src/utils/LocaleUtils.js +294 -0
  198. package/lib/src/utils/PageNameMatcher.d.ts +98 -0
  199. package/lib/src/utils/PathPreflight.d.ts +38 -0
  200. package/lib/src/utils/PathPreflight.js +50 -0
  201. package/lib/src/utils/SectionUtils.d.ts +40 -0
  202. package/lib/src/utils/addonConfigLayer.d.ts +144 -0
  203. package/lib/src/utils/addonConfigLayer.js +382 -0
  204. package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
  205. package/lib/src/utils/addonDatabaseRules.js +57 -0
  206. package/lib/src/utils/addonHost.d.ts +51 -0
  207. package/lib/src/utils/addonHost.js +72 -0
  208. package/lib/src/utils/addonInstaller.d.ts +109 -0
  209. package/lib/src/utils/addonLock.d.ts +37 -0
  210. package/lib/src/utils/addonLock.js +48 -0
  211. package/lib/src/utils/addonPageSync.d.ts +45 -0
  212. package/lib/src/utils/addonProvenance.d.ts +48 -0
  213. package/lib/src/utils/addonTarball.d.ts +41 -0
  214. package/lib/src/utils/addonsPathResolver.d.ts +76 -0
  215. package/lib/src/utils/addonsPathResolver.js +129 -0
  216. package/lib/src/utils/assetMetadataTags.d.ts +66 -0
  217. package/lib/src/utils/atomicWrite.d.ts +28 -0
  218. package/lib/src/utils/auditConfigChange.d.ts +57 -0
  219. package/lib/src/utils/auditEventNames.d.ts +50 -0
  220. package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
  221. package/lib/src/utils/auditEventNames.generated.js +168 -0
  222. package/lib/src/utils/auditEventNames.js +49 -0
  223. package/lib/src/utils/auditEvents.d.ts +265 -0
  224. package/lib/src/utils/auditEvents.js +309 -0
  225. package/lib/src/utils/auditRegistry.d.ts +102 -0
  226. package/lib/src/utils/auditRegistry.js +168 -0
  227. package/lib/src/utils/bip39English.d.ts +2 -0
  228. package/lib/src/utils/configEnvKeys.d.ts +68 -0
  229. package/lib/src/utils/configFiles.d.ts +74 -0
  230. package/lib/src/utils/configFiles.js +150 -0
  231. package/lib/src/utils/delegation.d.ts +48 -0
  232. package/lib/src/utils/deriveUsername.d.ts +21 -0
  233. package/lib/src/utils/explainMediaPath.d.ts +123 -0
  234. package/lib/src/utils/formerTitles.d.ts +69 -0
  235. package/lib/src/utils/frontmatterAccess.d.ts +54 -0
  236. package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
  237. package/lib/src/utils/imageTransform.d.ts +52 -0
  238. package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
  239. package/lib/src/utils/jobSlots.d.ts +43 -0
  240. package/lib/src/utils/keywordNormalizer.d.ts +127 -0
  241. package/lib/src/utils/logger.d.ts +70 -0
  242. package/lib/src/utils/logger.js +106 -0
  243. package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
  244. package/lib/src/utils/managerUtils.d.ts +40 -0
  245. package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
  246. package/lib/src/utils/orgFilename.d.ts +22 -0
  247. package/lib/src/utils/pageFeature.d.ts +27 -0
  248. package/lib/src/utils/pageFrontmatter.d.ts +21 -0
  249. package/lib/src/utils/pageNotes.d.ts +150 -0
  250. package/lib/src/utils/pageTitleRule.d.ts +37 -0
  251. package/lib/src/utils/pageToArticle.d.ts +59 -0
  252. package/lib/src/utils/pageUrl.d.ts +18 -0
  253. package/lib/src/utils/passwordChange.d.ts +12 -0
  254. package/lib/src/utils/passwordHash.d.ts +60 -0
  255. package/lib/src/utils/permissionCatalog.d.ts +52 -0
  256. package/lib/src/utils/pluginFormatters.d.ts +452 -0
  257. package/lib/src/utils/pluginFormatters.js +714 -0
  258. package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
  259. package/lib/src/utils/privateStoreAccess.d.ts +57 -0
  260. package/lib/src/utils/privateStoreAccess.js +65 -0
  261. package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
  262. package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
  263. package/lib/src/utils/privateStoreDoor.d.ts +181 -0
  264. package/lib/src/utils/privateStoreExport.d.ts +72 -0
  265. package/lib/src/utils/privateStoreFiles.d.ts +120 -0
  266. package/lib/src/utils/privateStoreMeta.d.ts +64 -0
  267. package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
  268. package/lib/src/utils/privateStorePath.d.ts +259 -0
  269. package/lib/src/utils/privateStorePath.js +436 -0
  270. package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
  271. package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
  272. package/lib/src/utils/proxyClaim.d.ts +59 -0
  273. package/lib/src/utils/proxyClaim.js +30 -0
  274. package/lib/src/utils/redactPrivateNames.d.ts +21 -0
  275. package/lib/src/utils/redactPrivateNames.js +89 -0
  276. package/lib/src/utils/redactSecrets.d.ts +58 -0
  277. package/lib/src/utils/redactSecrets.js +171 -0
  278. package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
  279. package/lib/src/utils/roleGrants.d.ts +35 -0
  280. package/lib/src/utils/safeRedirect.d.ts +32 -0
  281. package/lib/src/utils/schedule.d.ts +54 -0
  282. package/lib/src/utils/secretFileMode.d.ts +17 -0
  283. package/lib/src/utils/seededShippedPages.d.ts +45 -0
  284. package/lib/src/utils/sensitiveValues.d.ts +59 -0
  285. package/lib/src/utils/sensitiveValues.js +106 -0
  286. package/lib/src/utils/sessionEnd.d.ts +12 -0
  287. package/lib/src/utils/sessionGeneration.d.ts +24 -0
  288. package/lib/src/utils/sessionIdle.d.ts +41 -0
  289. package/lib/src/utils/sessionLimits.d.ts +68 -0
  290. package/lib/src/utils/shareLockbox.d.ts +64 -0
  291. package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
  292. package/lib/src/utils/sniffMime.d.ts +21 -0
  293. package/lib/src/utils/storeSearchIndex.d.ts +75 -0
  294. package/lib/src/utils/subjectMayDo.d.ts +21 -0
  295. package/lib/src/utils/systemCategoryRules.d.ts +30 -0
  296. package/lib/src/utils/systemCategoryRules.js +41 -0
  297. package/lib/src/utils/userCreateError.d.ts +62 -0
  298. package/lib/src/utils/username.d.ts +53 -0
  299. package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
  300. package/lib/src/utils/version.d.ts +84 -0
  301. package/lib/src/utils/videoFrame.d.ts +21 -0
  302. package/package.json +54 -3
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Preflight a configured filesystem path before anything tries to write to it.
3
+ *
4
+ * Detects ONE thing: a macOS `/Volumes/<X>/...` path whose mount is absent,
5
+ * where `fs.ensureDir` would otherwise crash the engine with an opaque
6
+ * `EACCES`. A mistyped path elsewhere is not caught at all — #645 tracks
7
+ * widening it.
8
+ *
9
+ * __The platform is injectable, and that is not a convenience.__ The check
10
+ * runs only on darwin, so on Linux CI the branch was unreachable: every
11
+ * assertion covering it was guarded by `if (process.platform === 'darwin')`
12
+ * and silently skipped, while `managerStatus.test.ts` asserted the degraded
13
+ * state unguarded and failed on every CI run. A test that can only pass on one
14
+ * developer's operating system is not a test of anything CI can protect, and a
15
+ * continuously-red check is one nobody reads.
16
+ *
17
+ * With the seam, the darwin logic is exercised on every platform, and the
18
+ * production default is still `process.platform`.
19
+ */
20
+ import * as fs from 'fs';
21
+ function realIsDirectory(path) {
22
+ try {
23
+ return fs.statSync(path).isDirectory();
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ export function checkConfiguredPath(p, options = {}) {
30
+ if (!p)
31
+ return { ok: true };
32
+ const platform = options.platform ?? process.platform;
33
+ const isDirectory = options.isDirectory ?? realIsDirectory;
34
+ if (platform === 'darwin' && p.startsWith('/Volumes/')) {
35
+ const segments = p.split('/').filter(Boolean);
36
+ if (segments.length >= 2) {
37
+ const mountRoot = `/${segments[0]}/${segments[1]}`;
38
+ if (!isDirectory(mountRoot)) {
39
+ return {
40
+ ok: false,
41
+ reason: 'missing-mount',
42
+ missingMount: mountRoot,
43
+ message: `Configured path "${p}" expects ${mountRoot} to be a mounted volume, but it is not currently mounted. ` +
44
+ 'Mount the volume, or update the configuration to point at an existing path.'
45
+ };
46
+ }
47
+ }
48
+ }
49
+ return { ok: true };
50
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * SectionUtils — extract and splice markdown sections for section editing.
3
+ *
4
+ * A "section" is an ATX-style heading (# through ######) plus all content
5
+ * up to (but not including) the next heading of the same or higher level.
6
+ *
7
+ * Frontmatter (YAML between leading --- delimiters) is preserved intact and
8
+ * excluded from section numbering. Headings inside fenced code blocks are
9
+ * ignored to avoid false positives.
10
+ */
11
+ /**
12
+ * Convert heading text to its anchor slug. This IS the heading-id format:
13
+ * the page converter uses it via markdown-it-anchor (#1273, decision R2), so
14
+ * section links (#500) and rendered ids cannot drift apart.
15
+ *
16
+ * Algorithm mirrors GitHub Flavored Markdown:
17
+ * 1. Lowercase
18
+ * 2. Remove any character that is not a letter, digit, space, or hyphen
19
+ * 3. Replace whitespace runs with a single hyphen
20
+ * 4. Trim leading/trailing hyphens
21
+ *
22
+ * Examples:
23
+ * "PLA (Polylactic Acid)" → "pla-polylactic-acid"
24
+ * "3D Printing Filaments" → "3d-printing-filaments"
25
+ * "Hello World" → "hello-world"
26
+ */
27
+ export declare function headingSlug(text: string): string;
28
+ /** Return the number of sections (headings) in the document */
29
+ export declare function getSectionCount(markdown: string): number;
30
+ /**
31
+ * Extract the raw markdown for section N (0-indexed).
32
+ * Returns null if sectionIndex is out of range.
33
+ */
34
+ export declare function extractSection(markdown: string, sectionIndex: number): string | null;
35
+ /**
36
+ * Replace section N with newContent, returning the full updated markdown.
37
+ * The frontmatter and all other sections are preserved unchanged.
38
+ * Returns the original markdown if sectionIndex is out of range.
39
+ */
40
+ export declare function spliceSection(markdown: string, sectionIndex: number, newContent: string): string;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Addon defaults as a layer of the configuration merge (#1220).
3
+ *
4
+ * Every addon may ship `config/default-config.json`. Until #1220 those keys
5
+ * were injected at runtime, after the engine had initialised, whole-key and
6
+ * only when absent — so an addon could set its own settings but never add an
7
+ * entry to a catalog map such as `ngdpbase.permissions.definitions` or
8
+ * `ngdpbase.access.policies`, and the managers that copy those catalogs at
9
+ * boot never saw anything an addon said.
10
+ *
11
+ * Now the addon files are a layer between the shipped defaults and the
12
+ * operator's custom file:
13
+ *
14
+ * merged = deepMerge(deepMerge(shipped, addons…), custom)
15
+ *
16
+ * The deep merge merges maps per entry and `id` arrays by id, so an addon adds
17
+ * a permission definition and its own policy additively and never touches a
18
+ * shipped entry. The operator's file still wins over everything.
19
+ *
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
23
+ * `ngdpbase.addons.<slug>.enabled` is true in the shipped+custom view
24
+ * contributes; a disabled addon's opinions do not reach the catalog.
25
+ *
26
+ * One exception, `ngdpbase.system-category` (#1559): the system-categories an
27
+ * addon declares are read from every installed addon's file, enabled or not,
28
+ * so turning an addon off never makes its pages and vault unreachable. A
29
+ * turned-off addon's entries are not offered (`enabled: false`); each entry is
30
+ * stamped with its `owner`, the addon's slug, and the owner's declaration of
31
+ * the fields in OWNER_DECIDED_FIELDS stands over the operator's file. An entry
32
+ * that claims another owner, reuses another entry's key or vault, or has a
33
+ * value of the wrong type is refused, with a problem line naming it, and its
34
+ * addon is then not loaded at all (operator, 2026-10-16): none of its other
35
+ * keys apply, and its sound entries are kept as a turned-off addon's are.
36
+ *
37
+ * A second, `ngdpbase.addons.<slug>.database` (#1860): an addon's database
38
+ * declaration is read the same way, from every installed addon, so a
39
+ * turned-off addon's database stays known (and is backed up, #1723). It is
40
+ * never merged into the configuration: DatabaseManager reads the declarations
41
+ * from here, so the operator's file cannot change one. An invalid declaration
42
+ * stops its addon loading, as a refused system-category does.
43
+ */
44
+ import { type AddonDatabaseDeclaration } from './addonDatabaseRules.js';
45
+ export declare const ADDONS_PATH_KEY = "ngdpbase.managers.addons-manager.addons-path";
46
+ export declare const ADDONS_MANAGER_ENABLED_KEY = "ngdpbase.managers.addons-manager.enabled";
47
+ export interface AddonDefaults {
48
+ slug: string;
49
+ dir: string;
50
+ source: 'directory' | 'npm';
51
+ /**
52
+ * Whether the addon is switched on. A turned-off addon's file is read for its
53
+ * system-categories alone (#1559); none of its other keys reach the merge.
54
+ */
55
+ enabled: boolean;
56
+ /** The addon's default-config.json with `_`-prefixed comment keys dropped. */
57
+ defaults: Record<string, unknown>;
58
+ /** Set when the file exists but could not be parsed; the addon then contributes nothing. */
59
+ error?: string;
60
+ }
61
+ /** Is this addon switched on in the shipped+custom view? */
62
+ export declare function isAddonEnabled(base: Record<string, unknown>, slug: string): boolean;
63
+ /**
64
+ * Every addon directory the configured path names, plus the installed add-ons
65
+ * in `<data>/addons` (#1830: only those the lock file names for a slug in the
66
+ * install map), whether or not it is enabled.
67
+ */
68
+ export declare function discoverAddonDirs(base: Record<string, unknown>, cwd?: string, dataFolder?: string): Array<{
69
+ dir: string;
70
+ source: 'directory' | 'npm';
71
+ }>;
72
+ /**
73
+ * The defaults of every installed addon, enabled or not, in discovery order,
74
+ * each marked with whether it is `enabled`: the merge takes everything from
75
+ * an enabled addon and only the system-categories from a turned-off one
76
+ * (#1559). An addon with no default-config.json is omitted; one whose file
77
+ * cannot be parsed is returned with `error` set so the caller can say so
78
+ * rather than silently skip it.
79
+ */
80
+ export declare function discoverAddonDefaults(base: Record<string, unknown>, cwd?: string, dataFolder?: string): AddonDefaults[];
81
+ export interface AddonDatabaseLayer {
82
+ /** Every sound declaration, of every installed addon, `enabled` as its addon is. */
83
+ declarations: AddonDatabaseDeclaration[];
84
+ /** Addons whose declaration was refused, by slug, with why: never loaded. */
85
+ refused: Map<string, string>;
86
+ problems: string[];
87
+ }
88
+ /**
89
+ * The databases the addons declare (#1860), from every addon's file whether
90
+ * or not it is enabled. An addon declares only under its own slug; a second
91
+ * declaration of one owner's ledger is refused. `custom` is the operator's
92
+ * file: a value there for a declaration's key is ignored, and said so.
93
+ */
94
+ export declare function addonDatabaseLayer(addons: readonly AddonDefaults[], custom: Record<string, unknown>): AddonDatabaseLayer;
95
+ /**
96
+ * shipped ⊕ addons ⊕ custom, in that order of precedence (custom wins), with
97
+ * the problems found in the addons' system-categories (#1559).
98
+ *
99
+ * Each enabled addon folds onto the shipped defaults in turn, not onto the
100
+ * other addons alone: an addon's list of names for a set map (`secret-keys`)
101
+ * then adds to the shipped set, and a second addon's list adds again rather
102
+ * than replacing the first (#1612). `ngdpbase.system-category` is taken from
103
+ * every addon, enabled or not, through {@link addonCategoryLayer}, and its
104
+ * owner-decided fields stand over custom.
105
+ */
106
+ export declare function mergeWithAddonLayer<T extends Record<string, unknown>>(shipped: T, addons: readonly AddonDefaults[], custom: Partial<T>): {
107
+ merged: T;
108
+ problems: string[];
109
+ refused: Map<string, string>;
110
+ databases: AddonDatabaseDeclaration[];
111
+ };
112
+ /**
113
+ * The full merge for a caller with the two files already read (the manager,
114
+ * and the pre-engine reader): discover addons against the shipped+custom view,
115
+ * then fold them in beneath custom. `declared` is the same merge without
116
+ * custom (#1638); `addons` lists the enabled addons whose keys apply;
117
+ * `problems` names every addon system-category refused, every operator value
118
+ * an owner's declaration overrode, and every turned-off addon whose file
119
+ * could not be read; `refused` names, by slug, each addon that must not load
120
+ * because an entry it declares was refused, and why (#1559), or because its
121
+ * database declaration was (#1860); `databases` is every sound database
122
+ * declaration of every installed addon, on or off (#1860).
123
+ */
124
+ export declare function mergeConfigWithAddons<T extends Record<string, unknown>>(shipped: T, custom: Partial<T>, cwd?: string, dataFolder?: string): {
125
+ merged: T;
126
+ declared: T;
127
+ addons: AddonDefaults[];
128
+ problems: string[];
129
+ refused: Map<string, string>;
130
+ databases: AddonDatabaseDeclaration[];
131
+ };
132
+ export interface MergedConfigFiles {
133
+ merged: Record<string, unknown>;
134
+ customKeys: Set<string>;
135
+ addons: AddonDefaults[];
136
+ }
137
+ /**
138
+ * The merged configuration for a caller that runs before the engine exists
139
+ * (#1214): the two files plus the addon layer, the same merge the manager
140
+ * performs. Returns null on any failure: a missing or malformed file must not
141
+ * stop the server binding a socket; the port simply falls back. No `${VAR}`
142
+ * resolution and no env-key overrides — those are `getProperty`'s job.
143
+ */
144
+ export declare function loadMergedConfigSync(env?: Record<string, string | undefined>, cwd?: string): MergedConfigFiles | null;
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Addon defaults as a layer of the configuration merge (#1220).
3
+ *
4
+ * Every addon may ship `config/default-config.json`. Until #1220 those keys
5
+ * were injected at runtime, after the engine had initialised, whole-key and
6
+ * only when absent — so an addon could set its own settings but never add an
7
+ * entry to a catalog map such as `ngdpbase.permissions.definitions` or
8
+ * `ngdpbase.access.policies`, and the managers that copy those catalogs at
9
+ * boot never saw anything an addon said.
10
+ *
11
+ * Now the addon files are a layer between the shipped defaults and the
12
+ * operator's custom file:
13
+ *
14
+ * merged = deepMerge(deepMerge(shipped, addons…), custom)
15
+ *
16
+ * The deep merge merges maps per entry and `id` arrays by id, so an addon adds
17
+ * a permission definition and its own policy additively and never touches a
18
+ * shipped entry. The operator's file still wins over everything.
19
+ *
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
23
+ * `ngdpbase.addons.<slug>.enabled` is true in the shipped+custom view
24
+ * contributes; a disabled addon's opinions do not reach the catalog.
25
+ *
26
+ * One exception, `ngdpbase.system-category` (#1559): the system-categories an
27
+ * addon declares are read from every installed addon's file, enabled or not,
28
+ * so turning an addon off never makes its pages and vault unreachable. A
29
+ * turned-off addon's entries are not offered (`enabled: false`); each entry is
30
+ * stamped with its `owner`, the addon's slug, and the owner's declaration of
31
+ * the fields in OWNER_DECIDED_FIELDS stands over the operator's file. An entry
32
+ * that claims another owner, reuses another entry's key or vault, or has a
33
+ * value of the wrong type is refused, with a problem line naming it, and its
34
+ * addon is then not loaded at all (operator, 2026-10-16): none of its other
35
+ * keys apply, and its sound entries are kept as a turned-off addon's are.
36
+ *
37
+ * A second, `ngdpbase.addons.<slug>.database` (#1860): an addon's database
38
+ * declaration is read the same way, from every installed addon, so a
39
+ * turned-off addon's database stays known (and is backed up, #1723). It is
40
+ * never merged into the configuration: DatabaseManager reads the declarations
41
+ * from here, so the operator's file cannot change one. An invalid declaration
42
+ * stops its addon loading, as a refused system-category does.
43
+ */
44
+ import fs from 'fs';
45
+ import path from 'path';
46
+ import { isDeepStrictEqual } from 'util';
47
+ import { configFilePaths, deepMergeConfigs, deepMergeObjects, instanceDataFolder, isPlainObject, readConfigFilesSync } from './configFiles.js';
48
+ import { configuredInstallSlugs, findNodeModulesDir, installedAddonsDir, matchNpmPackageDirs, resolveAddonSlug, splitAddonsPath } from './addonsPathResolver.js';
49
+ import { lockedAddonDirsSync } from './addonLock.js';
50
+ import { parsePrivatestore } from './privateStorePath.js';
51
+ import { OWNER_DECIDED_FIELDS, SYSTEM_CATEGORY_KEY, systemCategoryValueProblems } from './systemCategoryRules.js';
52
+ import { addonDatabaseKey, addonDatabaseKeySlug, addonDatabaseProblems } from './addonDatabaseRules.js';
53
+ export const ADDONS_PATH_KEY = 'ngdpbase.managers.addons-manager.addons-path';
54
+ export const ADDONS_MANAGER_ENABLED_KEY = 'ngdpbase.managers.addons-manager.enabled';
55
+ /** Is this addon switched on in the shipped+custom view? */
56
+ export function isAddonEnabled(base, slug) {
57
+ return base[`ngdpbase.addons.${slug}.enabled`] === true;
58
+ }
59
+ /**
60
+ * Every addon directory the configured path names, plus the installed add-ons
61
+ * in `<data>/addons` (#1830: only those the lock file names for a slug in the
62
+ * install map), whether or not it is enabled.
63
+ */
64
+ export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
65
+ if (base[ADDONS_MANAGER_ENABLED_KEY] === false)
66
+ return [];
67
+ const { directories, npmPatterns } = splitAddonsPath(base[ADDONS_PATH_KEY] ?? './addons');
68
+ const found = [];
69
+ for (const raw of directories) {
70
+ const root = path.resolve(cwd, raw);
71
+ let entries;
72
+ try {
73
+ entries = fs.readdirSync(root, { withFileTypes: true });
74
+ }
75
+ catch {
76
+ continue;
77
+ }
78
+ for (const entry of entries) {
79
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'shared')
80
+ continue;
81
+ found.push({ dir: path.join(root, entry.name), source: 'directory' });
82
+ }
83
+ }
84
+ const installed = installedAddonsDir(dataFolder, cwd);
85
+ if (!found.some((f) => path.dirname(f.dir) === installed)) {
86
+ for (const dir of lockedAddonDirsSync(installed, configuredInstallSlugs(base)))
87
+ found.push({ dir, source: 'directory' });
88
+ }
89
+ const nmRoot = npmPatterns.length > 0 ? findNodeModulesDir(cwd) : null;
90
+ if (nmRoot) {
91
+ for (const pattern of npmPatterns) {
92
+ for (const dir of matchNpmPackageDirs(nmRoot, pattern))
93
+ found.push({ dir, source: 'npm' });
94
+ }
95
+ }
96
+ return found;
97
+ }
98
+ /**
99
+ * The defaults of every installed addon, enabled or not, in discovery order,
100
+ * each marked with whether it is `enabled`: the merge takes everything from
101
+ * an enabled addon and only the system-categories from a turned-off one
102
+ * (#1559). An addon with no default-config.json is omitted; one whose file
103
+ * cannot be parsed is returned with `error` set so the caller can say so
104
+ * rather than silently skip it.
105
+ */
106
+ export function discoverAddonDefaults(base, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
107
+ const out = [];
108
+ for (const { dir, source } of discoverAddonDirs(base, cwd, dataFolder)) {
109
+ const slug = resolveAddonSlug(dir, source);
110
+ const enabled = isAddonEnabled(base, slug);
111
+ const file = path.join(dir, 'config', 'default-config.json');
112
+ if (!fs.existsSync(file))
113
+ continue;
114
+ try {
115
+ const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
116
+ if (!isPlainObject(raw))
117
+ throw new Error('not a JSON object');
118
+ const defaults = {};
119
+ for (const [key, value] of Object.entries(raw))
120
+ if (!key.startsWith('_'))
121
+ defaults[key] = value;
122
+ out.push({ slug, dir, source, enabled, defaults });
123
+ }
124
+ catch (err) {
125
+ out.push({ slug, dir, source, enabled, defaults: {}, error: err instanceof Error ? err.message : String(err) });
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+ function vaultIdOf(entry) {
131
+ return parsePrivatestore(entry?.storageLocation?.privatestore)?.vaultId;
132
+ }
133
+ /**
134
+ * The system-categories the addons declare (#1559), from every addon's file
135
+ * whether or not it is enabled. `shipped` and `custom` are the site's own
136
+ * category maps: an addon may not take a key the shipped map has, nor a vault
137
+ * another entry names.
138
+ */
139
+ function addonCategoryLayer(shipped, custom, addons) {
140
+ const siteKeys = new Set(isPlainObject(shipped) ? Object.keys(shipped) : []);
141
+ const vaultKey = new Map();
142
+ for (const map of [shipped, custom]) {
143
+ if (!isPlainObject(map))
144
+ continue;
145
+ for (const [key, entry] of Object.entries(map)) {
146
+ const vaultId = vaultIdOf(entry);
147
+ if (vaultId && !vaultKey.has(vaultId))
148
+ vaultKey.set(vaultId, key);
149
+ }
150
+ }
151
+ const layer = { entries: {}, turnedOff: new Set(), refused: new Map(), problems: [] };
152
+ const declaredBy = new Map();
153
+ for (const addon of addons) {
154
+ const declared = addon.defaults[SYSTEM_CATEGORY_KEY];
155
+ if (declared === undefined)
156
+ continue;
157
+ const who = `add-on '${addon.slug}'${addon.enabled ? '' : ' (turned off)'}`;
158
+ if (!isPlainObject(declared)) {
159
+ const reason = `${SYSTEM_CATEGORY_KEY} must be a map of entries`;
160
+ layer.problems.push(`${who}: ${reason}; it is refused and the add-on is not loaded`);
161
+ layer.refused.set(addon.slug, `its system-category declaration was refused: ${reason}`);
162
+ continue;
163
+ }
164
+ const refusedHere = [];
165
+ const acceptedHere = [];
166
+ for (const [key, entry] of Object.entries(declared)) {
167
+ const reasons = [];
168
+ if (!isPlainObject(entry)) {
169
+ reasons.push('the entry is not an object');
170
+ }
171
+ else {
172
+ if (entry.owner !== undefined && entry.owner !== addon.slug) {
173
+ reasons.push(`its owner is ${JSON.stringify(entry.owner)}; an add-on declares only its own, owner '${addon.slug}'`);
174
+ }
175
+ if (siteKeys.has(key))
176
+ reasons.push(`'${key}' is one of the site's own system-categories`);
177
+ else if (declaredBy.has(key))
178
+ reasons.push(`add-on '${declaredBy.get(key)}' already declares '${key}'`);
179
+ if (entry.default === true)
180
+ reasons.push('default: true belongs to the site\'s default system-category only');
181
+ const privatestore = entry.storageLocation?.privatestore;
182
+ const vaultId = vaultIdOf(entry);
183
+ if (privatestore !== undefined && !vaultId) {
184
+ reasons.push(`storageLocation.privatestore must be pages/<parent>/{user}/<vault id>/, got ${JSON.stringify(privatestore)}`);
185
+ }
186
+ else if (vaultId && vaultKey.has(vaultId) && vaultKey.get(vaultId) !== key) {
187
+ reasons.push(`its vault '${vaultId}' is already the vault of system-category '${vaultKey.get(vaultId)}'`);
188
+ }
189
+ reasons.push(...systemCategoryValueProblems(key, entry).map((p) => p.message));
190
+ }
191
+ if (reasons.length > 0) {
192
+ layer.problems.push(`${who}: system-category '${key}' is refused (${reasons.join('; ')}); the add-on is not loaded`);
193
+ refusedHere.push(`'${key}' (${reasons.join('; ')})`);
194
+ continue;
195
+ }
196
+ layer.entries[key] = { ...entry, owner: addon.slug };
197
+ acceptedHere.push(key);
198
+ declaredBy.set(key, addon.slug);
199
+ const vaultId = vaultIdOf(entry);
200
+ if (vaultId)
201
+ vaultKey.set(vaultId, key);
202
+ }
203
+ // An add-on with a refused entry is not loaded (operator, 2026-10-16), so
204
+ // its sound entries are kept as a turned-off add-on's are: readable, never
205
+ // offered.
206
+ if (refusedHere.length > 0)
207
+ layer.refused.set(addon.slug, `its system-category declaration was refused: ${refusedHere.join('; ')}`);
208
+ if (!addon.enabled || refusedHere.length > 0) {
209
+ for (const key of acceptedHere) {
210
+ layer.entries[key].enabled = false;
211
+ layer.turnedOff.add(key);
212
+ }
213
+ }
214
+ }
215
+ return layer;
216
+ }
217
+ /**
218
+ * The databases the addons declare (#1860), from every addon's file whether
219
+ * or not it is enabled. An addon declares only under its own slug; a second
220
+ * declaration of one owner's ledger is refused. `custom` is the operator's
221
+ * file: a value there for a declaration's key is ignored, and said so.
222
+ */
223
+ export function addonDatabaseLayer(addons, custom) {
224
+ const layer = { declarations: [], refused: new Map(), problems: [] };
225
+ const ledgerBy = new Map();
226
+ for (const addon of addons) {
227
+ const reasons = [];
228
+ let declared;
229
+ for (const [key, value] of Object.entries(addon.defaults)) {
230
+ const slug = addonDatabaseKeySlug(key);
231
+ if (slug === null)
232
+ continue;
233
+ if (slug === addon.slug)
234
+ declared = value;
235
+ else
236
+ reasons.push(`it declares ${key}; an add-on declares only its own, ${addonDatabaseKey(addon.slug)}`);
237
+ }
238
+ if (declared !== undefined) {
239
+ const valueProblems = addonDatabaseProblems(declared);
240
+ reasons.push(...valueProblems);
241
+ if (valueProblems.length === 0) {
242
+ const { owner, ledgerTable } = declared;
243
+ const by = ledgerBy.get(`${owner}/${ledgerTable}`);
244
+ if (by)
245
+ reasons.push(`add-on '${by}' already declares ledger '${ledgerTable}' of database '${owner}'`);
246
+ }
247
+ }
248
+ if (reasons.length > 0) {
249
+ const who = `add-on '${addon.slug}'${addon.enabled ? '' : ' (turned off)'}`;
250
+ layer.problems.push(`${who}: its database declaration is refused (${reasons.join('; ')}); the add-on is not loaded`);
251
+ layer.refused.set(addon.slug, `its database declaration was refused: ${reasons.join('; ')}`);
252
+ continue;
253
+ }
254
+ if (declared === undefined)
255
+ continue;
256
+ const { owner, ledgerTable, encrypt } = declared;
257
+ ledgerBy.set(`${owner}/${ledgerTable}`, addon.slug);
258
+ layer.declarations.push({ owner, ledgerTable, encrypt, declaredBy: addon.slug, enabled: addon.enabled });
259
+ if (custom[addonDatabaseKey(addon.slug)] !== undefined) {
260
+ layer.problems.push(`app-custom-config.json sets ${addonDatabaseKey(addon.slug)}, which is the declaration of add-on '${addon.slug}'; the site's value is ignored`);
261
+ }
262
+ }
263
+ return layer;
264
+ }
265
+ /**
266
+ * The owner's declaration stands (operator, 2026-09-29): for each entry an
267
+ * addon declared, the fields in OWNER_DECIDED_FIELDS are the addon's, never
268
+ * the operator's file's, and a turned-off addon's entry is never offered.
269
+ * Returns the merged map with that applied, and a line for each value of the
270
+ * operator's that is ignored.
271
+ */
272
+ function ownerDeclarationsStand(merged, layer, custom) {
273
+ if (!isPlainObject(merged))
274
+ return { categories: merged, problems: [] };
275
+ const categories = { ...merged };
276
+ const problems = [];
277
+ for (const [key, declared] of Object.entries(layer.entries)) {
278
+ const entry = categories[key];
279
+ if (!isPlainObject(entry))
280
+ continue;
281
+ const site = isPlainObject(custom) && isPlainObject(custom[key]) ? custom[key] : {};
282
+ const restored = { ...entry };
283
+ for (const field of OWNER_DECIDED_FIELDS) {
284
+ if (declared[field] === undefined)
285
+ delete restored[field];
286
+ else
287
+ restored[field] = declared[field];
288
+ if (site[field] !== undefined && !isDeepStrictEqual(site[field], declared[field])) {
289
+ problems.push(`app-custom-config.json sets ${SYSTEM_CATEGORY_KEY}.${key}.${field}, which is the declaration of its owner '${String(declared.owner)}'; the site's value is ignored`);
290
+ }
291
+ }
292
+ if (layer.turnedOff.has(key))
293
+ restored.enabled = false;
294
+ categories[key] = restored;
295
+ }
296
+ return { categories, problems };
297
+ }
298
+ /**
299
+ * shipped ⊕ addons ⊕ custom, in that order of precedence (custom wins), with
300
+ * the problems found in the addons' system-categories (#1559).
301
+ *
302
+ * Each enabled addon folds onto the shipped defaults in turn, not onto the
303
+ * other addons alone: an addon's list of names for a set map (`secret-keys`)
304
+ * then adds to the shipped set, and a second addon's list adds again rather
305
+ * than replacing the first (#1612). `ngdpbase.system-category` is taken from
306
+ * every addon, enabled or not, through {@link addonCategoryLayer}, and its
307
+ * owner-decided fields stand over custom.
308
+ */
309
+ export function mergeWithAddonLayer(shipped, addons, custom) {
310
+ const customCategories = custom[SYSTEM_CATEGORY_KEY];
311
+ const layer = addonCategoryLayer(shipped[SYSTEM_CATEGORY_KEY], customCategories, addons);
312
+ const databaseLayer = addonDatabaseLayer(addons, custom);
313
+ const refused = new Map(layer.refused);
314
+ for (const [slug, reason] of databaseLayer.refused)
315
+ refused.set(slug, refused.has(slug) ? `${refused.get(slug)}; ${reason}` : reason);
316
+ const databases = databaseLayer.declarations.map((d) => ({ ...d, enabled: d.enabled && !refused.has(d.declaredBy) }));
317
+ const withAddons = addons
318
+ .filter((a) => a.enabled && !refused.has(a.slug))
319
+ .reduce((acc, a) => {
320
+ const rest = { ...a.defaults };
321
+ delete rest[SYSTEM_CATEGORY_KEY];
322
+ for (const key of Object.keys(rest))
323
+ if (addonDatabaseKeySlug(key) !== null)
324
+ delete rest[key];
325
+ return deepMergeObjects(acc, rest);
326
+ }, shipped);
327
+ if (Object.keys(layer.entries).length === 0) {
328
+ return { merged: deepMergeConfigs(withAddons, custom), problems: [...layer.problems, ...databaseLayer.problems], refused, databases };
329
+ }
330
+ const shippedCategories = isPlainObject(withAddons[SYSTEM_CATEGORY_KEY]) ? withAddons[SYSTEM_CATEGORY_KEY] : {};
331
+ const layered = { ...withAddons, [SYSTEM_CATEGORY_KEY]: deepMergeObjects(shippedCategories, layer.entries) };
332
+ const merged = deepMergeObjects(layered, custom);
333
+ const stood = ownerDeclarationsStand(merged[SYSTEM_CATEGORY_KEY], layer, customCategories);
334
+ merged[SYSTEM_CATEGORY_KEY] = stood.categories;
335
+ return { merged: merged, problems: [...layer.problems, ...stood.problems, ...databaseLayer.problems], refused, databases };
336
+ }
337
+ /**
338
+ * The full merge for a caller with the two files already read (the manager,
339
+ * and the pre-engine reader): discover addons against the shipped+custom view,
340
+ * then fold them in beneath custom. `declared` is the same merge without
341
+ * custom (#1638); `addons` lists the enabled addons whose keys apply;
342
+ * `problems` names every addon system-category refused, every operator value
343
+ * an owner's declaration overrode, and every turned-off addon whose file
344
+ * could not be read; `refused` names, by slug, each addon that must not load
345
+ * because an entry it declares was refused, and why (#1559), or because its
346
+ * database declaration was (#1860); `databases` is every sound database
347
+ * declaration of every installed addon, on or off (#1860).
348
+ */
349
+ export function mergeConfigWithAddons(shipped, custom, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
350
+ const base = deepMergeConfigs(shipped, custom);
351
+ const all = discoverAddonDefaults(base, cwd, dataFolder);
352
+ const { merged, problems, refused, databases } = mergeWithAddonLayer(shipped, all, custom);
353
+ const unreadable = all
354
+ .filter((a) => !a.enabled && a.error)
355
+ .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`);
356
+ return {
357
+ merged,
358
+ declared: mergeWithAddonLayer(shipped, all, {}).merged,
359
+ addons: all.filter((a) => a.enabled && !refused.has(a.slug)),
360
+ problems: [...unreadable, ...problems],
361
+ refused,
362
+ databases
363
+ };
364
+ }
365
+ /**
366
+ * The merged configuration for a caller that runs before the engine exists
367
+ * (#1214): the two files plus the addon layer, the same merge the manager
368
+ * performs. Returns null on any failure: a missing or malformed file must not
369
+ * stop the server binding a socket; the port simply falls back. No `${VAR}`
370
+ * resolution and no env-key overrides — those are `getProperty`'s job.
371
+ */
372
+ export function loadMergedConfigSync(env = process.env, cwd = process.cwd()) {
373
+ try {
374
+ const paths = configFilePaths(env, cwd);
375
+ const files = readConfigFilesSync(paths);
376
+ const { merged, addons } = mergeConfigWithAddons(files.defaultConfig ?? {}, files.customConfig, cwd, paths.instanceDataFolder);
377
+ return { merged, customKeys: files.customKeys, addons };
378
+ }
379
+ catch {
380
+ return null;
381
+ }
382
+ }
@@ -0,0 +1,25 @@
1
+ /** A canonical add-on slug: lowercase letters and digits, single dashes. */
2
+ export declare const ADDON_SLUG: RegExp;
3
+ /** The values `encrypt` may take. */
4
+ export declare const DATABASE_ENCRYPT_VALUES: readonly ["required", "optional"];
5
+ export type DatabaseEncrypt = typeof DATABASE_ENCRYPT_VALUES[number];
6
+ /** The configuration key under which an add-on declares its database. */
7
+ export declare function addonDatabaseKey(slug: string): string;
8
+ /** The slug in a `ngdpbase.addons.<slug>.database` key, or null for any other key. */
9
+ export declare function addonDatabaseKeySlug(key: string): string | null;
10
+ /** What a ledger table of this owner must start with: the slug, dashes as underscores, then `_`. */
11
+ export declare function ledgerPrefix(owner: string): string;
12
+ /** One accepted declaration, as core knows it whether or not its add-on is on. */
13
+ export interface AddonDatabaseDeclaration {
14
+ /** Whose file: `<owner>.db` beside the application database. */
15
+ owner: string;
16
+ /** The migration ledger this declaration is for. */
17
+ ledgerTable: string;
18
+ encrypt: DatabaseEncrypt;
19
+ /** The add-on whose file declares it. */
20
+ declaredBy: string;
21
+ /** Whether that add-on is switched on and was not refused. */
22
+ enabled: boolean;
23
+ }
24
+ /** What is wrong with one declaration, one line per problem. Empty when sound. */
25
+ export declare function addonDatabaseProblems(value: unknown): string[];