@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.
- package/LICENSE +201 -0
- package/README.md +16 -2
- package/lib/packages/ngdpbase-types/index.d.ts +32 -0
- package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
- package/lib/packages/ngdpbase-types/testing.js +53 -0
- package/lib/src/cache/CacheDependencies.d.ts +50 -0
- package/lib/src/cache/CacheDependencies.js +50 -0
- package/lib/src/cache/ICacheAdapter.d.ts +86 -0
- package/lib/src/cache/RegionCache.d.ts +171 -0
- package/lib/src/context/ActorContext.d.ts +48 -0
- package/lib/src/context/ActorContext.js +33 -0
- package/lib/src/context/ApiContext.d.ts +194 -0
- package/lib/src/context/ApiContext.js +273 -0
- package/lib/src/context/BaseContext.d.ts +110 -0
- package/lib/src/context/BaseContext.js +143 -0
- package/lib/src/context/JobContext.d.ts +153 -0
- package/lib/src/context/JobContext.js +144 -0
- package/lib/src/context/WikiContext.d.ts +404 -0
- package/lib/src/context/bootActions.d.ts +64 -0
- package/lib/src/context/bootActions.js +111 -0
- package/lib/src/converters/HtmlConverter.d.ts +70 -0
- package/lib/src/converters/IContentConverter.d.ts +94 -0
- package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
- package/lib/src/converters/conversionWarning.d.ts +15 -0
- package/lib/src/converters/ncm/bridge.d.ts +22 -0
- package/lib/src/converters/ncm/existing.d.ts +23 -0
- package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
- package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
- package/lib/src/converters/ncm/fix/index.d.ts +36 -0
- package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
- package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
- package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
- package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
- package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
- package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
- package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
- package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
- package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
- package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
- package/lib/src/converters/ncm/fix/types.d.ts +33 -0
- package/lib/src/converters/ncm/footnotes.d.ts +39 -0
- package/lib/src/converters/ncm/images.d.ts +49 -0
- package/lib/src/converters/ncm/index.d.ts +20 -0
- package/lib/src/converters/ncm/links.d.ts +21 -0
- package/lib/src/converters/ncm/normalize.d.ts +39 -0
- package/lib/src/converters/ncm/placeholder.d.ts +25 -0
- package/lib/src/converters/ncm/tables.d.ts +36 -0
- package/lib/src/converters/ncm/types.d.ts +42 -0
- package/lib/src/http/egressPolicy.d.ts +52 -0
- package/lib/src/http/guardedFetch.d.ts +89 -0
- package/lib/src/http/guardedFetch.js +113 -0
- package/lib/src/http/guardedLookup.d.ts +48 -0
- package/lib/src/http/guardedLookup.js +73 -0
- package/lib/src/http/ssrf.d.ts +83 -0
- package/lib/src/http/ssrf.js +311 -0
- package/lib/src/mail/MailProvider.d.ts +36 -0
- package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
- package/lib/src/managers/AddonsManager.d.ts +691 -0
- package/lib/src/managers/AgentTokenManager.d.ts +269 -0
- package/lib/src/managers/AssetManager.d.ts +116 -0
- package/lib/src/managers/AttachmentManager.d.ts +1032 -0
- package/lib/src/managers/AuthManager.d.ts +1257 -0
- package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
- package/lib/src/managers/BaseManager.d.ts +391 -0
- package/lib/src/managers/BaseManager.js +415 -0
- package/lib/src/managers/CacheManager.d.ts +159 -0
- package/lib/src/managers/CatalogManager.d.ts +235 -0
- package/lib/src/managers/CommentManager.d.ts +75 -0
- package/lib/src/managers/ConfigurationManager.d.ts +747 -0
- package/lib/src/managers/DatabaseManager.d.ts +89 -0
- package/lib/src/managers/DatabaseManager.js +236 -0
- package/lib/src/managers/EmailManager.d.ts +47 -0
- package/lib/src/managers/ExportManager.d.ts +129 -0
- package/lib/src/managers/ExportManager.js +420 -0
- package/lib/src/managers/FootnoteManager.d.ts +118 -0
- package/lib/src/managers/MediaManager.d.ts +279 -0
- package/lib/src/managers/MetricsManager.d.ts +89 -0
- package/lib/src/managers/NotificationManager.d.ts +165 -0
- package/lib/src/managers/OidcManager.d.ts +210 -0
- package/lib/src/managers/OrganizationManager.d.ts +107 -0
- package/lib/src/managers/PageManager.d.ts +1645 -0
- package/lib/src/managers/PersonManager.d.ts +48 -0
- package/lib/src/managers/PluginManager.d.ts +179 -0
- package/lib/src/managers/PluginManager.js +393 -0
- package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
- package/lib/src/managers/RenderingManager.d.ts +358 -0
- package/lib/src/managers/RoleManager.d.ts +134 -0
- package/lib/src/managers/SearchManager.d.ts +402 -0
- package/lib/src/managers/ShareManager.d.ts +251 -0
- package/lib/src/managers/TemplateManager.d.ts +176 -0
- package/lib/src/managers/ThemeManager.d.ts +56 -0
- package/lib/src/managers/UserManager.d.ts +480 -0
- package/lib/src/managers/ValidationManager.d.ts +426 -0
- package/lib/src/managers/VariableManager.d.ts +145 -0
- package/lib/src/middleware/csrf.d.ts +3 -0
- package/lib/src/parsers/LinkParser.d.ts +457 -0
- package/lib/src/parsers/MarkupParser.d.ts +769 -0
- package/lib/src/parsers/context/ParseContext.d.ts +314 -0
- package/lib/src/parsers/data/emoji-map.d.ts +18 -0
- package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
- package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
- package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
- package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
- package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
- package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
- package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
- package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
- package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
- package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
- package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
- package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
- package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
- package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
- package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
- package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
- package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
- package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
- package/lib/src/plugins/types.d.ts +79 -0
- package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
- package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
- package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
- package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
- package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
- package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
- package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
- package/lib/src/providers/BaseDatabaseProvider.js +12 -0
- package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
- package/lib/src/providers/BaseLoggingProvider.js +37 -0
- package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
- package/lib/src/providers/BasePageProvider.d.ts +337 -0
- package/lib/src/providers/BaseProvider.d.ts +98 -0
- package/lib/src/providers/BaseProvider.js +74 -0
- package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
- package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
- package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
- package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
- package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
- package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
- package/lib/src/providers/FileLoggingProvider.js +120 -0
- package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
- package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
- package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
- package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
- package/lib/src/providers/NullCacheProvider.d.ts +72 -0
- package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
- package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
- package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
- package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
- package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
- package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
- package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
- package/lib/src/providers/databaseMigrations.d.ts +8 -0
- package/lib/src/providers/databaseMigrations.js +47 -0
- package/lib/src/providers/jobState.d.ts +99 -0
- package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
- package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
- package/lib/src/providers/sqliteLocation.d.ts +17 -0
- package/lib/src/providers/sqliteLocation.js +55 -0
- package/lib/src/providers/sqliteMigrations.d.ts +48 -0
- package/lib/src/providers/sqliteMigrations.js +72 -0
- package/lib/src/rendering/githubAlerts.d.ts +22 -0
- package/lib/src/rendering/htmlPolicy.d.ts +76 -0
- package/lib/src/rendering/markdownConverter.d.ts +73 -0
- package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
- package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
- package/lib/src/security/pageActions.d.ts +40 -0
- package/lib/src/security/permissions.generated.d.ts +78 -0
- package/lib/src/types/Asset.d.ts +414 -0
- package/lib/src/types/Catalog.d.ts +61 -0
- package/lib/src/types/Comment.d.ts +11 -0
- package/lib/src/types/Config.d.ts +388 -0
- package/lib/src/types/Organization.d.ts +78 -0
- package/lib/src/types/OrganizationProvider.d.ts +38 -0
- package/lib/src/types/Page.d.ts +220 -0
- package/lib/src/types/Person.d.ts +56 -0
- package/lib/src/types/PersonProvider.d.ts +25 -0
- package/lib/src/types/Policy.d.ts +139 -0
- package/lib/src/types/Policy.js +17 -0
- package/lib/src/types/Provider.d.ts +810 -0
- package/lib/src/types/Role.d.ts +84 -0
- package/lib/src/types/RoleProvider.d.ts +35 -0
- package/lib/src/types/Schema.d.ts +425 -0
- package/lib/src/types/Share.d.ts +172 -0
- package/lib/src/types/Share.js +81 -0
- package/lib/src/types/User.d.ts +304 -0
- package/lib/src/types/Version.d.ts +189 -0
- package/lib/src/types/WikiEngine.d.ts +128 -0
- package/lib/src/types/express.d.ts +120 -0
- package/lib/src/types/express.js +1 -0
- package/lib/src/types/guards.d.ts +78 -0
- package/lib/src/types/heic-decode.d.ts +16 -0
- package/lib/src/types/index.d.ts +19 -0
- package/lib/src/types/linkedom.d.ts +77 -0
- package/lib/src/types/markdown-it-plugins.d.ts +21 -0
- package/lib/src/utils/DeltaStorage.d.ts +118 -0
- package/lib/src/utils/LocaleUtils.d.ts +105 -0
- package/lib/src/utils/LocaleUtils.js +294 -0
- package/lib/src/utils/PageNameMatcher.d.ts +98 -0
- package/lib/src/utils/PathPreflight.d.ts +38 -0
- package/lib/src/utils/PathPreflight.js +50 -0
- package/lib/src/utils/SectionUtils.d.ts +40 -0
- package/lib/src/utils/addonConfigLayer.d.ts +144 -0
- package/lib/src/utils/addonConfigLayer.js +382 -0
- package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
- package/lib/src/utils/addonDatabaseRules.js +57 -0
- package/lib/src/utils/addonHost.d.ts +51 -0
- package/lib/src/utils/addonHost.js +72 -0
- package/lib/src/utils/addonInstaller.d.ts +109 -0
- package/lib/src/utils/addonLock.d.ts +37 -0
- package/lib/src/utils/addonLock.js +48 -0
- package/lib/src/utils/addonPageSync.d.ts +45 -0
- package/lib/src/utils/addonProvenance.d.ts +48 -0
- package/lib/src/utils/addonTarball.d.ts +41 -0
- package/lib/src/utils/addonsPathResolver.d.ts +76 -0
- package/lib/src/utils/addonsPathResolver.js +129 -0
- package/lib/src/utils/assetMetadataTags.d.ts +66 -0
- package/lib/src/utils/atomicWrite.d.ts +28 -0
- package/lib/src/utils/auditConfigChange.d.ts +57 -0
- package/lib/src/utils/auditEventNames.d.ts +50 -0
- package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
- package/lib/src/utils/auditEventNames.generated.js +168 -0
- package/lib/src/utils/auditEventNames.js +49 -0
- package/lib/src/utils/auditEvents.d.ts +265 -0
- package/lib/src/utils/auditEvents.js +309 -0
- package/lib/src/utils/auditRegistry.d.ts +102 -0
- package/lib/src/utils/auditRegistry.js +168 -0
- package/lib/src/utils/bip39English.d.ts +2 -0
- package/lib/src/utils/configEnvKeys.d.ts +68 -0
- package/lib/src/utils/configFiles.d.ts +74 -0
- package/lib/src/utils/configFiles.js +150 -0
- package/lib/src/utils/delegation.d.ts +48 -0
- package/lib/src/utils/deriveUsername.d.ts +21 -0
- package/lib/src/utils/explainMediaPath.d.ts +123 -0
- package/lib/src/utils/formerTitles.d.ts +69 -0
- package/lib/src/utils/frontmatterAccess.d.ts +54 -0
- package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
- package/lib/src/utils/imageTransform.d.ts +52 -0
- package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
- package/lib/src/utils/jobSlots.d.ts +43 -0
- package/lib/src/utils/keywordNormalizer.d.ts +127 -0
- package/lib/src/utils/logger.d.ts +70 -0
- package/lib/src/utils/logger.js +106 -0
- package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
- package/lib/src/utils/managerUtils.d.ts +40 -0
- package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
- package/lib/src/utils/orgFilename.d.ts +22 -0
- package/lib/src/utils/pageFeature.d.ts +27 -0
- package/lib/src/utils/pageFrontmatter.d.ts +21 -0
- package/lib/src/utils/pageNotes.d.ts +150 -0
- package/lib/src/utils/pageTitleRule.d.ts +37 -0
- package/lib/src/utils/pageToArticle.d.ts +59 -0
- package/lib/src/utils/pageUrl.d.ts +18 -0
- package/lib/src/utils/passwordChange.d.ts +12 -0
- package/lib/src/utils/passwordHash.d.ts +60 -0
- package/lib/src/utils/permissionCatalog.d.ts +52 -0
- package/lib/src/utils/pluginFormatters.d.ts +452 -0
- package/lib/src/utils/pluginFormatters.js +714 -0
- package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
- package/lib/src/utils/privateStoreAccess.d.ts +57 -0
- package/lib/src/utils/privateStoreAccess.js +65 -0
- package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
- package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
- package/lib/src/utils/privateStoreDoor.d.ts +181 -0
- package/lib/src/utils/privateStoreExport.d.ts +72 -0
- package/lib/src/utils/privateStoreFiles.d.ts +120 -0
- package/lib/src/utils/privateStoreMeta.d.ts +64 -0
- package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
- package/lib/src/utils/privateStorePath.d.ts +259 -0
- package/lib/src/utils/privateStorePath.js +436 -0
- package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
- package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
- package/lib/src/utils/proxyClaim.d.ts +59 -0
- package/lib/src/utils/proxyClaim.js +30 -0
- package/lib/src/utils/redactPrivateNames.d.ts +21 -0
- package/lib/src/utils/redactPrivateNames.js +89 -0
- package/lib/src/utils/redactSecrets.d.ts +58 -0
- package/lib/src/utils/redactSecrets.js +171 -0
- package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
- package/lib/src/utils/roleGrants.d.ts +35 -0
- package/lib/src/utils/safeRedirect.d.ts +32 -0
- package/lib/src/utils/schedule.d.ts +54 -0
- package/lib/src/utils/secretFileMode.d.ts +17 -0
- package/lib/src/utils/seededShippedPages.d.ts +45 -0
- package/lib/src/utils/sensitiveValues.d.ts +59 -0
- package/lib/src/utils/sensitiveValues.js +106 -0
- package/lib/src/utils/sessionEnd.d.ts +12 -0
- package/lib/src/utils/sessionGeneration.d.ts +24 -0
- package/lib/src/utils/sessionIdle.d.ts +41 -0
- package/lib/src/utils/sessionLimits.d.ts +68 -0
- package/lib/src/utils/shareLockbox.d.ts +64 -0
- package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
- package/lib/src/utils/sniffMime.d.ts +21 -0
- package/lib/src/utils/storeSearchIndex.d.ts +75 -0
- package/lib/src/utils/subjectMayDo.d.ts +21 -0
- package/lib/src/utils/systemCategoryRules.d.ts +30 -0
- package/lib/src/utils/systemCategoryRules.js +41 -0
- package/lib/src/utils/userCreateError.d.ts +62 -0
- package/lib/src/utils/username.d.ts +53 -0
- package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
- package/lib/src/utils/version.d.ts +84 -0
- package/lib/src/utils/videoFrame.d.ts +21 -0
- 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[];
|